Skip to content

Commit

Permalink
rewrite the handling of promises for autocomplete and tokens
Browse files Browse the repository at this point in the history
  • Loading branch information
givanse committed Jul 9, 2015
1 parent 2fbd612 commit 516775e
Show file tree
Hide file tree
Showing 6 changed files with 188 additions and 51 deletions.
112 changes: 64 additions & 48 deletions addon/components/input-tokenfield.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
})

});
2 changes: 1 addition & 1 deletion blueprints/ember-cli-bootstrap-tokenfield/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
14 changes: 14 additions & 0 deletions tests/dummy/app/controllers/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'} ,
Expand Down
34 changes: 34 additions & 0 deletions tests/dummy/app/templates/-tokens-promise.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<div class="panel panel-info">
<div class="panel-heading">
Set tokens with a promise
</div>

<div class="panel-body">
<p>Promises can be used to set the current tokens.</p>
{{input-tokenfield tokens=tokensPromise
autocomplete=autocomplete
placeholder="Type something and hit enter"}}
<br>

Controller
<pre>// 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
});
})</pre>

Template
<pre>\{{input-tokenfield tokens=tokensPromise
autocomplete=autocomplete
placeholder="Type something and hit enter"}}</pre>
</div>
</div>
2 changes: 2 additions & 0 deletions tests/dummy/app/templates/application.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ ember install:addon ember-cli-bootstrap-tokenfield

{{partial 'autocomplete-promise'}}

{{partial 'tokens-promise'}}

{{partial 'typeahead'}}

</div><!-- col -->
Expand Down
75 changes: 73 additions & 2 deletions tests/unit/components/input-tokenfield-test.js
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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'
});
Expand All @@ -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);
});
});
});

0 comments on commit 516775e

Please sign in to comment.