Merge lp:~jcsackett/launchpad/cleanup-pickers-with-base-create into lp:launchpad

Proposed by j.c.sackett
Status: Merged
Approved by: j.c.sackett
Approved revision: no longer in the source branch.
Merged at revision: 15471
Proposed branch: lp:~jcsackett/launchpad/cleanup-pickers-with-base-create
Merge into: lp:launchpad
Diff against target: 1332 lines (+354/-498)
5 files modified
lib/lp/app/javascript/picker/person_picker.js (+39/-65)
lib/lp/app/javascript/picker/picker.js (+310/-424)
lib/lp/app/javascript/picker/picker_patcher.js (+3/-3)
lib/lp/app/javascript/picker/tests/test_personpicker.html (+0/-4)
lib/lp/app/javascript/picker/tests/test_personpicker.js (+2/-2)
To merge this branch: bzr merge lp:~jcsackett/launchpad/cleanup-pickers-with-base-create
Reviewer Review Type Date Requested Status
Richard Harding (community) Approve
Review via email: mp+110921@code.launchpad.net

Commit message

Cleans up picker and personpicker code with Y.Base.create.

Description of the change

Summary
=======
The picker javascript code is a mess of competing styles, showing its long
history in the LP code tree.

As part of general tech-debt cleanup and updating our JS to more modern uses
of YUI, we need to start cleaning this up. Using Y.Base.create where possible
is one step in so doing.

Preimp
======
Spoke with Rick Harding about c-style constants and how best to replace them.

Implementation
==============
A bunch of constants, seemingly influenced by c-style coding have been removed
from the base picker.

The base picker, as well as the person picker, have been updated to use
Y.Base.create.

Both have been updated to define their own render/bind/syncUI methods rather
than chaining event calls after the normal cycle--this is in accordance with
standard means of writing yui widgets.

A number of attributes have been created as part of the ATTR block for both,
simplifying the initializers. More of this could probably be done, but the
diff is getting big, and this will not be my last branch on this effort.

Tests
=====
bin/test -vvct picker --layer=YUI

QA
==
Go play with all instances of the picker, esp the person picker. They should
function as they always have.

LoC
===
This removes 100+ LoC.

Lint
====

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/app/javascript/picker/tests/test_personpicker.html
  lib/lp/app/javascript/picker/person_picker.js
  lib/lp/app/javascript/picker/tests/test_personpicker.js
  lib/lp/app/javascript/picker/picker_patcher.js
  lib/lp/app/javascript/picker/picker.js

To post a comment you must log in.
Revision history for this message
Richard Harding (rharding) wrote :

Thank you so much for cleaning this up. I know it's a big chunk, but really think the LoC going down as it gets cleaned up and more readable is a great sign.

I'm going to mark needs fixing just because I want to chat on the event driven methods and their use of the explicit calling of the parent class before it lands. It could just be a case of 'that is the way it has to be' to avoid needing to rework a bunch of other things, but want to check.

#24
Can you test if everything still works without the parent call in the initializer? If I recall correctly, YUI will stack the initializer methods for you properly so that they don't need to be manually called for classes extending other classes.

Example Fiddle: http://jsfiddle.net/NdTHr/1/

#71
Can you not just call: this.hide() vs the full parent method: Y.lazr.picker.Picker.prototype.hide.call(this);
Is this not called from an instance perhaps? So it's meant to be called this way as if it was some sort of static method?

#82
In _update_button_text: how about using an else statement after the if == team check. It avoids an accessor to the remove_person_text.

#95
Do we need the value: null? In the calling code if it's using Y.Lang.isValue() either undefined or null will return false. I guess this requires a quick check on the subscribers to see what they're doing with the value.

#342
The properties should just be outside the initializer method, they'd be on the same line as the initializer itself.

Sample fiddle: http://jsfiddle.net/jSpWL/1/

#457
Any ATTR value that is passed into the initializer(cfg) will automatically get set to that ATTR as the current value. Lines that only check if it's set and if so, run a this.set('someattr', 'somevalue') can just be removed. I think this holds true for the next two attribute setters as well.

Sample fiddle: http://jsfiddle.net/X3ERp/1/

#913
Another case of wondering if you can just do this._defaultCancel(). If this is event based, can we check where the cancel event is fired from and see if it's calling the fire correctly? on the instance for example?

#1197
If the default is null, you can just leave the value bit out.

I notice you doc these ATTRS, but not the ones in the PersonPicker. As I one day dream of running YUIDoc against our code to help generate nice docs for our JS modules, it'd be great if you could add it in. One of the good ones for ATTRS is the @default null which would indicate a default value of null in the docs.

If you're using Vim and snipmate, I've got a series of snippets for doing YUI doc blocks.
http://paste.mitechie.com/show/701/

#1185
You can shortcut this using the setAttrs(). It allows you to change that to something more like

    this.get('host').setAttrs({
        'selected_value_metadata': result.metadata,
        'selected_value', result.value
    });

#1468
Looks like you killed a line on accident there.

review: Needs Fixing
Revision history for this message
j.c.sackett (jcsackett) wrote :
Download full text (3.5 KiB)

> #24
> Can you test if everything still works without the parent call in the
> initializer? If I recall correctly, YUI will stack the initializer methods for
> you properly so that they don't need to be manually called for classes
> extending other classes.

Done.

> #71
> Can you not just call: this.hide() vs the full parent method:
> Y.lazr.picker.Picker.prototype.hide.call(this);
> Is this not called from an instance perhaps? So it's meant to be called this
> way as if it was some sort of static method?

A quick test shows this doesn't work. This is defined in this.hide(), which may be part of the problem, and the object inheritance in this isn't entirely fixed. In truth, I want to not have new hide/show functions and move this extra stuff into a bindUI after('visibleChange') call, but that's happening in a subsequent branch.

> #82
> In _update_button_text: how about using an else statement after the if == team
> check. It avoids an accessor to the remove_person_text.

Done.

> #95
> Do we need the value: null? In the calling code if it's using Y.Lang.isValue()
> either undefined or null will return false. I guess this requires a quick
> check on the subscribers to see what they're doing with the value.

Not all subscribers for this are cleaned up yet--in fact, it's not entirely clear what all the possible subscribers *are*, and I'm not convinced they're adequately tested for me to just change and be comforted by tests passing. I've made a note of this for a follow up branch.

> #342
> The properties should just be outside the initializer method, they'd be on the
> same line as the initializer itself.

Ah, dig. Wasn't sure with the Y.Base.create where those went. Done.

> #457
> Any ATTR value that is passed into the initializer(cfg) will automatically get
> set to that ATTR as the current value. Lines that only check if it's set and
> if so, run a this.set('someattr', 'somevalue') can just be removed. I think
> this holds true for the next two attribute setters as well.

Done.

> #913
> Another case of wondering if you can just do this._defaultCancel(). If this is
> event based, can we check where the cancel event is fired from and see if it's
> calling the fire correctly? on the instance for example?

I've made a note again to look at this in followup, but this falls into the "I'm not yet confident to change here" category, so I've just preserved what's known to work.

> #1197
> If the default is null, you can just leave the value bit out.
>
> I notice you doc these ATTRS, but not the ones in the PersonPicker. As I one
> day dream of running YUIDoc against our code to help generate nice docs for
> our JS modules, it'd be great if you could add it in. One of the good ones for
> ATTRS is the @default null which would indicate a default value of null in the
> docs.
>
> If you're using Vim and snipmate, I've got a series of snippets for doing YUI
> doc blocks.
> http://paste.mitechie.com/show/701/

If you're alright with it, I'll tackle this in another follow up to clean up docs. This branch was slack time and expanded beyond that time allotment, so I'd like to get it landed and handle smaller bits in subsequent slack time.

> #1185
> ...

Read more...

Revision history for this message
Richard Harding (rharding) wrote :

Thanks for taking the time to update. I understand it's not a perfect refactor, but every step along the way is good stuff.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/app/javascript/picker/person_picker.js'
2--- lib/lp/app/javascript/picker/person_picker.js 2011-09-19 00:35:49 +0000
3+++ lib/lp/app/javascript/picker/person_picker.js 2012-06-21 15:03:22 +0000
4@@ -6,73 +6,35 @@
5 */
6 YUI.add('lazr.person-picker', function(Y) {
7
8+var ns = Y.namespace('lazr.picker');
9 /*
10 * Extend the picker into the PersonPicker
11 */
12-var PersonPicker;
13-
14-PersonPicker = function() {
15- PersonPicker.superclass.constructor.apply(this, arguments);
16- this._extra_buttons = Y.Node.create('<div class="extra-form-buttons"/>');
17- Y.after(this._renderPersonPickerUI, this, 'renderUI');
18-};
19-
20-Y.extend(PersonPicker, Y.lazr.picker.Picker, {
21+
22+ns.PersonPicker = Y.Base.create('picker', Y.lazr.picker.Picker, [], {
23+
24 initializer: function(cfg) {
25- PersonPicker.superclass.initializer.apply(this, arguments);
26-
27- var show_assign_me_button = true;
28- var show_remove_button = true;
29- var assign_me_text = 'Pick me';
30- var remove_person_text = 'Remove person';
31- var remove_team_text = 'Remove team';
32- var min_search_chars = 2;
33- if (cfg !== undefined) {
34- if (cfg.show_assign_me_button !== undefined) {
35- show_assign_me_button = cfg.show_assign_me_button;
36- }
37- if (cfg.show_remove_button !== undefined) {
38- show_remove_button = cfg.show_remove_button;
39- }
40- if (cfg.assign_me_text !== undefined) {
41- assign_me_text = cfg.assign_me_text;
42- }
43- if (cfg.remove_person_text !== undefined) {
44- remove_person_text = cfg.remove_person_text;
45- }
46- if (cfg.remove_team_text !== undefined) {
47- remove_team_text = cfg.remove_team_text;
48- }
49- if (cfg.min_search_chars !== undefined) {
50- min_search_chars = cfg.min_search_chars;
51- }
52+ // If the user isn't logged in, override the show_assign_me value.
53+ if (!Y.Lang.isValue(LP.links.me)) {
54+ this.set('show_assign_me_button', false);
55 }
56- this.set('min_search_chars', min_search_chars);
57-
58- /* Only show assign-me when the user is logged-in. */
59- show_assign_me_button = (
60- show_assign_me_button && Y.Lang.isValue(LP.links.me));
61- this._show_assign_me_button = show_assign_me_button;
62- this._show_remove_button = show_remove_button;
63- this._assign_me_text = assign_me_text;
64- this._remove_person_text = remove_person_text;
65- this._remove_team_text = remove_team_text;
66 },
67
68 hide: function() {
69 this.get('boundingBox').setStyle('display', 'none');
70- this.constructor.superclass.hide.call(this);
71+ Y.lazr.picker.Picker.prototype.hide.call(this);
72 },
73
74 show: function() {
75 this.get('boundingBox').setStyle('display', 'block');
76- this.constructor.superclass.show.call(this);
77+ Y.lazr.picker.Picker.prototype.show.call(this);
78 },
79
80 _update_button_text: function() {
81- var link_text = this._remove_person_text;
82 if (this.get('selected_value_metadata') === 'team') {
83- link_text = this._remove_team_text;
84+ var link_text = this.get('remove_team_text');
85+ } else {
86+ var link_text = this.get('remove_person_text');
87 }
88 this.remove_button.set('text', link_text);
89 },
90@@ -100,12 +62,12 @@
91
92 remove: function () {
93 this.hide();
94- this.fire(Y.lazr.picker.Picker.SAVE, {value: null});
95+ this.fire('save', {value: null});
96 },
97
98 assign_me: function () {
99 var name = LP.links.me.replace('/~', '');
100- this.fire(Y.lazr.picker.Picker.SAVE, {
101+ this.fire('save', {
102 image: '/@@/person',
103 title: 'Me',
104 api_uri: LP.links.me,
105@@ -113,40 +75,52 @@
106 });
107 },
108
109- _renderPersonPickerUI: function() {
110+ renderUI: function() {
111+ Y.lazr.picker.Picker.prototype.renderUI.apply(this, arguments);
112+ var extra_buttons = this.get('extra_buttons');
113 var remove_button, assign_me_button;
114
115- if (this._show_remove_button) {
116+ if (this.get('show_remove_button')) {
117 remove_button = Y.Node.create(
118 '<a class="yui-picker-remove-button bg-image" ' +
119 'href="javascript:void(0)" ' +
120 'style="background-image: url(/@@/remove); padding-right: ' +
121- '1em">' + this._remove_person_text + '</a>');
122+ '1em">' + this.get('remove_person_text') + '</a>');
123 remove_button.on('click', this.remove, this);
124- this._extra_buttons.appendChild(remove_button);
125+ extra_buttons.appendChild(remove_button);
126 this.remove_button = remove_button;
127 }
128
129- if (this._show_assign_me_button) {
130+ if (this.get('show_assign_me_button')) {
131 assign_me_button = Y.Node.create(
132 '<a class="yui-picker-assign-me-button bg-image" ' +
133 'href="javascript:void(0)" ' +
134 'style="background-image: url(/@@/person)">' +
135- this._assign_me_text + '</a>');
136+ this.get('assign_me_text') + '</a>');
137 assign_me_button.on('click', this.assign_me, this);
138- this._extra_buttons.appendChild(assign_me_button);
139+ extra_buttons.appendChild(assign_me_button);
140 this.assign_me_button = assign_me_button;
141 }
142 this._search_input.insert(
143- this._extra_buttons, this._search_input.get('parentNode'));
144+ extra_buttons, this._search_input.get('parentNode'));
145 this._show_hide_buttons();
146 this.after("selected_valueChange", function(e) {
147 this._show_hide_buttons();
148 });
149 }
150+}, {
151+ ATTRS: {
152+ extra_buttons: {
153+ valueFn: function () {
154+ return Y.Node.create('<div class="extra-form-buttons"/>')
155+ }
156+ },
157+ show_assign_me_button: { value: true },
158+ show_remove_button: {value: true },
159+ assign_me_text: {value: 'Pick me'},
160+ remove_person_text: {value: 'Remove person'},
161+ remove_team_text: {value: 'Remove team'},
162+ min_search_chars: {value: 2}
163+ }
164 });
165-PersonPicker.NAME = 'picker';
166-var namespace = Y.namespace('lazr.picker');
167-namespace.PersonPicker = PersonPicker;
168-
169-}, "0.1", {"requires": ["lazr.picker"]});
170+}, "0.1", {"requires": ["base", "node", "lazr.picker"]});
171
172=== modified file 'lib/lp/app/javascript/picker/picker.js'
173--- lib/lp/app/javascript/picker/picker.js 2012-03-22 22:38:09 +0000
174+++ lib/lp/app/javascript/picker/picker.js 2012-06-21 15:03:22 +0000
175@@ -1,6 +1,7 @@
176 /* Copyright (c) 2009, Canonical Ltd. All rights reserved. */
177
178 YUI.add('lazr.picker', function(Y) {
179+var ns = Y.namespace('lazr.picker');
180
181 /**
182 * Module containing the Lazr searchable picker.
183@@ -9,78 +10,20 @@
184 * @namespace lazr.picker
185 */
186
187+
188+// Alias getClassName to minimize line wrapping gymnastics later.
189+var getCN = Y.ClassNameManager.getClassName;
190+
191 /**
192 * A picker is a pop-up widget containing a search field and displaying a list
193 * of found results.
194- *
195- * @class Picker
196- * @extends lazr.PrettyOverlay
197- * @constructor
198 */
199-
200-var PICKER = 'picker',
201- BOUNDING_BOX = 'boundingBox',
202- CONTENT_BOX = 'contentBox',
203-
204- // Local aliases
205- getCN = Y.ClassNameManager.getClassName,
206-
207- // CSS Classes
208- C_SEARCH = getCN(PICKER, 'search'),
209- C_SEARCH_BOX = getCN(PICKER, 'search-box'),
210- C_SEARCH_SLOT = getCN(PICKER, 'search-slot'),
211- C_FOOTER_SLOT = getCN(PICKER, 'footer-slot'),
212- C_SEARCH_MODE = getCN(PICKER, 'search-mode'),
213- C_FILTER = getCN(PICKER, 'filter'),
214- C_RESULTS = getCN(PICKER, 'results'),
215- C_RESULT_TITLE = getCN(PICKER, 'result-title'),
216- C_RESULT_DESCRIPTION = getCN(PICKER, 'result-description'),
217- C_ERROR = getCN(PICKER, 'error'),
218- C_ERROR_MODE = getCN(PICKER, 'error-mode'),
219- C_NO_RESULTS = getCN(PICKER, 'no-results'),
220- C_BATCHES = getCN(PICKER, 'batches'),
221- C_SELECTED_BATCH = getCN(PICKER, 'selected-batch'),
222-
223- // Events
224- SAVE = 'save',
225- SEARCH = 'search',
226- VALIDATE = 'validate',
227-
228- // Property constants
229- MIN_SEARCH_CHARS = 'min_search_chars',
230- CURRENT_SEARCH_STRING = 'current_search_string',
231- ERROR = 'error',
232- RESULTS = 'results',
233- BATCHES = 'batches',
234- BATCH_COUNT = 'batch_count',
235- SEARCH_SLOT = 'search_slot',
236- FOOTER_SLOT = 'footer_slot',
237- SELECTED_BATCH = 'selected_batch',
238- SEARCH_MODE = 'search_mode',
239- NO_RESULTS_SEARCH_MESSAGE = 'no_results_search_message',
240- RENDERUI = "renderUI",
241- BINDUI = "bindUI",
242- ASSOCIATED_FIELD_ID = 'associated_field_id',
243- SELECTED_VALUE = 'selected_value',
244- SELECTED_VALUE_METADATA = 'selected_value_metadata',
245- FILTER_OPTIONS = 'filter_options',
246- CURRENT_FILTER_VALUE = 'current_filter_value';
247-
248-
249-var Picker;
250-Picker = function () {
251- Picker.superclass.constructor.apply(this, arguments);
252-
253- Y.after(this._renderUIPicker, this, RENDERUI);
254- Y.after(this._bindUIPicker, this, BINDUI);
255- Y.after(this._syncUIPicker, this, BINDUI);
256-};
257-
258-Y.extend(Picker, Y.lazr.PrettyOverlay, {
259- /**
260- * The search input box node.
261+ns.Picker = Y.Base.create('picker', Y.lazr.PrettyOverlay, [], {
262+
263+ /**
264+ * The search input node.
265 *
266- * @property _search_input
267+ * @property _search_button
268 * @type Node
269 * @private
270 */
271@@ -129,7 +72,7 @@
272 * @type Node
273 * @private
274 */
275- _batches_box: null,
276+ _batches_box: null,
277
278 /**
279 * The node containing the previous batch button.
280@@ -173,7 +116,7 @@
281 * @event search
282 * @preventable _defaultSearch
283 */
284- this.publish(SEARCH, { defaultFn: this._defaultSearch });
285+ this.publish('search', { defaultFn: this._defaultSearch });
286
287 /**
288 * Fires when the user selects one of the result. The event details
289@@ -182,7 +125,7 @@
290 * @event validate
291 * @preventable _defaultValidate
292 */
293- this.publish(VALIDATE, { defaultFn: this._defaultValidate } );
294+ this.publish('validate', { defaultFn: this._defaultValidate } );
295
296 /**
297 * Fires on successful validation of the selected result.
298@@ -192,7 +135,7 @@
299 * @event save
300 * @preventable _defaultSave
301 */
302- this.publish(SAVE, { defaultFn: this._defaultSave } );
303+ this.publish('save', { defaultFn: this._defaultSave } );
304
305
306 // Subscribe to the cancel event so that we can clear the widget when
307@@ -210,27 +153,10 @@
308
309 if (!Y.Lang.isUndefined(cfg)) {
310 // The picker's associated field.
311- if (Y.Lang.isValue(cfg[ASSOCIATED_FIELD_ID])) {
312+ if (Y.Lang.isValue(cfg.associated_field_id)) {
313 this.plug(TextFieldPickerPlugin,
314 {input_element:
315- '[id="'+cfg[ASSOCIATED_FIELD_ID]+'"]'});
316- }
317- // Meta information associated with the picker's associated
318- // field's current value.
319- if (Y.Lang.isValue(cfg[SELECTED_VALUE_METADATA])) {
320- this.set(
321- SELECTED_VALUE_METADATA, cfg[SELECTED_VALUE_METADATA]);
322- }
323- // The value of the picker's associated field.
324- if (Y.Lang.isValue(cfg[SELECTED_VALUE])) {
325- this.set(SELECTED_VALUE, cfg[SELECTED_VALUE]);
326- }
327- // Optional filter support
328- if (Y.Lang.isValue(cfg[FILTER_OPTIONS])) {
329- this.set(FILTER_OPTIONS, cfg[FILTER_OPTIONS]);
330- if (Y.Lang.isValue(cfg[CURRENT_FILTER_VALUE])) {
331- this.set(CURRENT_FILTER_VALUE, cfg[CURRENT_FILTER_VALUE]);
332- }
333+ '[id="'+cfg.associated_field_id+'"]'});
334 }
335 }
336 },
337@@ -242,7 +168,7 @@
338 * @protected
339 */
340 _syncSearchSlotUI: function() {
341- var search_slot = this.get(SEARCH_SLOT);
342+ var search_slot = this.get('search_slot');
343
344 // Clear previous slot contents.
345 this._search_slot_box.set('innerHTML', '');
346@@ -259,7 +185,7 @@
347 * @protected
348 */
349 _syncFooterSlotUI: function() {
350- var footer_slot = this.get(FOOTER_SLOT);
351+ var footer_slot = this.get('footer_slot');
352
353 // Clear previous slot contents.
354 this._footer_slot_box.set('innerHTML', '');
355@@ -276,10 +202,10 @@
356 * @protected
357 */
358 _getBatches: function() {
359- var batches = this.get(BATCHES);
360+ var batches = this.get('batches');
361
362 if (batches === null) {
363- var batch_count = this.get(BATCH_COUNT);
364+ var batch_count = this.get('batch_count');
365 if (batch_count === null) {
366 batches = [];
367 }
368@@ -320,10 +246,10 @@
369 // in _syncSelectedBatchUI.
370 this._prev_button = Y.Node.create(Y.lazr.ui.PREVIOUS_BUTTON);
371 this._prev_button.on('click', function (e) {
372- var selected = this.get(SELECTED_BATCH) - 1;
373- this.set(SELECTED_BATCH, selected);
374+ var selected = this.get('selected_batch') - 1;
375+ this.set('selected_batch', selected);
376 this.fire(
377- SEARCH, this.get(CURRENT_SEARCH_STRING),
378+ 'search', this.get('current_search_string'),
379 batches[selected].value);
380 }, this);
381 this._batches_box.appendChild(this._prev_button);
382@@ -335,19 +261,19 @@
383 this._batches_box.appendChild(batch_item);
384
385 batch_item.on('click', function (e) {
386- this.set(SELECTED_BATCH, i);
387+ this.set('selected_batch', i);
388 this.fire(
389- SEARCH, this.get(CURRENT_SEARCH_STRING), data.value);
390+ 'search', this.get('current_search_string'), data.value);
391 }, this);
392 }, this);
393
394 this._next_button = Y.Node.create(Y.lazr.ui.NEXT_BUTTON);
395 this._batches_box.appendChild(this._next_button);
396 this._next_button.on('click', function (e) {
397- var selected = this.get(SELECTED_BATCH) + 1;
398- this.set(SELECTED_BATCH, selected);
399+ var selected = this.get('selected_batch') + 1;
400+ this.set('selected_batch', selected);
401 this.fire(
402- SEARCH, this.get(CURRENT_SEARCH_STRING),
403+ 'search', this.get('current_search_string'),
404 batches[selected].value);
405 }, this);
406 },
407@@ -359,12 +285,13 @@
408 * @protected
409 */
410 _syncSelectedBatchUI: function() {
411- var idx = this.get(SELECTED_BATCH);
412+ var idx = this.get('selected_batch');
413 var items = this._batches_box.all('span');
414 if (items.size()) {
415+ var selected_batch_class = getCN('picker', 'selected-batch');
416 this._prev_button.set('disabled', idx === 0);
417- items.removeClass(C_SELECTED_BATCH);
418- items.item(idx).addClass(C_SELECTED_BATCH);
419+ items.removeClass(selected_batch_class);
420+ items.item(idx).addClass(selected_batch_class);
421 this._next_button.set('disabled', idx+1 === items.size());
422 }
423 },
424@@ -403,7 +330,7 @@
425 */
426 _renderTitleUI: function(data) {
427 var li_title = Y.Node.create('<a href="#"></a>')
428- .addClass(C_RESULT_TITLE)
429+ .addClass(getCN('picker', 'result-title'))
430 .addClass('js-action');
431 li_title.on('click', function (e, value) {
432 e.preventDefault();
433@@ -486,7 +413,8 @@
434 */
435 _renderDescriptionUI: function(data) {
436 var li_desc = Y.Node.create(
437- '<div><br /></div>').addClass(C_RESULT_DESCRIPTION);
438+ '<div><br /></div>').addClass(
439+ getCN('picker', 'result-description'));
440 if (data.description) {
441 li_desc.replaceChild(
442 document.createTextNode(data.description),
443@@ -505,7 +433,7 @@
444 }
445 var details_node = Y.Node.create('<div></div>')
446 .addClass('sprite')
447- .addClass(C_RESULT_DESCRIPTION);
448+ .addClass(getCN('picker', 'result-description'));
449 if (Y.Lang.isArray(data.details)) {
450 var data_node = Y.Node.create('<div></div>');
451 var escaped_details = [];
452@@ -542,7 +470,7 @@
453 .set('text', 'Select ' + data.title));
454 links[0].on('click', function (e, value) {
455 e.preventDefault();
456- this.fire(VALIDATE, value);
457+ this.fire('validate', value);
458 }, this, data);
459 links.push(this._text_or_link(
460 'View details', data.alt_title_link, data.link_css));
461@@ -564,14 +492,14 @@
462 * @protected
463 */
464 _syncResultsUI: function() {
465- var results = this.get(RESULTS);
466+ var results = this.get('results');
467
468 // Remove any previous results.
469 Y.Event.purgeElement(this._results_box, true);
470 this._results_box.set('innerHTML', '');
471 this._filter_box.set('innerHTML', '');
472
473- var expander_id = this.get(BOUNDING_BOX).get('id');
474+ var expander_id = this.get('boundingBox').get('id');
475 Y.Array.each(results, function(data, i) {
476 // Sort out the badges div.
477 var li_badges = this._renderTitleBadgesUI(data);
478@@ -606,12 +534,12 @@
479 li_desc, li_details, {group_id: expander_id});
480 li.expander.setUp(true);
481 li_title.on('click', function (e, value) {
482- this.fire(VALIDATE, value);
483+ this.fire('validate', value);
484 }, this, data);
485 } else {
486 // Attach implicit valdate/save handler.
487 li.on('click', function (e, value) {
488- this.fire(VALIDATE, value);
489+ this.fire('validate', value);
490 }, this, data);
491 }
492
493@@ -620,23 +548,24 @@
494
495 // If the user has entered a search and there ain't no results,
496 // display the message about no items matching.
497+ var no_results_class = getCN('picker', 'no-results');
498 if (this._search_input.get('value') && !results.length) {
499 var msg = Y.Node.create('<li></li>');
500 msg.appendChild(
501 document.createTextNode(
502- Y.substitute(this.get(NO_RESULTS_SEARCH_MESSAGE),
503+ Y.substitute(this.get('no_results_search_message'),
504 {query: this._search_input.get('value')})));
505 this._results_box.appendChild(msg);
506- this._results_box.addClass(C_NO_RESULTS);
507+ this._results_box.addClass(no_results_class);
508 this._syncFilterUI();
509 } else {
510- this._results_box.removeClass(C_NO_RESULTS);
511+ this._results_box.removeClass(no_results_class);
512 if (results.length) {
513- var filters = this.get(FILTER_OPTIONS);
514- var current_filter_value = this.get(CURRENT_FILTER_VALUE);
515+ var filters = this.get('filter_options');
516+ var current_filter_value = this.get('current_filter_value');
517 if (filters.length > 0 &&
518 !Y.Lang.isValue(current_filter_value)) {
519- this.set(CURRENT_FILTER_VALUE, filters[0].title);
520+ this.set('current_filter_value', filters[0].title);
521 }
522 this._syncFilterUI();
523 }
524@@ -650,7 +579,7 @@
525 * @protected
526 */
527 _syncProgressUI: function() {
528- var results = this.get(RESULTS);
529+ var results = this.get('results');
530 if (results.length) {
531 // Set PrettyOverlay's green progress bar to 100%.
532 this.set('progress', 100);
533@@ -666,11 +595,11 @@
534 */
535 _syncFilterUI: function() {
536 // Check that we need to display the filter UI.
537- var filters = this.get(FILTER_OPTIONS);
538+ var filters = this.get('filter_options');
539 if( filters.length === 0 ) {
540 return;
541 }
542- var current_filter_value = this.get(CURRENT_FILTER_VALUE);
543+ var current_filter_value = this.get('current_filter_value');
544 if (!Y.Lang.isValue(current_filter_value)) {
545 return;
546 }
547@@ -696,7 +625,7 @@
548 // event.
549 filter_link.on('click', function (e) {
550 e.halt();
551- picker.set(CURRENT_FILTER_VALUE, filter.title);
552+ picker.set('current_filter_value', filter.title);
553 var search_string = Y.Lang.trim(
554 picker._search_input.get('value'));
555 picker._performSearch(search_string, filter.name);
556@@ -719,13 +648,14 @@
557 * @protected
558 */
559 _syncSearchModeUI: function() {
560- var search_mode = this.get(SEARCH_MODE);
561+ var search_mode = this.get('search_mode');
562 this._search_input.set('disabled', search_mode);
563 this._search_button.set('disabled', search_mode);
564+ var search_class = getCN('picker', 'search-mode');
565 if (search_mode) {
566- this.get(BOUNDING_BOX).addClass(C_SEARCH_MODE);
567+ this.get('boundingBox').addClass(search_class);
568 } else {
569- this.get(BOUNDING_BOX).removeClass(C_SEARCH_MODE);
570+ this.get('boundingBox').removeClass(search_class);
571 // If the search input isn't blurred before it is focused,
572 // then the I-beam disappears.
573 this._search_input.blur();
574@@ -740,27 +670,23 @@
575 * @protected
576 */
577 _syncErrorUI: function() {
578- var error = this.get(ERROR);
579+ var error = this.get('error');
580 this._error_box.set('innerHTML', '');
581+ var error_class = getCN('picker', 'error-mode');
582 if (error === null) {
583- this.get(BOUNDING_BOX).removeClass(C_ERROR_MODE);
584+ this.get('boundingBox').removeClass(error_class);
585 } else {
586 this._error_box.appendChild(document.createTextNode(error));
587- this.get(BOUNDING_BOX).addClass(C_ERROR_MODE);
588+ this.get('boundingBox').addClass(error_class);
589 }
590 },
591
592 /**
593 * Create the widget's HTML components.
594- * <p>
595- * This method is invoked after renderUI is invoked for the Widget class
596- * using YUI's aop infrastructure.
597- * </p>
598 *
599- * @method _renderUIPicker
600- * @protected
601+ * @method renderUI
602 */
603- _renderUIPicker: function() {
604+ renderUI: function() {
605 this._search_button = Y.Node.create(Y.lazr.ui.SEARCH_BUTTON);
606
607 var search_box = Y.Node.create([
608@@ -770,31 +696,31 @@
609 '<div></div></div>'].join(""));
610
611 this._search_input = search_box.one('input');
612- this._search_input.addClass(C_SEARCH);
613+ this._search_input.addClass(getCN('picker', 'search'));
614
615 this._error_box = search_box.one('div');
616- this._error_box.addClass(C_ERROR);
617+ this._error_box.addClass(getCN('picker', 'error'));
618
619 // The search button is floated right to avoid problems with
620 // the input width in Safari 3.
621 search_box.insertBefore(this._search_button, this._search_input);
622- search_box.addClass(C_SEARCH_BOX);
623+ search_box.addClass(getCN('picker', 'search-box'));
624
625 this._search_slot_box = Y.Node.create('<div></div>');
626- this._search_slot_box.addClass(C_SEARCH_SLOT);
627+ this._search_slot_box.addClass(getCN('picker', 'search-slot'));
628 search_box.appendChild(this._search_slot_box);
629
630 this._filter_box = Y.Node.create('<div></div>');
631- this._filter_box.addClass(C_FILTER);
632+ this._filter_box.addClass(getCN('picker', 'filter'));
633
634 this._results_box = Y.Node.create('<ul></ul>');
635- this._results_box.addClass(C_RESULTS);
636+ this._results_box.addClass(getCN('picker', 'results'));
637
638 this._batches_box = Y.Node.create('<div></div>');
639- this._batches_box.addClass(C_BATCHES);
640+ this._batches_box.addClass(getCN('picker', 'batches'));
641
642 this._footer_slot_box = Y.Node.create('<div></div>');
643- this._footer_slot_box.addClass(C_FOOTER_SLOT);
644+ this._footer_slot_box.addClass(getCN('picker', 'footer-slot'));
645
646 var body = Y.Node.create('<div></div>');
647 body.appendChild(search_box);
648@@ -810,15 +736,10 @@
649
650 /**
651 * Bind the widget's DOM elements to their event handlers.
652- * <p>
653- * This method is invoked after bindUI is invoked for the Widget class
654- * using YUI's aop infrastructure.
655- * </p>
656 *
657- * @method _bindUIPicker
658- * @protected
659+ * @method bindUI
660 */
661- _bindUIPicker: function() {
662+ bindUI: function() {
663 Y.on('click', this._defaultSearchUserAction, this._search_button,
664 this);
665
666@@ -845,7 +766,7 @@
667 this.after('resultsChange', function (e) {
668 this._syncResultsUI();
669 this._syncProgressUI();
670- this.set(SEARCH_MODE, false);
671+ this.set('search_mode', false);
672 }, this);
673
674 // Update the search slot box whenever the "search_slot" property
675@@ -888,15 +809,10 @@
676
677 /**
678 * Synchronize the search box, error message and results with the UI.
679- * <p>
680- * This method is invoked after syncUI is invoked for the Widget class
681- * using YUI's aop infrastructure.
682- * </p>
683 *
684- * @method _syncUIPicker
685- * @protected
686+ * @method syncUI
687 */
688- _syncUIPicker: function() {
689+ syncUI: function() {
690 this._syncResultsUI();
691 this._syncProgressUI();
692 this._syncSearchModeUI();
693@@ -914,13 +830,13 @@
694 * @protected
695 */
696 _clear: function() {
697- this.set(CURRENT_SEARCH_STRING, '');
698- this.set(ERROR, '');
699- this.set(RESULTS, []);
700- this.set(BATCHES, null);
701- this.set(BATCH_COUNT, null);
702- this.set(SELECTED_BATCH, 0);
703- this.set(CURRENT_FILTER_VALUE, null);
704+ this.set('current_search_string', '');
705+ this.set('error', '');
706+ this.set('results', []);
707+ this.set('batches', null);
708+ this.set('batch_count', null);
709+ this.set('selected_batch', 0);
710+ this.set('current_filter_value', null);
711 this._search_input.set('value', '');
712 this._results_box.set('innerHTML', '');
713 this._filter_box.set('innerHTML', '');
714@@ -936,7 +852,7 @@
715 */
716 _defaultSearchUserAction: function(e) {
717 e.preventDefault();
718- this.set(CURRENT_FILTER_VALUE, null);
719+ this.set('current_filter_value', null);
720 var search_string = Y.Lang.trim(this._search_input.get('value'));
721 this._performSearch(search_string);
722 },
723@@ -949,19 +865,19 @@
724 * @param filter_name The name of a filter to use to limit the results.
725 */
726 _performSearch: function(search_string, filter_name) {
727- if (search_string.length < this.get(MIN_SEARCH_CHARS)) {
728+ if (search_string.length < this.get('min_search_chars')) {
729 var msg = Y.substitute(
730 "Please enter at least {min} characters.",
731- {min: this.get(MIN_SEARCH_CHARS)});
732- this.set(ERROR, msg);
733+ {min: this.get('min_search_chars')});
734+ this.set('error', msg);
735 } else {
736 // Reset the selected batch for new searches.
737- var current_search_string = this.get(CURRENT_SEARCH_STRING);
738+ var current_search_string = this.get('current_search_string');
739 if (current_search_string !== search_string) {
740- this.set(SELECTED_BATCH, 0);
741+ this.set('selected_batch', 0);
742 }
743- this.set(CURRENT_SEARCH_STRING, search_string);
744- this.fire(SEARCH, search_string, undefined, undefined,
745+ this.set('current_search_string', search_string);
746+ this.fire('search', search_string, undefined, undefined,
747 filter_name);
748 }
749 },
750@@ -975,8 +891,8 @@
751 * @protected
752 */
753 _defaultSearch: function(e) {
754- this.set(ERROR, null);
755- this.set(SEARCH_MODE, true);
756+ this.set('error', null);
757+ this.set('search_mode', true);
758 },
759
760 /**
761@@ -988,8 +904,8 @@
762 * @protected
763 */
764 _defaultCancel : function(e) {
765- Picker.superclass._defaultCancel.apply(this, arguments);
766- this.set(SEARCH_MODE, false);
767+ Y.lazr.PrettyOverlay.prototype._defaultCancel.apply(this, arguments);
768+ this.set('search_mode', false);
769 if ( this.get('clear_on_cancel') ) {
770 this._clear();
771 }
772@@ -1020,7 +936,7 @@
773 * @protected
774 */
775 _defaultValidate : function(e) {
776- this.fire(SAVE, e);
777+ this.fire('save', e);
778 },
779
780 /**
781@@ -1031,243 +947,213 @@
782 * @protected
783 */
784 _defaultSelectBatch: function(e) {
785- this.set(SEARCH_MODE, true);
786- }
787- });
788-
789-/**
790- * Some constants.
791- */
792-Picker.NAME = PICKER;
793-Picker.SAVE = SAVE;
794-Picker.SEARCH = SEARCH;
795-Picker.VALIDATE = VALIDATE;
796-
797-/**
798- * The details index of the save result.
799- *
800- * @static
801- * @property SAVE_RESULT
802- */
803-Picker.SAVE_RESULT = 0;
804-
805-/**
806- * The details index of the search string.
807- *
808- * @static
809- * @property SEARCH_STRING
810- */
811-Picker.SEARCH_STRING = 0;
812-
813-/**
814- * The details index of the selected batch value.
815- *
816- * @static
817- * @property SELECTED_BATCH_VALUE
818- */
819-Picker.SELECTED_BATCH_VALUE = 1;
820-
821-
822-Picker.ATTRS = {
823- /**
824- * Whether or not the search box and result list should be cleared when
825- * the save event is fired.
826- *
827- * @attribute clear_on_save
828- * @type Boolean
829- */
830- clear_on_save: { value: true },
831-
832- /**
833- * Whether or not the search box and result list should be cleared when
834- * the cancel event is fired.
835- *
836- * @attribute clear_on_cancel
837- * @type Boolean
838- */
839- clear_on_cancel: { value: false },
840-
841- /**
842- * A CSS selector for the DOM element that will activate (show) the picker
843- * once clicked.
844- *
845- * @attribute picker_activator
846- * @type String
847- */
848- picker_activator: { value: null },
849-
850- /**
851- * An extra CSS class to be added to the picker_activator, generally used
852- * to distinguish regular links from js-triggering ones.
853- *
854- * @attribute picker_activator_css_class
855- * @type String
856- */
857- picker_activator_css_class: { value: 'js-action' },
858-
859- /**
860- * Minimum number of characters that need to be entered in the search
861- * string input before a search event will be fired. The search string
862- * will be trimmed before testing the length.
863- *
864- * @attribute min_search_chars
865- * @type Integer
866- */
867- min_search_chars: { value: 3 },
868-
869- /**
870- * The current search string, which is needed when clicking on a different
871- * batch if the search input has been modified.
872- *
873- * @attribute current_search_string
874- * @type String
875- */
876- current_search_string: {value: ''},
877-
878- /**
879- * The string representation of the current filter.
880- *
881- * @attribute current_filter_value
882- * @type String
883- */
884- current_filter_value: {value: null},
885-
886- /**
887- * A list of attribute name values used to construct the filtering options
888- * for this picker..
889- *
890- * @attribute filter_options
891- * @type Object
892- */
893- filter_options: {value: []},
894-
895- /**
896- * The string representation of the value selected by using this picker.
897- *
898- * @attribute selected_value
899- * @type String
900- */
901- selected_value: {value: null},
902-
903- /**
904- * Meta information about the current state of the associated field,
905- * whose value is selected by using this picker.
906- *
907- * @attribute selected_value_metadata
908- * @type String
909- */
910- selected_value_metadata: {value: null},
911-
912- /**
913- * Results currently displayed by the widget. Updating this value
914- * automatically updates the display.
915- *
916- * @attribute results
917- * @type Array
918- */
919- results: { value: [] },
920-
921- /**
922- * This adds any form fields you want below the search field.
923- * Updating this value automatically updates the display, but only
924- * if the widget has already been rendered. Otherwise, the change
925- * event never fires.
926- *
927- * @attribute search_slot
928- * @type Node
929- */
930- search_slot: {value: null},
931-
932- /**
933- * A place for custom html at the bottom of the widget. When there
934- * are no search results the search_slot and the footer_slot are
935- * right next to each other.
936- * Updating this value automatically updates the display, but only
937- * if the widget has already been rendered. Otherwise, the change
938- * event never fires.
939- *
940- * @attribute footer_slot
941- * @type Node
942- */
943- footer_slot: {value: null},
944-
945- /**
946- * Batches currently displayed in the widget, which can be
947- * clicked to change the batch of results being displayed. Updating
948- * this value automatically updates the display.
949- *
950- * This an array of object containing the two keys, name (used as
951- * the batch label) and value (used as additional details to SEARCH
952- * event).
953- *
954- * @attribute batches
955- * @type Array
956- */
957- batches: {value: null},
958-
959- /**
960- * For simplified batch creation, you can set this to the number of
961- * batches in the search results. In this case, the batch labels
962- * and values are automatically calculated. The batch name (used as the
963- * batch label) will be the batch number starting from 1. The batch value
964- * (used as additional details to the SEARCH event) will be the batch
965- * number, starting from zero.
966- *
967- * If 'batches' is set (see above), batch_count is ignored.
968- *
969- * @attribute batch_count
970- * @type Integer
971- */
972- batch_count: {value: null},
973-
974- /**
975- * Batch currently selected.
976- *
977- * @attribute selected_batch
978- * @type Integer
979- */
980- selected_batch: {
981- value: 0,
982- getter: function (value) {
983- return value || 0;
984- },
985- validator: function (value) {
986- var batches = this._getBatches();
987- return Y.Lang.isNumber(value) &&
988- value >= 0 &&
989- value < batches.length;
990- }},
991-
992- /**
993- * Flag indicating if the widget is currently in search mode (so users
994- * has triggered a search and we are waiting for results.)
995- *
996- * @attribute search_mode
997- * @type Boolean
998- */
999- search_mode: { value: false },
1000-
1001- /**
1002- * The current error message. This puts the widget in 'error-mode',
1003- * setting this value to null clears that state.
1004- *
1005- * @attribute error
1006- * @type String
1007- */
1008- error: { value: null },
1009-
1010- /**
1011- * The message to display when the search returned no results. This string
1012- * can contain a 'query' placeholder
1013- *
1014- * @attribute no_results_search_message
1015- * @type String
1016- * @default No items matched "{query}".
1017- */
1018- no_results_search_message: {
1019- value: 'No items matched "{query}".'
1020- }
1021-};
1022+ this.set('search_mode', true);
1023+ }
1024+}, {
1025+ ATTRS: {
1026+ /**
1027+ * Whether or not the search box and result list should be cleared when
1028+ * the save event is fired.
1029+ *
1030+ * @attribute clear_on_save
1031+ * @type Boolean
1032+ */
1033+ clear_on_save: { value: true },
1034+
1035+ /**
1036+ * Whether or not the search box and result list should be cleared when
1037+ * the cancel event is fired.
1038+ *
1039+ * @attribute clear_on_cancel
1040+ * @type Boolean
1041+ */
1042+ clear_on_cancel: { value: false },
1043+
1044+ /**
1045+ * A CSS selector for the DOM element that will activate (show) the picker
1046+ * once clicked.
1047+ *
1048+ * @attribute picker_activator
1049+ * @type String
1050+ */
1051+ picker_activator: {},
1052+
1053+ /**
1054+ * An extra CSS class to be added to the picker_activator, generally used
1055+ * to distinguish regular links from js-triggering ones.
1056+ *
1057+ * @attribute picker_activator_css_class
1058+ * @type String
1059+ */
1060+ picker_activator_css_class: { value: 'js-action' },
1061+
1062+ /**
1063+ * Minimum number of characters that need to be entered in the search
1064+ * string input before a search event will be fired. The search string
1065+ * will be trimmed before testing the length.
1066+ *
1067+ * @attribute min_search_chars
1068+ * @type Integer
1069+ */
1070+ min_search_chars: { value: 3 },
1071+
1072+ /**
1073+ * The current search string, which is needed when clicking on a different
1074+ * batch if the search input has been modified.
1075+ *
1076+ * @attribute current_search_string
1077+ * @type String
1078+ */
1079+ current_search_string: {value: ''},
1080+
1081+ /**
1082+ * The string representation of the current filter.
1083+ *
1084+ * @attribute current_filter_value
1085+ * @type String
1086+ */
1087+ current_filter_value: {value: null},
1088+
1089+ /**
1090+ * A list of attribute name values used to construct the filtering options
1091+ * for this picker..
1092+ *
1093+ * @attribute filter_options
1094+ * @type Object
1095+ */
1096+ filter_options: {value: []},
1097+
1098+ /**
1099+ * The string representation of the value selected by using this picker.
1100+ *
1101+ * @attribute selected_value
1102+ * @type String
1103+ */
1104+ selected_value: {value: null},
1105+
1106+ /**
1107+ * Meta information about the current state of the associated field,
1108+ * whose value is selected by using this picker.
1109+ *
1110+ * @attribute selected_value_metadata
1111+ * @type String
1112+ */
1113+ selected_value_metadata: {value: null},
1114+
1115+ /**
1116+ * Results currently displayed by the widget. Updating this value
1117+ * automatically updates the display.
1118+ *
1119+ * @attribute results
1120+ * @type Array
1121+ */
1122+ results: { value: [] },
1123+
1124+ /**
1125+ * This adds any form fields you want below the search field.
1126+ * Updating this value automatically updates the display, but only
1127+ * if the widget has already been rendered. Otherwise, the change
1128+ * event never fires.
1129+ *
1130+ * @attribute search_slot
1131+ * @type Node
1132+ */
1133+ search_slot: {value: null},
1134+
1135+ /**
1136+ * A place for custom html at the bottom of the widget. When there
1137+ * are no search results the search_slot and the footer_slot are
1138+ * right next to each other.
1139+ * Updating this value automatically updates the display, but only
1140+ * if the widget has already been rendered. Otherwise, the change
1141+ * event never fires.
1142+ *
1143+ * @attribute footer_slot
1144+ * @type Node
1145+ */
1146+ footer_slot: {value: null},
1147+
1148+ /**
1149+ * Batches currently displayed in the widget, which can be
1150+ * clicked to change the batch of results being displayed. Updating
1151+ * this value automatically updates the display.
1152+ *
1153+ * This an array of object containing the two keys, name (used as
1154+ * the batch label) and value (used as additional details to 'search'
1155+ * event).
1156+ *
1157+ * @attribute batches
1158+ * @type Array
1159+ */
1160+ batches: {value: null},
1161+
1162+ /**
1163+ * For simplified batch creation, you can set this to the number of
1164+ * batches in the search results. In this case, the batch labels
1165+ * and values are automatically calculated. The batch name (used as the
1166+ * batch label) will be the batch number starting from 1. The batch value
1167+ * (used as additional details to the 'search' event) will be the batch
1168+ * number, starting from zero.
1169+ *
1170+ * If 'batches' is set (see above), batch_count is ignored.
1171+ *
1172+ * @attribute batch_count
1173+ * @type Integer
1174+ */
1175+ batch_count: {value: null},
1176+
1177+ /**
1178+ * Batch currently selected.
1179+ *
1180+ * @attribute selected_batch
1181+ * @type Integer
1182+ */
1183+ selected_batch: {
1184+ value: 0,
1185+ getter: function (value) {
1186+ return value || 0;
1187+ },
1188+ validator: function (value) {
1189+ var batches = this._getBatches();
1190+ return Y.Lang.isNumber(value) &&
1191+ value >= 0 &&
1192+ value < batches.length;
1193+ }},
1194+
1195+ /**
1196+ * Flag indicating if the widget is currently in search mode (so users
1197+ * has triggered a search and we are waiting for results.)
1198+ *
1199+ * @attribute search_mode
1200+ * @type Boolean
1201+ */
1202+ search_mode: { value: false },
1203+
1204+ /**
1205+ * The current error message. This puts the widget in 'error-mode',
1206+ * setting this value to null clears that state.
1207+ *
1208+ * @attribute error
1209+ * @type String
1210+ */
1211+ error: { value: null },
1212+
1213+ /**
1214+ * The message to display when the search returned no results. This string
1215+ * can contain a 'query' placeholder
1216+ *
1217+ * @attribute no_results_search_message
1218+ * @type String
1219+ * @default No items matched "{query}".
1220+ */
1221+ no_results_search_message: {
1222+ value: 'No items matched "{query}".'
1223+ }
1224+ }
1225+});
1226+
1227+
1228+ns.Picker.SAVE_RESULT = 0;
1229
1230
1231 /**
1232@@ -1296,9 +1182,11 @@
1233 initializer: function(config) {
1234 var input = Y.one(config.input_element);
1235 this.doAfter('save', function (e) {
1236- var result = e.details[Picker.SAVE_RESULT];
1237- this.get('host').set(SELECTED_VALUE_METADATA, result.metadata);
1238- this.get('host').set(SELECTED_VALUE, result.value);
1239+ var result = e.details[ns.Picker.SAVE_RESULT];
1240+ this.get('host').setAttrs({
1241+ selected_value_metadata: result.metadata,
1242+ selected_value: result.value
1243+ })
1244 input.set("value", result.value || '');
1245 // If the search input isn't blurred before it is focused,
1246 // then the I-beam disappears.
1247@@ -1313,17 +1201,15 @@
1248 // IE doesn't like setting the value to null, so use ''
1249 // instead.
1250 this.get('host')._search_input.set('value', selected_value || '');
1251- this.get('host').set(SELECTED_VALUE, selected_value);
1252+ this.get('host').set('selected_value', selected_value);
1253 });
1254 }
1255 });
1256
1257-var namespace = Y.namespace('lazr.picker');
1258-namespace.Picker = Picker;
1259-namespace.TextFieldPickerPlugin = TextFieldPickerPlugin;
1260+ns.TextFieldPickerPlugin = TextFieldPickerPlugin;
1261
1262 }, "0.1", {"skinnable": true,
1263- "requires": ["oop", "escape", "event", "event-focus", "node",
1264+ "requires": ["oop", "escape", "event", "event-focus", "base", "node",
1265 "plugin", "substitute", "widget", "widget-stdmod",
1266 "lazr.overlay", "lp.anim", "lazr.base",
1267 "lp.app.widgets.expander"]
1268
1269=== modified file 'lib/lp/app/javascript/picker/picker_patcher.js'
1270--- lib/lp/app/javascript/picker/picker_patcher.js 2012-03-21 01:26:45 +0000
1271+++ lib/lp/app/javascript/picker/picker_patcher.js 2012-06-21 15:03:22 +0000
1272@@ -177,7 +177,7 @@
1273 if (!show_search_box) {
1274 config.temp_spinner.removeClass('unseen');
1275 picker.set('min_search_chars', 0);
1276- picker.fire(Y.lazr.picker.Picker.SEARCH, '');
1277+ picker.fire('search', '');
1278 picker.get('contentBox').one(
1279 '.yui3-picker-search-box').addClass('unseen');
1280 }
1281@@ -520,7 +520,7 @@
1282 }
1283 picker._defaultSave(e);
1284 };
1285- picker.after(Y.lazr.picker.Picker.SAVE, save_handler);
1286+ picker.after('save', save_handler);
1287
1288 picker.subscribe('cancel', function (e) {
1289 Y.log('Got cancel event.');
1290@@ -666,7 +666,7 @@
1291 }
1292 };
1293
1294- picker.after(Y.lazr.picker.Picker.SEARCH, search_handler);
1295+ picker.after('search', search_handler);
1296
1297 picker.render();
1298 picker.hide();
1299
1300=== modified file 'lib/lp/app/javascript/picker/tests/test_personpicker.html'
1301--- lib/lp/app/javascript/picker/tests/test_personpicker.html 2012-04-25 18:17:08 +0000
1302+++ lib/lp/app/javascript/picker/tests/test_personpicker.html 2012-06-21 15:03:22 +0000
1303@@ -60,10 +60,6 @@
1304
1305 </head>
1306 <body class="yui3-skin-sam">
1307- <ul id="suites">
1308- <!-- <li>lp.large_indicator.test</li> -->
1309- <li>lp.person_picker.test</li>
1310- </ul>
1311
1312 <div class="yui3-widget yui3-activator yui3-activator-focused">
1313 <span id="picker_id" class="yui3-activator-content yui3-activator-success">
1314
1315=== modified file 'lib/lp/app/javascript/picker/tests/test_personpicker.js'
1316--- lib/lp/app/javascript/picker/tests/test_personpicker.js 2012-02-06 18:16:06 +0000
1317+++ lib/lp/app/javascript/picker/tests/test_personpicker.js 2012-06-21 15:03:22 +0000
1318@@ -1,5 +1,4 @@
1319-/* Copyright 2011 Canonical Ltd. This software is licensed under the
1320- * GNU Affero General Public License version 3 (see the file LICENSE).
1321+/* Copyright 2011 Canonical Ltd. This software is licensed under the * GNU Affero General Public License version 3 (see the file LICENSE).
1322 */
1323
1324 YUI().use('test', 'console', 'plugin',
1325@@ -286,6 +285,7 @@
1326 "test_link",
1327 "picker_id",
1328 config);
1329+ console.log(this.picker);
1330 },
1331
1332 test_picker_assign_me_button_hide_on_save: function() {