Permalink
Browse files

Add choiceAttribute option to allow objects as choices and listTempla…

…te option to allow custom list item schema
  • Loading branch information...
ZenCocoon committed Sep 3, 2010
1 parent 618416b commit 586102b33ba68ad2697488233225628f7fb7e28f
Showing with 96 additions and 50 deletions.
  1. +55 −49 src/ui/controls/autocompleter.js
  2. +41 −1 test/functional/controls_autocompleter.html
@@ -1,14 +1,14 @@
(function(UI) {
/** section: scripty2 ui
* class S2.UI.Autocompleter < S2.UI.Base
*
**/
UI.Autocompleter = Class.create(UI.Base, {
NAME: "S2.UI.Autocompleter",
/**
* new S2.UI.Autocompleter(element, options)
*
@@ -17,25 +17,25 @@
initialize: function(element, options) {
this.element = $(element);
var opt = this.setOptions(options);
UI.addClassNames(this.element, 'ui-widget ui-autocompleter');
this.input = this.element.down('input[type="text"]');
if (!this.input) {
this.input = new Element('input', { type: 'text' });
this.element.insert(this.input);
}
this.input.insert({ before: this.button });
this.input.setAttribute('autocomplete', 'off');
this.name = opt.parameterName || this.input.readAttribute('name');
if (opt.choices) {
this.choices = opt.choices.clone();
}
this.menu = new UI.Menu();
this.element.insert(this.menu.element);
@@ -47,17 +47,17 @@
top: (iLayout.get('top') + iLayout.get('margin-box-height')) + 'px'
});
}).bind(this).defer();
this.observers = {
blur: this._blur.bind(this),
keyup: this._keyup.bind(this),
keydown: this._keydown.bind(this),
selected: this._selected.bind(this)
};
this.addObservers();
},
addObservers: function() {
this.input.observe('blur', this.observers.blur);
this.input.observe('keyup', this.observers.keyup);
@@ -66,19 +66,19 @@
this.menu.observe('ui:menu:selected',
this.observers.selected);
},
_schedule: function() {
this._unschedule();
this._timeout = this._change.bind(this).delay(this.options.frequency);
},
_unschedule: function() {
if (this._timeout) window.clearTimeout(this._timeout);
},
_keyup: function(event) {
var value = this.input.getValue();
if (value) {
if (value.blank() || value.length < this.options.minCharacters) {
// Empty values mean the menu should be hidden and all timers
@@ -95,16 +95,16 @@
this.menu.close();
this._unschedule();
}
this._value = value;
},
_keydown: function(event) {
if (UI.modifierUsed(event)) return;
if (!this.menu.isOpen()) return;
var keyCode = event.keyCode || event.charCode;
switch (event.keyCode) {
case Event.KEY_UP:
this.menu.moveHighlight(-1);
@@ -128,80 +128,86 @@
break;
}
},
// TODO: Implement tokenizing.
_getInput: function() {
return this.input.getValue();
},
// TODO: Implement tokenizing.
_setInput: function(value) {
this.input.setValue(value);
},
_change: function() {
this.findChoices();
},
/**
* S2.UI.Autocompleter#findChoices() -> undefined
*
* Triggers an update of the choices presented to the user. If results
* come from the server, this is an asynchronous operation.
**/
findChoices: function() {
var value = this._getInput();
var choices = this.choices || [];
var value = this._getInput(),
choices = this.choices || [],
choiceValue,
choiceAttribute = this.options.choiceAttribute;
var results = choices.inject([], function(memo, choice) {
if (choice.toLowerCase().include(value.toLowerCase())) {
choiceValue = Object.isUndefined(choiceAttribute) ? choice : choice[choiceAttribute]
if (choiceValue.toLowerCase().include(value.toLowerCase())) {
memo.push(choice);
}
return memo;
});
this.setChoices(results);
},
setChoices: function(results) {
this.results = results;
this._updateMenu(results);
},
_updateMenu: function(results) {
var opt = this.options;
this.menu.clear();
// Build a case-insensitive regexp for highlighting the substring match.
var needle = new RegExp(RegExp.escape(this._value), 'i');
for (var i = 0, result, li, text; result = results[i]; i++) {
value = Object.isUndefined(opt.choiceAttribute) ? result : result[opt.choiceAttribute]
text = opt.highlightSubstring ?
result.replace(needle, "<b>$&</b>") :
result;
li = new Element('li').update(text);
value.replace(needle, "<b>$&</b>") :
value;
li = new Element('li').update(Object.isUndefined(opt.listTemplate) ? text : opt.listTemplate.evaluate({text: text, object: result}));

This comment has been minimized.

Show comment
Hide comment
@ZenCocoon

ZenCocoon Sep 3, 2010

Owner

Object.isTemplate would be sweet, shall a patch to Prototype be useful?

@ZenCocoon

ZenCocoon Sep 3, 2010

Owner

Object.isTemplate would be sweet, shall a patch to Prototype be useful?

This comment has been minimized.

Show comment
Hide comment
@savetheclocktower

savetheclocktower Jan 23, 2011

Not sold on Object.isTemplate. Why not just do foo instanceof Template?

@savetheclocktower

savetheclocktower Jan 23, 2011

Not sold on Object.isTemplate. Why not just do foo instanceof Template?

This comment has been minimized.

Show comment
Hide comment
@ZenCocoon

ZenCocoon Jan 24, 2011

Owner

Great point! Much better like that.

@ZenCocoon

ZenCocoon Jan 24, 2011

Owner

Great point! Much better like that.

li.store('ui.autocompleter.value', result);
this.menu.addChoice(li);
}
if (results.length === 0) {
this.menu.close();
} else {
this.menu.open();
}
},
_moveMenuChoice: function(delta) {
var choices = this.list.down('li');
this._selectedIndex = (this._selectedIndex + delta).constrain(
-1, this.results.length - 1);
this._highlightMenuChoice();
},
_highlightMenuChoice: function(element) {
var choices = this.list.select('li'), index;
if (Object.isElement(element)) {
index = choices.indexOf(element);
} else if (Object.isNumber(element)) {
@@ -213,42 +219,42 @@
UI.removeClassNames(choices, 'ui-state-active');
if (index === -1 || index === null) return;
choices[index].addClassName('ui-state-active');
this._selectedIndex = index;
},
_selected: function(event) {
var memo = event.memo, li = memo.element;
if (li) {
var value = li.retrieve('ui.autocompleter.value');
var result = this.element.fire('ui:autocompleter:selected', {
instance: this,
value: value
});
if (result.stopped) return;
this._setInput(value);
this._setInput(Object.isUndefined(this.options.choiceAttribute) ? value : value[this.options.choiceAttribute]);
}
this.menu.close();
},
_blur: function(event) {
this._unschedule();
this.menu.close();
}
});
Object.extend(UI.Autocompleter, {
DEFAULT_OPTIONS: {
tokens: [],
frequency: 0.4,
minCharacters: 1,
highlightSubstring: true,
onShow: Prototype.K,
onHide: Prototype.K
}
});
})(S2.UI);
@@ -142,6 +142,46 @@ <h2>Standard Autocompleter</h2>
<li>The matching substring of a given choice <strong>should</strong> be bold.</li>
</ul>
</div> <!-- .description -->
<h2>Autocompleter using Objects as choices and Template to style list items</h2>
<span id="autocompleter2">
<input type="text" name="firstname" />
</span> <!-- #autocompleter2 -->
<script type="text/javascript">
var choices = [
{ client: { login: "madrobby", name: "Thomas Fuchs"} },
{ client: { login: "savetheclocktower", name: "Andrew Dupont"} },
{ client: { login: "RStankov", name: "Radoslav Stankov"} },
{ client: { login: "smith", name: "Nathan L Smith"} },
{ client: { login: "joe-loco", name: "Philipp Markovics"} },
{ client: { login: "kommen", name: "Dieter Komendera",} },
{ client: { login: "eric1234", name: "Eric Anderson"} },
{ client: { login: "sgruhier", name: "Sébastien Gruhier"} },
{ client: { login: "stepheneb", name: "Stephen Bannasch"} },
{ client: { login: "ZenCocoon", name: "Sébastien Grosjean"} },
{ client: { login: "TomK32", name: "Thomas R. Koll"} },
{ client: { login: "charettes", name: "Simon Charette"} },
{ client: { login: "rumble", name: "Mike Rumble"} }
];
new S2.UI.Autocompleter('autocompleter2', {
choices: choices.collect(function(o) { return o.client }),
choiceAttribute: 'name',
listTemplate: new Template('#{text} <em>(#{object.login})</em>')
});
</script>
<div class="description">
The choices are the scripty2 contributors at test's creation date. It should behave like a standard autocompleter, with a few exceptions:
<ul>
<li>It <strong>should</strong> accept an array of objects as choices</li>
<li>The list of completions <strong>should</strong> offer customized HTML items.</li>
<li>When selected, it <strong>should</strong> set the value with the right object attribute.</li>
<li>When <code>ui:autocompleter:selected</code> is fired, it <strong>should</strong> return the complete object as memo's value.</li>
</ul>
</div> <!-- .description -->
</body>
</html>

2 comments on commit 586102b

@savetheclocktower

This comment has been minimized.

Show comment
Hide comment
@savetheclocktower

savetheclocktower Jan 23, 2011

I like this. Suggestions:

  • Can we call it choiceTemplate instead of listTemplate?
  • How about changing choiceAttribute to choiceValue and have it be a function? The default function could be Prototype.K or else a simple function that calls toString.
choiceValue: function(choice) { return choice.name; }

savetheclocktower replied Jan 23, 2011

I like this. Suggestions:

  • Can we call it choiceTemplate instead of listTemplate?
  • How about changing choiceAttribute to choiceValue and have it be a function? The default function could be Prototype.K or else a simple function that calls toString.
choiceValue: function(choice) { return choice.name; }
@ZenCocoon

This comment has been minimized.

Show comment
Hide comment
@ZenCocoon

ZenCocoon Jan 24, 2011

Owner

Sounds good to me, that makes things cleaner and even more flexible.

Owner

ZenCocoon replied Jan 24, 2011

Sounds good to me, that makes things cleaner and even more flexible.

Please sign in to comment.