From 516775e7cc5e6d43d82a0bed5998caece58a0667 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gast=C3=B3n=20I=2E=20Silva?= Date: Thu, 9 Jul 2015 04:02:15 -0500 Subject: [PATCH] rewrite the handling of promises for autocomplete and tokens --- addon/components/input-tokenfield.js | 112 ++++++++++-------- .../ember-cli-bootstrap-tokenfield/index.js | 2 +- tests/dummy/app/controllers/application.js | 14 +++ tests/dummy/app/templates/-tokens-promise.hbs | 34 ++++++ tests/dummy/app/templates/application.hbs | 2 + .../unit/components/input-tokenfield-test.js | 75 +++++++++++- 6 files changed, 188 insertions(+), 51 deletions(-) create mode 100644 tests/dummy/app/templates/-tokens-promise.hbs diff --git a/addon/components/input-tokenfield.js b/addon/components/input-tokenfield.js index 245ef7e..4ac9c24 100644 --- a/addon/components/input-tokenfield.js +++ b/addon/components/input-tokenfield.js @@ -7,78 +7,94 @@ export default Ember.TextField.extend({ classNames: ['form-control'], - _setElement$: function() { - var element$ = Ember.$('#' + this.get('elementId')); - this.set('element$', element$); + /* + Appends only truthy values that are not promises. + */ + _appendOption: function(options, attributeName) { + let attrValue = this.get(attributeName); + if (attrValue && typeof attrValue.then !== 'function') { + options[attributeName] = attrValue; + } + return options; }, - tokens: null, - - autocomplete: null, + _buildTokenfieldOptions: function() { + var options = {}; + options = this._appendOption(options, 'limit'); + options = this._appendOption(options, 'minLength'); + options = this._appendOption(options, 'minWidth'); + options = this._appendOption(options, 'showAutocompleteOnFocus'); + options = this._appendOption(options, 'delimiter'); + options = this._appendOption(options, 'beautify'); + options = this._appendOption(options, 'inputType'); + options = this._appendOption(options, 'createTokenOnBlur'); + options = this._appendOption(options, 'typeahead'); + options = this._appendOption(options, 'tokens'); + options = this._appendOption(options, 'autocomplete'); + return options; + }, didInsertElement: function() { - this._setElement$(); - - var tokens = this._processTokensObject(); - var autocomplete = this._processAutocompleteObject(); + let element$ = Ember.$('#' + this.get('elementId')); + this.set('element$', element$); - var options = {}; - if ( tokens ) { options.tokens = tokens; } - if ( this.limit ) { options.limit = this.limit; } - if ( this.minLength ) { options.minLength = this.minLength; } - if ( this.minWidth ) { options.minWidth = this.minWidth; } - if ( autocomplete ) { options.autocomplete = autocomplete; } - if ( this.showAutocompleteOnFocus ) { - options.showAutocompleteOnFocus = true; - } + var options = this._buildTokenfieldOptions(); - var typeahead = this.get('typeahead'); - if ( typeahead ) { options.typeahead = typeahead; } - if ( this.createTokensOnBlur ) { - options.createTokensOnBlur = this.createTokensOnBlur; - } - if ( this.delimiter ) { options.delimiter = this.delimiter; } - if ( this.beautify ) { options.beautify = this.beautify; } - if ( this.inputType ) { options.inputType = this.inputType; } + element$.tokenfield(options); - this.get('element$').tokenfield(options); + this._consumeAutocompletePromise(); + this._consumeTokensPromise(); }, - /* - Knows how to handle the autocomplete object if its a promise. - */ - _processAutocompleteObject: Ember.observer('autocomplete', function() { + _consumeAutocompletePromise: function() { var autocomplete = this.get('autocomplete'); - if ( ! autocomplete || typeof autocomplete.then !== 'function') { - return autocomplete; + if (!autocomplete || typeof autocomplete.then !== 'function') { + return; // nothing to do, value already passed in with the options object } - var _this = this; - autocomplete.then(function(resolvedAutocomplete) { - _this.get('element$') - .data('bs.tokenfield') - .$input.autocomplete(resolvedAutocomplete); + autocomplete.then(autocompleteValues => { + let element$ = this.get('element$'); + if (element$) { + element$ + .data('bs.tokenfield') + .$input.autocomplete(autocompleteValues); + } + + this.set('autocomplete', autocompleteValues); }); + }, - return null; + _observeAutocomplete: Ember.observer('autocomplete', function() { + this._consumeAutocompletePromise(); }), - _processTokensObject: Ember.observer('tokens', 'tokens.[]', function() { + _consumeTokensPromise: function() { var tokens = this.get('tokens'); - if ( ! tokens ) { - return; + if (!tokens || typeof tokens.then !== 'function') { + return; // nothing to do, value already passed in with the options object } // test for Ember.ArrayProxy - if ( typeof tokens.get('hasArrayObservers') === 'boolean') { - tokens = tokens.toArray(); + /* + if ( tokens.get && typeof tokens.get('hasArrayObservers') === 'boolean') { + let tokensList = tokens.toArray(); + element$.tokenfield('setTokens', tokensList); } + */ + + tokens.then(tokensList => { + let element$ = this.get('element$'); + if (element$) { + element$.tokenfield('setTokens', tokensList); + } + this.set('tokens', tokensList); + }); + }, - this.get('element$').tokenfield('setTokens', tokens); - - return tokens; + _observeTokens: Ember.observer('tokens', function() { + this._consumeTokensPromise(); }) }); diff --git a/blueprints/ember-cli-bootstrap-tokenfield/index.js b/blueprints/ember-cli-bootstrap-tokenfield/index.js index 0f60924..257dd63 100644 --- a/blueprints/ember-cli-bootstrap-tokenfield/index.js +++ b/blueprints/ember-cli-bootstrap-tokenfield/index.js @@ -18,7 +18,7 @@ module.exports = { var _this = this; console.log('Note: bootstrap-tokenfield depends on jquery 2.1.0'); - console.log(' Use that.'); + console.log(' Use that version or higher.'); return _this.addBowerPackageToProject('jquery', '2.1.0').then(function() { return _this.addBowerPackageToProject('jquery-ui', '1.11.4').then(function() { diff --git a/tests/dummy/app/controllers/application.js b/tests/dummy/app/controllers/application.js index 22d72ed..fcbb9f7 100644 --- a/tests/dummy/app/controllers/application.js +++ b/tests/dummy/app/controllers/application.js @@ -23,6 +23,20 @@ export default Ember.Controller.extend({ }); }), + tokensPromise: Ember.computed(function() { + var tokens = [ + {value: 1, label: 'one'}, + {value: 2, label: 'two'}, + {value: 3, label: 'three'} + ]; + + return new Ember.RSVP.Promise(function(resolve) { + Ember.run.later(function() { + resolve( tokens ); + }, 3000); // simulate a 3 seconds delay + }); + }), + typeahead: (function() { var engine = new Bloodhound({ local: [{value: 'red'}, {value: 'blue'}, {value: 'green'} , diff --git a/tests/dummy/app/templates/-tokens-promise.hbs b/tests/dummy/app/templates/-tokens-promise.hbs new file mode 100644 index 0000000..beaace1 --- /dev/null +++ b/tests/dummy/app/templates/-tokens-promise.hbs @@ -0,0 +1,34 @@ +
+
+ Set tokens with a promise +
+ +
+

Promises can be used to set the current tokens.

+ {{input-tokenfield tokens=tokensPromise + autocomplete=autocomplete + placeholder="Type something and hit enter"}} +
+ + Controller +
// contribed example, this data is likely to come from ember-data
+tokensPromise: Ember.computed(function() {
+  var tokens = [
+    {value: 1, label: 'one'},
+    {value: 2, label: 'two'},
+    {value: 3, label: 'three'}
+  ];
+
+  return new Ember.RSVP.Promise(function(resolve) {
+    Ember.run.later(function() {
+      resolve( tokens );
+    }, 3000); // simulate a 3 seconds delay 
+  });
+})
+ + Template +
\{{input-tokenfield tokens=tokensPromise 
+                   autocomplete=autocomplete
+                   placeholder="Type something and hit enter"}}
+
+
diff --git a/tests/dummy/app/templates/application.hbs b/tests/dummy/app/templates/application.hbs index 693694a..d800c97 100644 --- a/tests/dummy/app/templates/application.hbs +++ b/tests/dummy/app/templates/application.hbs @@ -24,6 +24,8 @@ ember install:addon ember-cli-bootstrap-tokenfield {{partial 'autocomplete-promise'}} + {{partial 'tokens-promise'}} + {{partial 'typeahead'}} diff --git a/tests/unit/components/input-tokenfield-test.js b/tests/unit/components/input-tokenfield-test.js index 729cae0..6e83060 100644 --- a/tests/unit/components/input-tokenfield-test.js +++ b/tests/unit/components/input-tokenfield-test.js @@ -1,9 +1,10 @@ +import Ember from 'ember'; import { moduleForComponent, test } from 'ember-qunit'; -moduleForComponent('input-tokenfield', { +moduleForComponent('input-tokenfield', 'Unit | Component | input-tokenfield', { // specify the other units that are required for this test // needs: ['component:foo', 'helper:bar'] unit: true @@ -21,8 +22,9 @@ test('it renders', function(assert) { assert.equal(component._state, 'inDOM'); }); -test('getTokens', function(assert) { +test('tokenfield.getTokens', function(assert) { assert.expect(2); + var component = this.subject({ value: 'red' }); @@ -39,3 +41,72 @@ test('getTokens', function(assert) { expected = []; assert.deepEqual(tokens, expected); }); + +test('_appendOption', function(assert) { + assert.expect(3); + + let component = this.subject({ + autocomplete: {source: ['1', '2']} + }); + + let options = {}; + options = component._appendOption(options, 'not-a-real-option'); + assert.deepEqual(options, {}, 'only truthy values are added'); + + options = {}; + options = component._appendOption(options, 'autocomplete'); + assert.deepEqual(options, {autocomplete: {source: ['1', '2']}}); + + Ember.run(() => { + this.render(); + component.set('autocomplete', Ember.RSVP.resolve({source: ['a', 'b']})); + options = {}; + options = component._appendOption(options, 'autocomplete'); + assert.deepEqual(options, {}, 'promises are ignored'); + }); +}); + +test('autocomplete is a promise', function(assert) { + assert.expect(1); + + let autocompletePromise = new Ember.RSVP.Promise(function(resolve) { + Ember.run.later(function() { + let autocomplete = {source: ['a', 'b']}; + resolve(autocomplete); + }, 7); // 7 ms must be enough to not let the test pass synchronously + }); + let component = this.subject({ + autocomplete: autocompletePromise + }); + this.render(); + + Ember.run(() => { + component._consumeAutocompletePromise(); + component.get('autocomplete').then(() => { + assert.deepEqual(component.get('autocomplete'), {source: ['a', 'b']}); + }); + }); +}); + +test('tokens is a promise', function(assert) { + assert.expect(1); + + let tokensPromise = new Ember.RSVP.Promise(function(resolve) { + Ember.run.later(function() { + let tokens = [{value: '1', label: 'uno'}, {value: '2', label: 'dos'}]; + resolve(tokens); + }, 7); // 7 ms must be enough to not let the test pass synchronously + }); + let component = this.subject({ + tokens: tokensPromise + }); + this.render(); + + Ember.run(() => { + component._consumeTokensPromise(); + component.get('tokens').then(() => { + let expected = [{value: '1', label: 'uno'}, {value: '2', label: 'dos'}]; + assert.deepEqual(component.get('tokens'), expected); + }); + }); +});