aboutsummaryrefslogtreecommitdiffstats
path: root/src/widget
diff options
context:
space:
mode:
Diffstat (limited to 'src/widget')
-rw-r--r--src/widget/form.js51
-rw-r--r--src/widget/input.js89
-rw-r--r--src/widget/select.js554
3 files changed, 342 insertions, 352 deletions
diff --git a/src/widget/form.js b/src/widget/form.js
index 962cb6b8..405aae74 100644
--- a/src/widget/form.js
+++ b/src/widget/form.js
@@ -82,28 +82,31 @@
</doc:scenario>
</doc:example>
*/
-angularWidget('form', function(form){
- this.descend(true);
- this.directives(true);
- return ['$formFactory', '$element', function($formFactory, formElement) {
- var name = formElement.attr('name'),
- parentForm = $formFactory.forElement(formElement),
- form = $formFactory(parentForm);
- formElement.data('$form', form);
- formElement.bind('submit', function(event) {
- if (!formElement.attr('action')) event.preventDefault();
- });
- if (name) {
- this[name] = form;
+var ngFormDirective = ['$formFactory', function($formFactory) {
+ return {
+ restrict: 'E',
+ compile: function() {
+ return {
+ pre: function(scope, formElement, attr) {
+ var name = attr.name,
+ parentForm = $formFactory.forElement(formElement),
+ form = $formFactory(parentForm);
+ formElement.data('$form', form);
+ formElement.bind('submit', function(event){
+ if (!attr.action) event.preventDefault();
+ });
+ if (name) {
+ scope[name] = form;
+ }
+ watch('valid');
+ watch('invalid');
+ function watch(name) {
+ form.$watch('$' + name, function(value) {
+ formElement[value ? 'addClass' : 'removeClass']('ng-' + name);
+ });
+ }
+ }
+ };
}
- watch('valid');
- watch('invalid');
- function watch(name) {
- form.$watch('$' + name, function(value) {
- formElement[value ? 'addClass' : 'removeClass']('ng-' + name);
- });
- }
- }];
-});
-
-angularWidget('ng:form', angularWidget('form'));
+ };
+}];
diff --git a/src/widget/input.js b/src/widget/input.js
index e666a0c1..9f9d9852 100644
--- a/src/widget/input.js
+++ b/src/widget/input.js
@@ -542,23 +542,23 @@ angularInputType('checkbox', function(inputElement, widget) {
</doc:scenario>
</doc:example>
*/
-angularInputType('radio', function(inputElement, widget) {
+angularInputType('radio', function(inputElement, widget, attr) {
//correct the name
- inputElement.attr('name', widget.$id + '@' + inputElement.attr('name'));
+ attr.$set('name', widget.$id + '@' + attr.name);
inputElement.bind('click', function() {
widget.$apply(function() {
if (inputElement[0].checked) {
- widget.$emit('$viewChange', widget.$value);
+ widget.$emit('$viewChange', attr.value);
}
});
});
widget.$render = function() {
- inputElement[0].checked = isDefined(widget.$value) && (widget.$value == widget.$viewValue);
+ inputElement[0].checked = isDefined(attr.value) && (attr.value == widget.$viewValue);
};
if (inputElement[0].checked) {
- widget.$viewValue = widget.$value;
+ widget.$viewValue = attr.value;
}
});
@@ -664,28 +664,28 @@ var HTML5_INPUTS_TYPES = makeMap(
</doc:source>
<doc:scenario>
it('should initialize to model', function() {
- expect(binding('user')).toEqual('{\n \"last\":\"visitor",\n \"name\":\"guest\"}');
+ expect(binding('user')).toEqual('{"last":"visitor","name":"guest"}');
expect(binding('myForm.userName.$valid')).toEqual('true');
expect(binding('myForm.$valid')).toEqual('true');
});
it('should be invalid if empty when required', function() {
input('user.name').enter('');
- expect(binding('user')).toEqual('{\n \"last\":\"visitor",\n \"name\":\"\"}');
+ expect(binding('user')).toEqual('{"last":"visitor","name":""}');
expect(binding('myForm.userName.$valid')).toEqual('false');
expect(binding('myForm.$valid')).toEqual('false');
});
it('should be valid if empty when min length is set', function() {
input('user.last').enter('');
- expect(binding('user')).toEqual('{\n \"last\":\"",\n \"name\":\"guest\"}');
+ expect(binding('user')).toEqual('{"last":"","name":"guest"}');
expect(binding('myForm.lastName.$valid')).toEqual('true');
expect(binding('myForm.$valid')).toEqual('true');
});
it('should be invalid if less than required min length', function() {
input('user.last').enter('xx');
- expect(binding('user')).toEqual('{\n \"last\":\"xx",\n \"name\":\"guest\"}');
+ expect(binding('user')).toEqual('{"last":"xx","name":"guest"}');
expect(binding('myForm.lastName.$valid')).toEqual('false');
expect(binding('myForm.lastName.$error')).toMatch(/MINLENGTH/);
expect(binding('myForm.$valid')).toEqual('false');
@@ -694,7 +694,7 @@ var HTML5_INPUTS_TYPES = makeMap(
it('should be valid if longer than max length', function() {
input('user.last').enter('some ridiculously long name');
expect(binding('user'))
- .toEqual('{\n \"last\":\"some ridiculously long name",\n \"name\":\"guest\"}');
+ .toEqual('{"last":"some ridiculously long name","name":"guest"}');
expect(binding('myForm.lastName.$valid')).toEqual('false');
expect(binding('myForm.lastName.$error')).toMatch(/MAXLENGTH/);
expect(binding('myForm.$valid')).toEqual('false');
@@ -702,26 +702,24 @@ var HTML5_INPUTS_TYPES = makeMap(
</doc:scenario>
</doc:example>
*/
-angularWidget('input', function(inputElement){
- this.directives(true);
- this.descend(true);
- var modelExp = inputElement.attr('ng:model');
- return modelExp &&
- ['$defer', '$formFactory', '$element',
- function($defer, $formFactory, inputElement) {
+var inputDirective = ['$defer', '$formFactory', function($defer, $formFactory) {
+ return {
+ restrict: 'E',
+ link: function(modelScope, inputElement, attr) {
+ if (!attr.ngModel) return;
+
var form = $formFactory.forElement(inputElement),
// We have to use .getAttribute, since jQuery tries to be smart and use the
// type property. Trouble is some browser change unknown to text.
- type = inputElement[0].getAttribute('type') || 'text',
+ type = attr.type || 'text',
TypeController,
- modelScope = this,
patternMatch, widget,
- pattern = trim(inputElement.attr('ng:pattern')),
- minlength = parseInt(inputElement.attr('ng:minlength'), 10),
- maxlength = parseInt(inputElement.attr('ng:maxlength'), 10),
+ pattern = attr.ngPattern,
+ modelExp = attr.ngModel,
+ minlength = parseInt(attr.ngMinlength, 10),
+ maxlength = parseInt(attr.ngMaxlength, 10),
loadFromScope = type.match(/^\s*\@\s*(.*)/);
-
if (!pattern) {
patternMatch = valueFn(true);
} else {
@@ -743,7 +741,7 @@ angularWidget('input', function(inputElement){
type = lowercase(type);
TypeController = (loadFromScope
- ? (assertArgFn(this.$eval(loadFromScope[1]), loadFromScope[1])).$unboundFn
+ ? (assertArgFn(modelScope.$eval(loadFromScope[1]), loadFromScope[1])).$unboundFn
: angularInputType(type)) || noop;
if (!HTML5_INPUTS_TYPES[type]) {
@@ -757,26 +755,21 @@ angularWidget('input', function(inputElement){
}
//TODO(misko): setting $inject is a hack
- !TypeController.$inject && (TypeController.$inject = ['$element', '$scope']);
+ !TypeController.$inject && (TypeController.$inject = ['$element', '$scope', '$attr']);
widget = form.$createWidget({
scope: modelScope,
model: modelExp,
- onChange: inputElement.attr('ng:change'),
- alias: inputElement.attr('name'),
+ onChange: attr.ngChange,
+ alias: attr.name,
controller: TypeController,
- controllerArgs: {$element: inputElement}
+ controllerArgs: {$element: inputElement, $attr: attr}
});
- watchElementProperty(this, widget, 'value', inputElement);
- watchElementProperty(this, widget, 'required', inputElement);
- watchElementProperty(this, widget, 'readonly', inputElement);
- watchElementProperty(this, widget, 'disabled', inputElement);
-
widget.$pristine = !(widget.$dirty = false);
widget.$on('$validate', function() {
var $viewValue = trim(widget.$viewValue),
- inValid = widget.$required && !$viewValue,
+ inValid = attr.required && !$viewValue,
tooLong = maxlength && $viewValue && $viewValue.length > maxlength,
tooShort = minlength && $viewValue && $viewValue.length < minlength,
missMatch = $viewValue && !patternMatch($viewValue);
@@ -812,7 +805,7 @@ angularWidget('input', function(inputElement){
inputElement.val(widget.$viewValue || '');
};
- inputElement.bind('keydown change input', function(event) {
+ inputElement.bind('keydown change input', function(event){
var key = event.keyCode;
if (/*command*/ key != 91 &&
/*modifiers*/ !(15 < key && key < 19) &&
@@ -827,8 +820,9 @@ angularWidget('input', function(inputElement){
}
});
}
- }];
-});
+ }
+ };
+}];
/**
@@ -856,24 +850,3 @@ angularWidget('input', function(inputElement){
* @param {string=} ng:change Angular expression to be executed when input changes due to user
* interaction with the input element.
*/
-angularWidget('textarea', angularWidget('input'));
-
-
-function watchElementProperty(modelScope, widget, name, element) {
- var bindAttr = fromJson(element.attr('ng:bind-attr') || '{}'),
- match = /\s*\{\{(.*)\}\}\s*/.exec(bindAttr[name]),
- isBoolean = BOOLEAN_ATTR[name];
- widget['$' + name] = isBoolean
- ? ( // some browsers return true some '' when required is set without value.
- isString(element.prop(name)) || !!element.prop(name) ||
- // this is needed for ie9, since it will treat boolean attributes as false
- !!element[0].attributes[name])
- : element.attr(name);
- if (bindAttr[name] && match) {
- modelScope.$watch(match[1], function(value) {
- widget['$' + name] = isBoolean ? !!value : value;
- widget.$emit('$validate');
- widget.$render && widget.$render();
- });
- }
-}
diff --git a/src/widget/select.js b/src/widget/select.js
index a3633ddd..ae9e1b7c 100644
--- a/src/widget/select.js
+++ b/src/widget/select.js
@@ -112,321 +112,335 @@
</doc:source>
<doc:scenario>
it('should check ng:options', function() {
- expect(binding('color')).toMatch('red');
+ expect(binding('{selected_color:color}')).toMatch('red');
select('color').option('0');
- expect(binding('color')).toMatch('black');
+ expect(binding('{selected_color:color}')).toMatch('black');
using('.nullable').select('color').option('');
- expect(binding('color')).toMatch('null');
+ expect(binding('{selected_color:color}')).toMatch('null');
});
</doc:scenario>
</doc:example>
*/
-
- //00001111100000000000222200000000000000000000003333000000000000044444444444444444000000000555555555555555550000000666666666666666660000000000000007777
-var NG_OPTIONS_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?(?:\s+group\s+by\s+(.*))?\s+for\s+(?:([\$\w][\$\w\d]*)|(?:\(\s*([\$\w][\$\w\d]*)\s*,\s*([\$\w][\$\w\d]*)\s*\)))\s+in\s+(.*)$/;
-
-
-angularWidget('select', function(element){
- this.directives(true);
- this.descend(true);
- return element.attr('ng:model') &&
- ['$formFactory', '$compile', '$parse', '$element',
- function($formFactory, $compile, $parse, selectElement){
- var modelScope = this,
- match,
- form = $formFactory.forElement(selectElement),
- multiple = selectElement.attr('multiple'),
- optionsExp = selectElement.attr('ng:options'),
- modelExp = selectElement.attr('ng:model'),
- widget = form.$createWidget({
- scope: modelScope,
- model: modelExp,
- onChange: selectElement.attr('ng:change'),
- alias: selectElement.attr('name'),
- controller: ['$scope', optionsExp ? Options : (multiple ? Multiple : Single)]});
-
- selectElement.bind('$destroy', function() { widget.$destroy(); });
-
- widget.$pristine = !(widget.$dirty = false);
-
- watchElementProperty(modelScope, widget, 'required', selectElement);
- watchElementProperty(modelScope, widget, 'readonly', selectElement);
- watchElementProperty(modelScope, widget, 'disabled', selectElement);
-
- widget.$on('$validate', function() {
- var valid = !widget.$required || !!widget.$modelValue;
- if (valid && multiple && widget.$required) valid = !!widget.$modelValue.length;
- if (valid !== !widget.$error.REQUIRED) {
- widget.$emit(valid ? '$valid' : '$invalid', 'REQUIRED');
- }
- });
-
- widget.$on('$viewChange', function() {
- widget.$pristine = !(widget.$dirty = true);
- });
-
- forEach(['valid', 'invalid', 'pristine', 'dirty'], function(name) {
- widget.$watch('$' + name, function(value) {
- selectElement[value ? 'addClass' : 'removeClass']('ng-' + name);
+var ngOptionsDirective = valueFn({ terminal: true });
+var selectDirective = ['$formFactory', '$compile', '$parse',
+ function($formFactory, $compile, $parse){
+ //00001111100000000000222200000000000000000000003333000000000000044444444444444444000000000555555555555555550000000666666666666666660000000000000007777
+ var NG_OPTIONS_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?(?:\s+group\s+by\s+(.*))?\s+for\s+(?:([\$\w][\$\w\d]*)|(?:\(\s*([\$\w][\$\w\d]*)\s*,\s*([\$\w][\$\w\d]*)\s*\)))\s+in\s+(.*)$/;
+
+ return {
+ restrict: 'E',
+ link: function(modelScope, selectElement, attr) {
+ if (!attr.ngModel) return;
+ var form = $formFactory.forElement(selectElement),
+ multiple = attr.multiple,
+ optionsExp = attr.ngOptions,
+ modelExp = attr.ngModel,
+ widget = form.$createWidget({
+ scope: modelScope,
+ model: modelExp,
+ onChange: attr.ngChange,
+ alias: attr.name,
+ controller: ['$scope', optionsExp ? Options : (multiple ? Multiple : Single)]});
+
+ selectElement.bind('$destroy', function() { widget.$destroy(); });
+
+ widget.$pristine = !(widget.$dirty = false);
+
+ widget.$on('$validate', function() {
+ var valid = !attr.required || !!widget.$modelValue;
+ if (valid && multiple && attr.required) valid = !!widget.$modelValue.length;
+ if (valid !== !widget.$error.REQUIRED) {
+ widget.$emit(valid ? '$valid' : '$invalid', 'REQUIRED');
+ }
});
- });
- ////////////////////////////
+ widget.$on('$viewChange', function() {
+ widget.$pristine = !(widget.$dirty = true);
+ });
- function Multiple(widget) {
- widget.$render = function() {
- var items = new HashMap(widget.$viewValue);
- forEach(selectElement.children(), function(option){
- option.selected = isDefined(items.get(option.value));
+ forEach(['valid', 'invalid', 'pristine', 'dirty'], function(name) {
+ widget.$watch('$' + name, function(value) {
+ selectElement[value ? 'addClass' : 'removeClass']('ng-' + name);
});
- };
+ });
+
+ ////////////////////////////
- selectElement.bind('change', function() {
- widget.$apply(function() {
- var array = [];
+ function Multiple(widget) {
+ widget.$render = function() {
+ var items = new HashMap(this.$viewValue);
forEach(selectElement.children(), function(option){
- if (option.selected) {
- array.push(option.value);
- }
+ option.selected = isDefined(items.get(option.value));
+ });
+ };
+
+ selectElement.bind('change', function() {
+ widget.$apply(function() {
+ var array = [];
+ forEach(selectElement.children(), function(option){
+ if (option.selected) {
+ array.push(option.value);
+ }
+ });
+ widget.$emit('$viewChange', array);
});
- widget.$emit('$viewChange', array);
});
- });
- }
+ }
- function Single(widget) {
- widget.$render = function() {
- selectElement.val(widget.$viewValue);
- };
+ function Single(widget) {
+ widget.$render = function() {
+ selectElement.val(widget.$viewValue);
+ };
- selectElement.bind('change', function() {
- widget.$apply(function() {
- widget.$emit('$viewChange', selectElement.val());
+ selectElement.bind('change', function() {
+ widget.$apply(function() {
+ widget.$emit('$viewChange', selectElement.val());
+ });
});
- });
-
- widget.$viewValue = selectElement.val();
- }
-
- function Options(widget) {
- var match;
- if (! (match = optionsExp.match(NG_OPTIONS_REGEXP))) {
- throw Error(
- "Expected ng:options in form of '_select_ (as _label_)? for (_key_,)?_value_ in _collection_'" +
- " but got '" + optionsExp + "'.");
+ widget.$viewValue = selectElement.val();
}
- var displayFn = $parse(match[2] || match[1]),
- valueName = match[4] || match[6],
- keyName = match[5],
- groupByFn = $parse(match[3] || ''),
- valueFn = $parse(match[2] ? match[1] : valueName),
- valuesFn = $parse(match[7]),
- // we can't just jqLite('<option>') since jqLite is not smart enough
- // to create it in <select> and IE barfs otherwise.
- optionTemplate = jqLite(document.createElement('option')),
- optGroupTemplate = jqLite(document.createElement('optgroup')),
- nullOption = false, // if false then user will not be able to select it
- // This is an array of array of existing option groups in DOM. We try to reuse these if possible
- // optionGroupsCache[0] is the options with no option group
- // optionGroupsCache[?][0] is the parent: either the SELECT or OPTGROUP element
- optionGroupsCache = [[{element: selectElement, label:''}]];
-
- // find existing special options
- forEach(selectElement.children(), function(option) {
- if (option.value == '') {
- // developer declared null option, so user should be able to select it
- nullOption = jqLite(option).remove();
- // compile the element since there might be bindings in it
- $compile(nullOption)(modelScope);
- }
- });
- selectElement.html(''); // clear contents
+ function Options(widget) {
+ var match;
- selectElement.bind('change', function() {
- widget.$apply(function() {
- var optionGroup,
- collection = valuesFn(modelScope) || [],
- key = selectElement.val(),
- tempScope = inherit(modelScope),
- value, optionElement, index, groupIndex, length, groupLength;
+ if (! (match = optionsExp.match(NG_OPTIONS_REGEXP))) {
+ throw Error(
+ "Expected ng:options in form of '_select_ (as _label_)? for (_key_,)?_value_ in _collection_'" +
+ " but got '" + optionsExp + "'.");
+ }
- if (multiple) {
- value = [];
- for (groupIndex = 0, groupLength = optionGroupsCache.length;
- groupIndex < groupLength;
- groupIndex++) {
- // list of options for that group. (first item has the parent)
- optionGroup = optionGroupsCache[groupIndex];
-
- for(index = 1, length = optionGroup.length; index < length; index++) {
- if ((optionElement = optionGroup[index].element)[0].selected) {
- if (keyName) tempScope[keyName] = key;
- tempScope[valueName] = collection[optionElement.val()];
- value.push(valueFn(tempScope));
+ var displayFn = $parse(match[2] || match[1]),
+ valueName = match[4] || match[6],
+ keyName = match[5],
+ groupByFn = $parse(match[3] || ''),
+ valueFn = $parse(match[2] ? match[1] : valueName),
+ valuesFn = $parse(match[7]),
+ // we can't just jqLite('<option>') since jqLite is not smart enough
+ // to create it in <select> and IE barfs otherwise.
+ optionTemplate = jqLite(document.createElement('option')),
+ optGroupTemplate = jqLite(document.createElement('optgroup')),
+ nullOption = false, // if false then user will not be able to select it
+ // This is an array of array of existing option groups in DOM. We try to reuse these if possible
+ // optionGroupsCache[0] is the options with no option group
+ // optionGroupsCache[?][0] is the parent: either the SELECT or OPTGROUP element
+ optionGroupsCache = [[{element: selectElement, label:''}]];
+
+ // find existing special options
+ forEach(selectElement.children(), function(option) {
+ if (option.value == '') {
+ // developer declared null option, so user should be able to select it
+ nullOption = jqLite(option).remove();
+ // compile the element since there might be bindings in it
+ $compile(nullOption)(modelScope);
+ }
+ });
+ selectElement.html(''); // clear contents
+
+ selectElement.bind('change', function() {
+ widget.$apply(function() {
+ var optionGroup,
+ collection = valuesFn(modelScope) || [],
+ key = selectElement.val(),
+ tempScope = inherit(modelScope),
+ value, optionElement, index, groupIndex, length, groupLength;
+
+ if (multiple) {
+ value = [];
+ for (groupIndex = 0, groupLength = optionGroupsCache.length;
+ groupIndex < groupLength;
+ groupIndex++) {
+ // list of options for that group. (first item has the parent)
+ optionGroup = optionGroupsCache[groupIndex];
+
+ for(index = 1, length = optionGroup.length; index < length; index++) {
+ if ((optionElement = optionGroup[index].element)[0].selected) {
+ if (keyName) tempScope[keyName] = key;
+ tempScope[valueName] = collection[optionElement.val()];
+ value.push(valueFn(tempScope));
+ }
}
}
- }
- } else {
- if (key == '?') {
- value = undefined;
- } else if (key == ''){
- value = null;
} else {
- tempScope[valueName] = collection[key];
- if (keyName) tempScope[keyName] = key;
- value = valueFn(tempScope);
+ if (key == '?') {
+ value = undefined;
+ } else if (key == ''){
+ value = null;
+ } else {
+ tempScope[valueName] = collection[key];
+ if (keyName) tempScope[keyName] = key;
+ value = valueFn(tempScope);
+ }
}
- }
- if (isDefined(value) && modelScope.$viewVal !== value) {
- widget.$emit('$viewChange', value);
- }
+ if (isDefined(value) && modelScope.$viewVal !== value) {
+ widget.$emit('$viewChange', value);
+ }
+ });
});
- });
- widget.$watch(render);
- widget.$render = render;
-
- function render() {
- var optionGroups = {'':[]}, // Temporary location for the option groups before we render them
- optionGroupNames = [''],
- optionGroupName,
- optionGroup,
- option,
- existingParent, existingOptions, existingOption,
- modelValue = widget.$modelValue,
- values = valuesFn(modelScope) || [],
- keys = keyName ? sortedKeys(values) : values,
- groupLength, length,
- groupIndex, index,
- optionScope = inherit(modelScope),
- selected,
- selectedSet = false, // nothing is selected yet
- lastElement,
- element;
-
- if (multiple) {
- selectedSet = new HashMap(modelValue);
- } else if (modelValue === null || nullOption) {
- // if we are not multiselect, and we are null then we have to add the nullOption
- optionGroups[''].push({selected:modelValue === null, id:'', label:''});
- selectedSet = true;
- }
+ widget.$watch(render);
+ widget.$render = render;
+
+ function render() {
+ var optionGroups = {'':[]}, // Temporary location for the option groups before we render them
+ optionGroupNames = [''],
+ optionGroupName,
+ optionGroup,
+ option,
+ existingParent, existingOptions, existingOption,
+ modelValue = widget.$modelValue,
+ values = valuesFn(modelScope) || [],
+ keys = keyName ? sortedKeys(values) : values,
+ groupLength, length,
+ groupIndex, index,
+ optionScope = inherit(modelScope),
+ selected,
+ selectedSet = false, // nothing is selected yet
+ lastElement,
+ element;
- // We now build up the list of options we need (we merge later)
- for (index = 0; length = keys.length, index < length; index++) {
- optionScope[valueName] = values[keyName ? optionScope[keyName]=keys[index]:index];
- optionGroupName = groupByFn(optionScope) || '';
- if (!(optionGroup = optionGroups[optionGroupName])) {
- optionGroup = optionGroups[optionGroupName] = [];
- optionGroupNames.push(optionGroupName);
- }
if (multiple) {
- selected = selectedSet.remove(valueFn(optionScope)) != undefined;
- } else {
- selected = modelValue === valueFn(optionScope);
- selectedSet = selectedSet || selected; // see if at least one item is selected
+ selectedSet = new HashMap(modelValue);
+ } else if (modelValue === null || nullOption) {
+ // if we are not multiselect, and we are null then we have to add the nullOption
+ optionGroups[''].push({selected:modelValue === null, id:'', label:''});
+ selectedSet = true;
}
- optionGroup.push({
- id: keyName ? keys[index] : index, // either the index into array or key from object
- label: displayFn(optionScope) || '', // what will be seen by the user
- selected: selected // determine if we should be selected
- });
- }
- if (!multiple && !selectedSet) {
- // nothing was selected, we have to insert the undefined item
- optionGroups[''].unshift({id:'?', label:'', selected:true});
- }
- // Now we need to update the list of DOM nodes to match the optionGroups we computed above
- for (groupIndex = 0, groupLength = optionGroupNames.length;
- groupIndex < groupLength;
- groupIndex++) {
- // current option group name or '' if no group
- optionGroupName = optionGroupNames[groupIndex];
-
- // list of options for that group. (first item has the parent)
- optionGroup = optionGroups[optionGroupName];
-
- if (optionGroupsCache.length <= groupIndex) {
- // we need to grow the optionGroups
- existingParent = {
- element: optGroupTemplate.clone().attr('label', optionGroupName),
- label: optionGroup.label
- };
- existingOptions = [existingParent];
- optionGroupsCache.push(existingOptions);
- selectElement.append(existingParent.element);
- } else {
- existingOptions = optionGroupsCache[groupIndex];
- existingParent = existingOptions[0]; // either SELECT (no group) or OPTGROUP element
-
- // update the OPTGROUP label if not the same.
- if (existingParent.label != optionGroupName) {
- existingParent.element.attr('label', existingParent.label = optionGroupName);
+ // We now build up the list of options we need (we merge later)
+ for (index = 0; length = keys.length, index < length; index++) {
+ optionScope[valueName] = values[keyName ? optionScope[keyName]=keys[index]:index];
+ optionGroupName = groupByFn(optionScope) || '';
+ if (!(optionGroup = optionGroups[optionGroupName])) {
+ optionGroup = optionGroups[optionGroupName] = [];
+ optionGroupNames.push(optionGroupName);
+ }
+ if (multiple) {
+ selected = selectedSet.remove(valueFn(optionScope)) != undefined;
+ } else {
+ selected = modelValue === valueFn(optionScope);
+ selectedSet = selectedSet || selected; // see if at least one item is selected
}
+ optionGroup.push({
+ id: keyName ? keys[index] : index, // either the index into array or key from object
+ label: displayFn(optionScope) || '', // what will be seen by the user
+ selected: selected // determine if we should be selected
+ });
+ }
+ if (!multiple && !selectedSet) {
+ // nothing was selected, we have to insert the undefined item
+ optionGroups[''].unshift({id:'?', label:'', selected:true});
}
- lastElement = null; // start at the begining
- for(index = 0, length = optionGroup.length; index < length; index++) {
- option = optionGroup[index];
- if ((existingOption = existingOptions[index+1])) {
- // reuse elements
- lastElement = existingOption.element;
- if (existingOption.label !== option.label) {
- lastElement.text(existingOption.label = option.label);
- }
- if (existingOption.id !== option.id) {
- lastElement.val(existingOption.id = option.id);
- }
- if (existingOption.element.selected !== option.selected) {
- lastElement.prop('selected', (existingOption.selected = option.selected));
- }
+ // Now we need to update the list of DOM nodes to match the optionGroups we computed above
+ for (groupIndex = 0, groupLength = optionGroupNames.length;
+ groupIndex < groupLength;
+ groupIndex++) {
+ // current option group name or '' if no group
+ optionGroupName = optionGroupNames[groupIndex];
+
+ // list of options for that group. (first item has the parent)
+ optionGroup = optionGroups[optionGroupName];
+
+ if (optionGroupsCache.length <= groupIndex) {
+ // we need to grow the optionGroups
+ existingParent = {
+ element: optGroupTemplate.clone().attr('label', optionGroupName),
+ label: optionGroup.label
+ };
+ existingOptions = [existingParent];
+ optionGroupsCache.push(existingOptions);
+ selectElement.append(existingParent.element);
} else {
- // grow elements
+ existingOptions = optionGroupsCache[groupIndex];
+ existingParent = existingOptions[0]; // either SELECT (no group) or OPTGROUP element
- // if it's a null option
- if (option.id === '' && nullOption) {
- // put back the pre-compiled element
- element = nullOption;
- } else {
- // jQuery(v1.4.2) Bug: We should be able to chain the method calls, but
- // in this version of jQuery on some browser the .text() returns a string
- // rather then the element.
- (element = optionTemplate.clone())
- .val(option.id)
- .attr('selected', option.selected)
- .text(option.label);
+ // update the OPTGROUP label if not the same.
+ if (existingParent.label != optionGroupName) {
+ existingParent.element.attr('label', existingParent.label = optionGroupName);
}
+ }
- existingOptions.push(existingOption = {
- element: element,
- label: option.label,
- id: option.id,
- selected: option.selected
- });
- if (lastElement) {
- lastElement.after(element);
+ lastElement = null; // start at the begining
+ for(index = 0, length = optionGroup.length; index < length; index++) {
+ option = optionGroup[index];
+ if ((existingOption = existingOptions[index+1])) {
+ // reuse elements
+ lastElement = existingOption.element;
+ if (existingOption.label !== option.label) {
+ lastElement.text(existingOption.label = option.label);
+ }
+ if (existingOption.id !== option.id) {
+ lastElement.val(existingOption.id = option.id);
+ }
+ if (existingOption.element.selected !== option.selected) {
+ lastElement.prop('selected', (existingOption.selected = option.selected));
+ }
} else {
- existingParent.element.append(element);
+ // grow elements
+
+ // if it's a null option
+ if (option.id === '' && nullOption) {
+ // put back the pre-compiled element
+ element = nullOption;
+ } else {
+ // jQuery(v1.4.2) Bug: We should be able to chain the method calls, but
+ // in this version of jQuery on some browser the .text() returns a string
+ // rather then the element.
+ (element = optionTemplate.clone())
+ .val(option.id)
+ .attr('selected', option.selected)
+ .text(option.label);
+ }
+
+ existingOptions.push(existingOption = {
+ element: element,
+ label: option.label,
+ id: option.id,
+ selected: option.selected
+ });
+ if (lastElement) {
+ lastElement.after(element);
+ } else {
+ existingParent.element.append(element);
+ }
+ lastElement = element;
}
- lastElement = element;
+ }
+ // remove any excessive OPTIONs in a group
+ index++; // increment since the existingOptions[0] is parent element not OPTION
+ while(existingOptions.length > index) {
+ existingOptions.pop().element.remove();
}
}
- // remove any excessive OPTIONs in a group
- index++; // increment since the existingOptions[0] is parent element not OPTION
- while(existingOptions.length > index) {
- existingOptions.pop().element.remove();
+ // remove any excessive OPTGROUPs from select
+ while(optionGroupsCache.length > groupIndex) {
+ optionGroupsCache.pop()[0].element.remove();
}
+ };
+ }
+ }
+ }
+}];
+
+var optionDirective = ['$interpolate', function($interpolate) {
+ return {
+ priority: 100,
+ compile: function(element, attr) {
+ if (isUndefined(attr.value)) {
+ var interpolateFn = $interpolate(element.text(), true);
+ if (interpolateFn) {
+ return function (scope, element, attr) {
+ scope.$watch(interpolateFn, function(value) {
+ attr.$set('value', value);
+ });
+ }
+ } else {
+ attr.$set('value', element.text());
}
- // remove any excessive OPTGROUPs from select
- while(optionGroupsCache.length > groupIndex) {
- optionGroupsCache.pop()[0].element.remove();
- }
- };
+ }
}
- }];
-});
+ }
+}];