Skip to content

Commit 4348694

Browse files
author
George Paterson
committed
Selectgroup: Code commenting to aid open development.
1 parent 665af10 commit 4348694

File tree

1 file changed

+75
-27
lines changed

1 file changed

+75
-27
lines changed

ui/jquery.ui.selectgroup.js

+75-27
Original file line numberDiff line numberDiff line change
@@ -39,35 +39,44 @@
3939
var that = this,
4040
id = this.element.attr('id');
4141
this.identifiers = ['ui-' + id, 'ui-' + id];
42+
// Append one selectgroup.group for the widget as outlined in the flyweight pattern.
4243
if ($.ui.selectgroup.group.initialised === false) {
4344
$('body').append($.ui.selectgroup.group);
4445
$.ui.selectgroup.group.hide();
4546
}
4647
$.ui.selectgroup.group.initialised = true;
48+
// Obtain the placeholder text either from the selected option or the first option.
4749
if ($(this.element).find('option:selected').length) {
4850
this.copy = this.element.find('option:selected').text();
4951
}
5052
else {
5153
this.copy = this.element.find('option').first().text();
5254
}
55+
// Create an element to replace the select element interface.
5356
this.placeholder = $('<a href="#" id="' + this.identifiers[1] + '" class="' + this.widgetBaseClass + ' ui-widget ui-state-default ui-corner-all"'
5457
+ 'role="button" aria-haspopup="true" aria-owns="' + this.widgetBaseClass + '-group">'
5558
+ '<span class="' + this.widgetBaseClass + '-copy">'+ this.copy +'</span>'
5659
+ '<span class="' + this.widgetBaseClass + '-icon ui-icon ui-icon-triangle-1-s"></span></a>');
60+
// If the option to inherit classes is true then get the classnames from the select element and apply to the placeholder element.
5761
if (this.options.classInherit.select) {
5862
this.placeholder.addClass(this.element.attr('class'));
5963
}
64+
// If the option to use the popup style is true then we need to add the popup classes.
6065
if (this.options.style === 'popup') {
6166
this.placeholder.addClass(this.widgetBaseClass + '-popup');
6267
this.placeholder.find('.' + this.widgetBaseClass + '-icon').removeClass('ui-icon-triangle-1-s').addClass('ui-icon-triangle-2-n-s');
6368
}
69+
// Place the placeholder element after the select element and hide the select element.
6470
this.element.after(this.placeholder).hide();
71+
// Binds events to the placeholder element to match the native select element events.
6572
this._placeholderEvents(true);
73+
// Bind the focus event on the associated label to the placeholder element.
6674
$('label[for="' + id + '"]').attr( 'for', this.identifiers[0] )
6775
.bind('click.selectgroup', function(event) {
6876
event.preventDefault();
6977
that.placeholder.focus();
7078
});
79+
// Bind click event to the document that will close the selectgroup.group if it is open.
7180
$(document).bind('click.selectmenu', function(event) {
7281
if (that.isOpen && !($(event.target).closest('.ui-selectgroup').length || $(event.target).closest('.ui-selectgroup-group').length)) {
7382
window.setTimeout( function() {
@@ -80,6 +89,7 @@
8089
},
8190
_placeholderEvents: function(value) {
8291
var that = this;
92+
// If true then we want to bind events to the placeholder.
8393
if (value === true) {
8494
this.placeholder.removeClass('ui-state-disabled')
8595
.bind('click.selectgroup', function(event) {
@@ -141,6 +151,7 @@
141151
$(this).removeClass('ui-state-hover');
142152
});
143153
}
154+
// Else binding events is false and we want to repath them to do nothing.
144155
else {
145156
this.placeholder.addClass('ui-state-disabled').unbind('.selectgroup');
146157
this.placeholder.bind('click.selectgroup', function(event) {
@@ -152,6 +163,7 @@
152163
}
153164
},
154165
_index: function() {
166+
// Get all the options within the select element. Map their properties to an array for quick reference.
155167
this.selectors = $.map($('option', this.element), function(value) {
156168
return {
157169
element: $(value),
@@ -169,6 +181,7 @@
169181
_renderGroup: function() {
170182
var that = this,
171183
hidden = false;
184+
// Create the selectgroup list container.
172185
this.group = $('<ul class="' + this.widgetBaseClass + '-list" role="listbox" aria-hidden="true"></ul>');
173186
if (this.options.autoWidth) {
174187
if (this.options.style === 'dropdown') {
@@ -181,14 +194,17 @@
181194
if (this.options.style === 'popup') {
182195
this.group.addClass(this.widgetBaseClass + '-popup');
183196
}
197+
// Render the options available within the select.
184198
this._renderOption();
185199
this.group.attr('aria-labelledby', this.identifiers[0]);
186200
$($.ui.selectgroup.group).html(this.group);
187201
},
188202
_renderOption: function() {
189203
var that = this;
204+
// Iterate through the selectors array which is a map of the options within the select element.
190205
$.each(this.selectors, function(index) {
191206
var self = this;
207+
// Each option will be represented as a list item. Bind properties and events to each list item.
192208
var list = $('<li role="presentation"><a role="option" href="#">'+ this.text +'</a></li>')
193209
.bind('click.selectgroup', function(event) {
194210
event.preventDefault();
@@ -219,11 +235,14 @@
219235
if (typeof this.disabled !== "undefined" && this.disabled === 'disabled') {
220236
list.addClass('ui-state-disabled');
221237
}
238+
// The option may be part of an option group.
222239
if (this.optgroup.length) {
223240
var name = that.widgetBaseClass + '-optgroup-' + that.element.find('optgroup').index(this.optgroup);
241+
// If the option group has already been rendered append the option list item to the option group list.
224242
if (that.group.find('li.' + name).length ) {
225243
that.group.find('li.' + name + ' ul').append(list);
226244
}
245+
// Else we need to create a representation of an option group and then append the option list item.
227246
else {
228247
var opt = $('<li class="' + name + ' ' + that.widgetBaseClass + '-optgroup"><span>'+ this.optgroup.attr('label') +'</span><ul></ul></li>');
229248
if (that.options.classInherit.optionGroup) {
@@ -237,6 +256,7 @@
237256
}
238257
}
239258
}
259+
// Else it is not part of an option group and we can append directly to the selectgroup list container.
240260
else {
241261
list.appendTo(that.group);
242262
}
@@ -246,11 +266,14 @@
246266
var options = this.options,
247267
local = this.group.find('li').not('.ui-selectgroup-optgroup'),
248268
instance = local.get(this.position);
269+
// Reset the selectgroup.group position.
249270
$($.ui.selectgroup.group).css({'top': 0, 'left': 0});
250-
$($.ui.selectgroup.group).show()
271+
$($.ui.selectgroup.group).show();
272+
// Adjust position if it is a popup.
251273
if (this.options.style === 'popup' && !this.options.positioning.offset) {
252274
var adjust = '0 -' + ($(instance).outerHeight() + $(instance).offset().top);
253275
}
276+
// Position the selectgroup.group using the jQuery UI position widget and any applied options.
254277
$($.ui.selectgroup.group).position({
255278
of: options.positioning.of || this.placeholder,
256279
my: options.positioning.my,
@@ -260,6 +283,7 @@
260283
});
261284
},
262285
_toggle: function() {
286+
// Toggle the selectgroup to open or close.
263287
if ($.ui.selectgroup.group.past !== null) {
264288
if ($.ui.selectgroup.group.past.element !== this.element) {
265289
this.focus();
@@ -296,34 +320,37 @@
296320
position = this.position,
297321
instance = null;
298322
position = this.position + value;
299-
if (position < 0) {
300-
position = 0;
301-
}
302-
if (position > maximum) {
303-
position = maximum;
304-
}
305-
if (position === record) {
306-
return;
307-
}
308-
if (this.selectors[position].disabled === 'disabled' || this.selectors[position].optDisabled === 'disabled') {
309-
if (value > 0) {
310-
++value;
311-
}
312-
else {
313-
--value;
314-
}
315-
this._traverse(value, position);
323+
// Traverse the selectgroup based on a numerical iteration.
324+
if (position < 0) {
325+
position = 0;
326+
}
327+
if (position > maximum) {
328+
position = maximum;
329+
}
330+
// If we have traversed through the positions and not found a new one due to a disabled state we can return to the last available position.
331+
if (position === record) {
332+
return;
333+
}
334+
if (this.selectors[position].disabled === 'disabled' || this.selectors[position].optDisabled === 'disabled') {
335+
if (value > 0) {
336+
++value;
316337
}
317338
else {
318-
this.position = position;
319-
instance = local.get(this.position);
320-
this.copy = $(instance).find('a').text();
321-
local.removeClass('ui-state-hover');
322-
$(instance).addClass('ui-state-hover');
323-
this.placeholder.find('.ui-selectgroup-copy').text(this.copy);
324-
this.element.find('option:selected').removeAttr('selected');
325-
$(this.selectors[this.position].element).attr('selected', 'selected');
339+
--value;
326340
}
341+
// The next position may be disabled. Iterate through until a position is found, zero or maxmimum position reached.
342+
this._traverse(value, position);
343+
}
344+
else {
345+
this.position = position;
346+
instance = local.get(this.position);
347+
this.copy = $(instance).find('a').text();
348+
local.removeClass('ui-state-hover');
349+
$(instance).addClass('ui-state-hover');
350+
this.placeholder.find('.ui-selectgroup-copy').text(this.copy);
351+
this.element.find('option:selected').removeAttr('selected');
352+
$(this.selectors[this.position].element).attr('selected', 'selected');
353+
}
327354
$.ui.selectgroup.group.position = value;
328355
},
329356
_typeahead: function(character) {
@@ -332,6 +359,7 @@
332359
local = this.group.find('li').not('.ui-selectgroup-optgroup'),
333360
instance = null,
334361
found = false;
362+
// Type ahead based on an alpha numeric charater input.
335363
character = character.toLowerCase();
336364
this.search[1] += character;
337365
window.clearTimeout(this.timer);
@@ -346,7 +374,10 @@
346374
found = true;
347375
that.search[3] = index;
348376
};
377+
// Typing ahead we need to remember an array of possible word states based on time and character position.
378+
// Is the new character a repetition of the first character.
349379
if (this.search[0] === this.search[1][0]) {
380+
// Are we attempting to iterate through the selectors using the same character?
350381
if (this.search[1].length < 2) {
351382
$.each(this.selectors, function(index) {
352383
if (!found) {
@@ -361,6 +392,7 @@
361392
});
362393
this.search[0] = this.search[1][0];
363394
}
395+
// Or are we navigating to a character combination based on the first character?
364396
else {
365397
$.each(this.selectors, function(index) {
366398
if (!found) {
@@ -377,6 +409,7 @@
377409
}
378410
}
379411
else {
412+
// Iterate through the selectors with the new unrepeated character.
380413
$.each(this.selectors, function(index) {
381414
if (!found) {
382415
if (that.search[1] === that.selectors[index].text.substring(0, that.search[1].length).toLowerCase()) {
@@ -387,14 +420,18 @@
387420
});
388421
this.search[0] = this.search[1][0];
389422
}
423+
// If the end of available matches is meet we can reposition to the start of available matches.
390424
if (that.search[4] === that.search[3]) {
391425
that.search[3] = that.search[2];
392426
focusOption(that.search[3]);
393427
}
394428
this.search[4] = this.search[3];
429+
// Clear the initial search after 1 second.
395430
this.timer = window.setTimeout(function() {that.search[1] = '';}, (1000));
396431
},
397432
destroy: function() {
433+
// Remove the selectgroup completely.
434+
// Has known issues.
398435
var id = this.identifiers[0].split('ui-')
399436
if (this.isOpen) {
400437
this.close();
@@ -407,6 +444,8 @@
407444
this.element.show();
408445
},
409446
enable: function(index, type) {
447+
// Enable the placeholder, an option group or option in the original select.
448+
// Trigger a 'change' event to redraw the selectgroup.
410449
if (this.isOpen) {
411450
this.close();
412451
}
@@ -423,6 +462,8 @@
423462
}
424463
},
425464
disable: function(index, type) {
465+
// Disable the placeholder, an option group or option in the original select.
466+
// Trigger a 'change' event to redraw the selectgroup.
426467
if (this.isOpen) {
427468
this.close();
428469
}
@@ -439,18 +480,22 @@
439480
}
440481
},
441482
focus: function() {
483+
// Focus on the placeholder to activate without opening the selectgroup.group.
442484
this._index();
443485
this._renderGroup();
444486
this.isActive = true;
445487
},
446488
blur: function() {
489+
// After losing focus on the placeholder.
447490
this.isActive = false;
448491
},
449492
change: function() {
493+
// Redraw the selectgroup.group.
450494
this._index();
451495
this._renderGroup();
452496
},
453497
refresh: function() {
498+
// Refresh the placeholder.
454499
if ($(this.element).find('option:selected').length) {
455500
this.copy = this.element.find('option:selected').text();
456501
}
@@ -460,6 +505,7 @@
460505
this.placeholder.find('.ui-selectgroup-copy').text(this.copy);
461506
},
462507
open: function() {
508+
// Open the selectgroup.group.
463509
if (this.options.style === 'dropdown') {
464510
$.ui.selectgroup.group.removeClass('ui-corner-all').addClass('ui-corner-bottom');
465511
this.placeholder.removeClass('ui-corner-all').addClass('ui-state-active ui-corner-top');
@@ -473,6 +519,7 @@
473519
this.isOpen = true;
474520
},
475521
close: function() {
522+
// Close the selectgroup.group.
476523
if ($.ui.selectgroup.group.past !== null) {
477524
$.ui.selectgroup.group.past.placeholder.removeClass('ui-state-active');
478525
}
@@ -486,7 +533,8 @@
486533
this.group.attr('aria-hidden', 'true');
487534
this.isOpen = false;
488535
}
489-
})
536+
});
537+
// Publically available objects used once by the selectgroup to enable the flyweight pattern.
490538
$.ui.selectgroup.group = $('<div class="ui-selectgroup-group ui-widget ui-widget-content ui-corner-all"></div>');
491539
$.ui.selectgroup.group.initialised = false;
492540
$.ui.selectgroup.group.past = null;

0 commit comments

Comments
 (0)