Skip to content

Commit 457cb16

Browse files
Merge branch 'develop' into issue-255
2 parents 9ace33e + 338104b commit 457cb16

File tree

9 files changed

+302
-67
lines changed

9 files changed

+302
-67
lines changed

app/concepts/matestack/ui/core/form/form.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,27 @@ const componentDef = {
104104
if (self.componentConfig["success"] != undefined && self.componentConfig["success"]["emit"] != undefined) {
105105
matestackEventHub.$emit(self.componentConfig["success"]["emit"], response.data);
106106
}
107-
if (self.componentConfig["success"] != undefined && self.componentConfig["success"]["transition"] != undefined && self.$store != undefined) {
107+
if (self.componentConfig["success"] != undefined
108+
&& self.componentConfig["success"]["transition"] != undefined
109+
&& (
110+
self.componentConfig["success"]["transition"]["follow_response"] == undefined
111+
||
112+
self.componentConfig["success"]["transition"]["follow_response"] === false
113+
)
114+
&& self.$store != undefined
115+
) {
108116
let path = self.componentConfig["success"]["transition"]["path"]
109117
self.$store.dispatch('navigateTo', {url: path, backwards: false})
118+
return;
119+
}
120+
if (self.componentConfig["success"] != undefined
121+
&& self.componentConfig["success"]["transition"] != undefined
122+
&& self.componentConfig["success"]["transition"]["follow_response"] === true
123+
&& self.$store != undefined
124+
) {
125+
let path = response.data["transition_to"]
126+
self.$store.dispatch('navigateTo', {url: path, backwards: false})
127+
return;
110128
}
111129
self.setProps(self.data, null);
112130
self.initValues()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
%object{@tag_attributes}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
module Matestack::Ui::Core::Object
2+
class Object < Matestack::Ui::Core::Component::Static
3+
def setup
4+
@tag_attributes.merge!({
5+
width: options[:width],
6+
height: options[:height],
7+
data: options[:data],
8+
form: options[:form],
9+
name: options[:name],
10+
type: options[:type],
11+
usemap: options[:usemap]
12+
})
13+
end
14+
end
15+
end

docs/components/form.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,49 @@ We then get displayed our nice success message (`server says: form submitted suc
275275

276276
If we fill in the the input field there and hit the submit button, we not only see the failure messages (`server says: form had errors` and `'foo': [ 'seems to be invalid' ]`), we also get transferred back to the first page, just the way we specified this behavior in the page definition above!
277277

278+
### Example 3.1: Async submit request with success transition - dynamically determined by server
279+
280+
In the example shown above, the `success` `transition` is statically defined. Sometimes the `transition` needs to be dynamically controlled within the server action.
281+
Imagine creating a new Active Record instance with a `form`. If you want to show the fresh instance on another page and therefore want to define a `transition` after successful form submission, you would need to know the ID of the fresh instance! That is not possible, as the ID is auto-generated and depends on the current environment/state. Therefore you can tell the `form` component to follow a transition, which the server action defines after creating the new instance (and now knowing the ID):
282+
283+
On the `page`:
284+
```ruby
285+
#...
286+
287+
def form_config
288+
return {
289+
for: :my_object,
290+
method: :post,
291+
path: :success_form_test_path,
292+
success: {
293+
emit: 'my_form_success',
294+
transition: {
295+
follow_response: true # follow the serverside transition
296+
}
297+
}
298+
}
299+
end
300+
```
301+
On the `controller` `action`:
302+
303+
```ruby
304+
#...
305+
def model_submit
306+
@test_model = TestModel.create(model_params)
307+
if @test_model.errors.any?
308+
render json: {
309+
message: 'server says: something went wrong!',
310+
errors: @test_model.errors
311+
}, status: :unproccessable_entity
312+
else
313+
render json: {
314+
message: 'server says: form submitted successfully!',
315+
transition_to: some_other_path(id: @test_model.id) #tell the form component where to transition to with the id, which was not available before
316+
}, status: :ok
317+
end
318+
end
319+
```
320+
278321
### Example 4: Multiple input fields of different types
279322

280323
Of course, our input core component accepts not only 'text', but very different input types: In this example, we will introduce 'password', 'number', 'email', 'textarea' types!

docs/components/object.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# matestack core component: Object
2+
3+
Show [specs](/spec/usage/components/object_spec.rb)
4+
5+
The HTML `<object>` tag implemented in Ruby.
6+
7+
## Parameters
8+
9+
This component can take 9 optional configuration params, but at least one of the `data` or `type` attribute **MUST** be defined.
10+
11+
#### # class (optional)
12+
Expects a string with all classes the `<object>` should have.
13+
14+
#### # data (optional)
15+
Expects a string that specifies the URL of the resource to be used by the `<object`.
16+
17+
#### # form (optional)
18+
Expects a string that contains one or more *form_id*-s to specify one or more forms the `<object>` belongs to.
19+
20+
#### # height (optional)
21+
Expects a number to specify the height of the `<object>`.
22+
23+
#### # id (optional)
24+
Expects a string with all ids the `<object>` should have.
25+
26+
#### # name (optional)
27+
Expects a string that specifies a name for the `<object>`.
28+
29+
#### # type (optional)
30+
Expects a string to specify the media type of data specified in the data attribute.
31+
32+
#### # usemap (optional)
33+
Expects a string to specify the name of a client-side image map to be used with the `<object>`.
34+
35+
#### # width (optional)
36+
Expects a number to specify the width of the `<object>`.
37+
38+
39+
## Example 1
40+
41+
```ruby
42+
object width: 400, height: 400, data: 'helloworld.swf'
43+
```
44+
45+
returns
46+
47+
```html
48+
<object width="400" height="400" data="helloworld.swf"></object>
49+
```
50+
51+
## Example 2
52+
53+
```ruby
54+
object id: 'my-id', class: 'my-class', width: 400, height: 400, data: 'helloworld.swf'
55+
```
56+
57+
returns
58+
59+
```html
60+
<object id="my-id" class="my-class" width="400" height="400" data="helloworld.swf"></object>
61+
```

spec/usage/components/form_spec.rb

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@ def success_submit
2424
render json: { message: "server says: form submitted successfully" }, status: 200
2525
end
2626

27+
def success_submit_with_transition
28+
render json: {
29+
message: "server says: form submitted successfully",
30+
transition_to: form_test_page_4_path(id: 42)
31+
}, status: 200
32+
end
33+
2734
def failure_submit
2835
render json: {
2936
message: "server says: form had errors",
@@ -35,6 +42,7 @@ def failure_submit
3542

3643
Rails.application.routes.append do
3744
post '/success_form_test/:id', to: 'form_test#success_submit', as: 'success_form_test'
45+
post '/success_form_test_with_transition/:id', to: 'form_test#success_submit_with_transition', as: 'success_form_test_with_transition'
3846
post '/failure_form_test/:id', to: 'form_test#failure_submit', as: 'failure_form_test'
3947
end
4048
Rails.application.reload_routes!
@@ -376,6 +384,108 @@ def page2
376384

377385
end
378386

387+
it "Example 5 - Async submit request with success transition determined by server response" do
388+
class Apps::ExampleApp < Matestack::Ui::App
389+
390+
def response
391+
components {
392+
heading size: 1, text: "My Example App Layout"
393+
main do
394+
page_content
395+
end
396+
async show_on: "my_form_success", hide_after: 300 do
397+
plain "{{event.data.message}}"
398+
end
399+
async show_on: "my_form_failure", hide_after: 300 do
400+
plain "{{event.data.message}}"
401+
plain "{{event.data.errors}}"
402+
end
403+
}
404+
end
405+
406+
end
407+
408+
module Pages::ExampleApp
409+
410+
end
411+
412+
class Pages::ExampleApp::ExamplePage3 < Matestack::Ui::Page
413+
414+
def response
415+
components {
416+
heading size: 2, text: "This is Page 3"
417+
form form_config, :include do
418+
form_input id: "my-test-input-on-page-3", key: :foo, type: :text
419+
form_submit do
420+
button text: "Submit me!"
421+
end
422+
end
423+
}
424+
end
425+
426+
def form_config
427+
return {
428+
for: :my_object,
429+
method: :post,
430+
path: :success_form_test_with_transition_path,
431+
params: {
432+
id: 42
433+
},
434+
success: {
435+
emit: "my_form_success",
436+
transition: {
437+
follow_response: true
438+
}
439+
}
440+
}
441+
end
442+
443+
end
444+
445+
class Pages::ExampleApp::ExamplePage4 < Matestack::Ui::Page
446+
447+
def response
448+
components {
449+
heading size: 2, text: "This is Page 4"
450+
}
451+
end
452+
453+
end
454+
455+
class ExampleAppPagesController < ExampleController
456+
include Matestack::Ui::Core::ApplicationHelper
457+
458+
def page3
459+
responder_for(Pages::ExampleApp::ExamplePage3)
460+
end
461+
462+
def page4
463+
responder_for(Pages::ExampleApp::ExamplePage4)
464+
end
465+
466+
end
467+
468+
Rails.application.routes.append do
469+
scope :form_test do
470+
get 'page3', to: 'example_app_pages#page3', as: 'form_test_page_3'
471+
get 'page4/:id', to: 'example_app_pages#page4', as: 'form_test_page_4'
472+
end
473+
end
474+
Rails.application.reload_routes!
475+
476+
visit "form_test/page3"
477+
478+
expect(page).to have_content("This is Page 3")
479+
480+
fill_in "my-test-input-on-page-3", with: "bar"
481+
click_button "Submit me!"
482+
483+
expect(page).to have_content("server says: form submitted successfully")
484+
485+
expect(page).to have_content("This is Page 4")
486+
487+
end
488+
379489
describe "Form Input Component" do
380490

381491
it "Example 1 - Supports 'text', 'password', 'number', 'email', 'textarea' type" do

spec/usage/components/object_spec.rb

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
require_relative '../../support/utils'
2+
include Utils
3+
4+
describe 'Object Component', type: :feature, js: true do
5+
6+
it 'Example 1' do
7+
8+
class ExamplePage < Matestack::Ui::Page
9+
10+
def response
11+
components {
12+
# simple object
13+
object width: 400, height: 400, data: 'helloworld.swf'
14+
15+
# enhanced object
16+
object id: 'my-id', class: 'my-class', width: 400, height: 400, data: 'helloworld.swf'
17+
18+
# using all attributes
19+
object id: 'my-id', class: 'my-class', width: 400, height: 400, form: '#my_form', data: 'helloworld.swf', name: 'my_object', type: 'application/vnd.adobe.flash-movie', usemap: '#my_map'
20+
}
21+
end
22+
23+
end
24+
25+
visit '/example'
26+
27+
static_output = page.html
28+
29+
expected_static_output = <<~HTML
30+
<object data="helloworld.swf" height="400" width="400"></object>
31+
<object data="helloworld.swf" height="400" id="my-id" width="400" class="my-class"></object>
32+
<object data="helloworld.swf" form="#my_form" height="400" id="my-id" name="my_object" type="application/vnd.adobe.flash-movie" usemap="#my_map" width="400" class="my-class"></object>
33+
HTML
34+
35+
expect(stripped(static_output)).to include(stripped(expected_static_output))
36+
end
37+
38+
end

0 commit comments

Comments
 (0)