Skip to content

Commit

Permalink
Add ability to use closure actions to react to form value changes
Browse files Browse the repository at this point in the history
start over after wrong turn

Add note on jquery resolution

suggested resolution for deprecation causes 2 tests to fail

fixed test failure

Add on change action support

Add documentation for new change action

Fix phantom test

simplify gatering of fields

add action on keyup and click
  • Loading branch information
toddjordan committed Feb 17, 2016
1 parent b29079f commit 0eae9d4
Show file tree
Hide file tree
Showing 11 changed files with 265 additions and 9 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ An Ember addon for creating dynamic forms, powered by [alpacajs](http://alpacajs

`ember install ember-cli-dynamic-forms`

For now, you'll get a jquery resolution prompt when running install. You'll want to pick the 1.11.3 option.

## Upgrading

It's advisable to run `ember g ember-cli-dynamic-forms` between upgrades as dependencies may have been added, removed, or upgraded between releases. Please try this, along with clearing node_modules and bower_components before reporting issues after upgrading.
Expand Down
45 changes: 38 additions & 7 deletions addon/components/dynamic-form.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Ember from 'ember';
import getOwner from 'ember-getowner-polyfill';

const TYPE_MAP = {
validator: {
Expand All @@ -21,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])) {
Expand All @@ -44,7 +79,7 @@ const DynamicForm = Ember.Component.extend({
const newSchema = _.reduce(optionFields, (result, val, key) => {
if(val['filter-rules']) {
val['filter-rules'].forEach((element) => {
const filterRule = this.container.lookup(`${element}:dynamic-forms.filter-rules`);
const filterRule = getOwner(this).lookup(`${element}:dynamic-forms.filter-rules`);
filterRule.filter(key, result);
});
}
Expand All @@ -60,18 +95,14 @@ 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');
}
return schemaObj;
},

_replaceKeywordsWithFunctions(schemaObj) {
const container = this.container;
const container = getOwner(this);
const replaceWithFunction = function (object, value, key) {
if (TYPE_MAP.hasOwnProperty(key) && typeof value === 'string') {
const type = TYPE_MAP[key];
Expand Down
1 change: 1 addition & 0 deletions package.json
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"ember-disable-prototype-extensions": "^1.0.0",
"ember-disable-proxy-controllers": "^1.0.1",
"ember-export-application-global": "^1.0.4",
"ember-getowner-polyfill": "1.0.0",
"ember-resolver": "^2.0.3",
"ember-try": "~0.0.8"
},
Expand Down
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
Expand Up @@ -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() {});
Expand Down
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
Expand Up @@ -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>
Expand Down
2 changes: 1 addition & 1 deletion tests/dummy/app/templates/demos/basic-usage.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -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 0eae9d4

Please sign in to comment.