Skip to content

Commit

Permalink
Merge pull request #25 from toddjordan/support-actions
Browse files Browse the repository at this point in the history
[WIP] Support closure actions for handling changes to the form
toddjordan committed Feb 17, 2016
2 parents 0c02594 + 0eae9d4 commit 6464dbc
Showing 9 changed files with 259 additions and 7 deletions.
40 changes: 35 additions & 5 deletions addon/components/dynamic-form.js
Original file line number Diff line number Diff line change
@@ -22,12 +22,46 @@ const DynamicForm = Ember.Component.extend({

didReceiveAttrs() {
let schemaObj = this._initSchema(this.get('schema'));
const schemaWithActions = this._addActions(schemaObj);
const schemaWithPostRender = this._buildPostRender(schemaObj);
const schemaWithActions = this._addActions(schemaWithPostRender);
const filteredSchema = this._processFilters(schemaWithActions);
const mappedSchema = this._replaceKeywordsWithFunctions(filteredSchema);
this.set('renderSchema', mappedSchema);
},

_buildPostRender(schemaObj) {
let postRenderFns = [];
if (this.get('changeAction')) {
let fields = Object.keys(schemaObj.schema.properties);
const changeAction = this.get('changeAction');
let changeFn = function (control) {
fields.forEach((field) => {
control.childrenByPropertyId[field].on('keyup', function (e) {
changeAction(e, field);
});
control.childrenByPropertyId[field].on('click', function (e) {
changeAction(e, field);
});
});
};
postRenderFns.push(changeFn);
}
if (this.get('postRender')) {
postRenderFns.push(this.get('postRender'));
}

if (postRenderFns.length > 0) {
if (schemaObj.postRender) {
postRenderFns.push(schemaObj.postRender);
}
schemaObj.postRender = function () {
const args = arguments;
postRenderFns.forEach((fn) => fn(args[0]));
};
}
return schemaObj;
},

_addActions(schemaObj) {
return _.reduce(this.get('formActions'), (result, value, key) => {
if ((((((result || {}).options || {}).form || {}).buttons || {})[key])) {
@@ -61,10 +95,6 @@ const DynamicForm = Ember.Component.extend({
} else {
schemaObj = _.clone(schema, true);
}
if (this.get('postRender')) {
const postRenderFn = this.get('postRender');
schemaObj.postRender = postRenderFn;
}
if (this.get('data')) {
schemaObj.data = this.get('data');
}
13 changes: 13 additions & 0 deletions tests/dummy/app/controllers/demos/change-action.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import Ember from 'ember';

export default Ember.Controller.extend({
actions: {
updateOnChange(event) {
Ember.Logger.debug('changed', event);
const propertyName = event.target.name;
const value = event.target.value;
Ember.Logger.debug('sending updateModel actions with ', propertyName, value);
this.send('updateModel', propertyName, value);
}
}
});
1 change: 1 addition & 0 deletions tests/dummy/app/router.js
Original file line number Diff line number Diff line change
@@ -13,6 +13,7 @@ Router.map(function() {
this.route('basic-usage');
this.route('data');
this.route('actions');
this.route('change-action');
});

this.route('demo', function() {});
46 changes: 46 additions & 0 deletions tests/dummy/app/routes/demos/change-action.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import Ember from 'ember';

export default Ember.Route.extend({
data: Ember.Object.create({
name: 'Todd Jordan',
feedback: 'Ember + Alpaca = Awesome',
ranking: 'excellent'
}),

actions: {
updateModel(propertyName, value) {
Ember.Logger.debug('changed', propertyName, value);
this.set(`data.${propertyName}`, value);
}
},

model() {
const schema = {
"schema": {
"title":"User Feedback",
"description":"What do you think about Alpaca?",
"type":"object",
"properties": {
"name": {
"type":"string",
"title":"Name"
},
"feedback": {
"type":"string",
"title":"Feedback"
},
"ranking": {
"type":"string",
"title":"Ranking",
"enum":['excellent','ok','so so']
}
}
}
};

return {
schema,
data: this.get('data')
};
}
});
3 changes: 2 additions & 1 deletion tests/dummy/app/templates/demos.hbs
Original file line number Diff line number Diff line change
@@ -10,7 +10,8 @@
{{#link-to "demos.formatting" class="list-group-item"}}Formatting{{/link-to}}
{{#link-to "demos.data" class="list-group-item"}}Data{{/link-to}}
{{#link-to "demos.filter-rules" class="list-group-item"}}Filter Rules{{/link-to}}
{{#link-to "demos.actions" class="list-group-item"}}Form Actions{{/link-to}}
{{#link-to "demos.actions" class="list-group-item"}}Button Actions{{/link-to}}
{{#link-to "demos.change-action" class="list-group-item"}}Change Action{{/link-to}}
</div>
</div>
</div>
2 changes: 1 addition & 1 deletion tests/dummy/app/templates/demos/basic-usage.hbs
Original file line number Diff line number Diff line change
@@ -70,6 +70,6 @@ export default Ember.Route.extend({
<p>Once the object is made available, you can use it within your template to render the form.</p>

{{#highlight-js}}
<pre>/app/templates/demos/basic-usage.hbs<code>\{{dynamic-form schema=model.basicObject}}</code></pre>
<pre>/app/templates/demos/basic-usage.hbs<code>\{{dynamic-form schema=model}}</code></pre>
{{/highlight-js}}

97 changes: 97 additions & 0 deletions tests/dummy/app/templates/demos/change-action.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<h2>Change Actions</h2>

<p>This form has a closure action that will update the text below as the form is updated</p>

{{dynamic-form schema=model.schema data=model.data changeAction=(action "updateOnChange") }}

<h4>Model Values</h4>

<ul>
<li>name: {{model.data.name}}</li>
<li>feedback: {{model.data.feedback}}</li>
<li>ranking: {{model.data.ranking}}</li>
</ul>

<hr/>

<p>The dynamic-form component supports the data down actions up (DDAU) pattern by taking an action that will be executed whenever a rendered form field changes.</p>

<p>In the example our controller defines a closure action that will receive events from our dynamic form component, pull out the field name and value, and then send an action to the server.</p>

{{#highlight-js}}
<pre>/app/controller/demos/change-action.js<code>
import Ember from 'ember';

export default Ember.Controller.extend({
actions: {
updateOnChange(event) {
const propertyName = event.target.name;
const value = event.target.value;
this.send('updateModel', propertyName, value);
}
}
});
</code></pre>
{{/highlight-js}}

<p>The route sets up a dynamic form schema as well as data to prepopulate it with. It also uses the update action to update the data model.</p>

{{#highlight-js}}
<pre>/app/routes/demos/change-action.js<code>
import Ember from 'ember';

export default Ember.Route.extend({
data: Ember.Object.create({
name: 'Todd Jordan',
feedback: 'Ember + Alpaca = Awesome',
ranking: 'excellent'
}),

actions: {
updateModel(propertyName, value) {
Ember.Logger.debug('changed', propertyName, value);
this.set(`data.${propertyName}`, value);
}
},

model() {
const schema = {
"schema": {
"title":"User Feedback",
"description":"What do you think about Alpaca?",
"type":"object",
"properties": {
"name": {
"type":"string",
"title":"Name"
},
"feedback": {
"type":"string",
"title":"Feedback"
},
"ranking": {
"type":"string",
"title":"Ranking",
"enum":['excellent','ok','so so']
}
}
}
};

return {
schema,
data: this.get('data')
};
}
});
</code></pre>
{{/highlight-js}}

<p>Finally, the template defines a dynamic-form with a closure action that will provide the controller action.</p>

{{#highlight-js}}
<pre>/app/templates/demos/change-action.hbs<code>\{{dynamic-form schema=model.schema
data=model.data
changeAction=(action "updateOnChange")}}
</code></pre>
{{/highlight-js}}
64 changes: 64 additions & 0 deletions tests/integration/components/change-action-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { moduleForComponent, test } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';

const schemaObject = {
"schema": {
"title": "What do you think of Alpaca?",
"type": "object",
"properties": {
"name": {
"type": "string",
"title": "Name"
},
"ranking": {
"type": "string",
"title": "Ranking",
"enum": ['excellent', 'not too shabby', 'alpaca built my hotrod']
}
}
}

};

moduleForComponent('/dynamic-form' , 'Integration | Component | dynamic-form:changeAction', {
integration: true
});

test('should fire action on changes in form', function (assert) {
const done = assert.async();
this.on('changeAction', function (event) {
assert.equal(event.target.value, 'todd');
done();
});

const schema = _.clone(schemaObject, true);
let postRender = () => {
this.$('.alpaca-field-text input').val('todd').keyup();
};
this.set('postRender', postRender);
this.set('schema', schema);

this.render(hbs`
{{dynamic-form postRender=postRender schema=schema changeAction=(action 'changeAction')}}
`);

});

test('should fire action and perform postRender when defined on schema', function (assert) {
const done = assert.async();
this.on('changeAction', function (event) {
assert.equal(event.target.value, 'todd');
done();
});

const schema = _.clone(schemaObject, true);
schema.postRender = () => {
this.$('.alpaca-field-text input').val('todd').keyup();
};
this.set('schema', schema);

this.render(hbs`
{{dynamic-form schema=schema changeAction=(action 'changeAction')}}
`);

});

0 comments on commit 6464dbc

Please sign in to comment.