diff options
Diffstat (limited to 'src/widget')
| -rw-r--r-- | src/widget/form.js | 51 | ||||
| -rw-r--r-- | src/widget/input.js | 89 | ||||
| -rw-r--r-- | src/widget/select.js | 554 |
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(); - } - }; + } } - }]; -}); + } +}]; |
