Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

Commit

Permalink
Closes #170. Corrected the behavior of select when options are ng:rep…
Browse files Browse the repository at this point in the history
…eated

 - Delete $postEval method, as it was a hack
  • Loading branch information
mhevery committed Dec 3, 2010
1 parent 41d5938 commit 5a8ad8f
Show file tree
Hide file tree
Showing 19 changed files with 328 additions and 229 deletions.
28 changes: 28 additions & 0 deletions regression/issue-170.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<!DOCTYPE HTML>
<html xmlns:ng="http://angularjs.org">
<head>
<script type="text/javascript" src="../lib/jquery/jquery-1.4.2.js"></script>
<script type="text/javascript" src="../src/angular-bootstrap.js" ng:autobind></script>
<head>
<body>
<select name='selection0' style="display:block;">
<option ng:repeat='value in ["FOO","BAR"]'">{{value}}</option>
</select>
{{selection0}} &lt;-- FOO should be shown here

<hr/>

<select ng:init="selection1='ignore'" name='selection1' style="display:block;">
<option ng:repeat='value in ["FOO","BAR"]' ng:bind-attr="{selected:'{{value==\'BAR\'}}'}">{{value}}</option>
</select>
{{selection1}} &lt;-- BAR should be shown here

<hr/>

<select ng:init="selection2=1" name="selection2" style="display:block;">
<option value="{{$index}}" ng:repeat="opt in ['zero', 'one']">{{opt}}</option>
</select>
{{selection2}} &lt;-- 1 should be shown here

</body>
</html>
5 changes: 5 additions & 0 deletions src/Angular.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ function fromCharCode(code) { return String.fromCharCode(code); }
var _undefined = undefined,
_null = null,
$$element = '$element',
$$update = '$update',
$$scope = '$scope',
$$validate = '$validate',
$angular = 'angular',
$array = 'array',
$boolean = 'boolean',
Expand All @@ -69,6 +72,8 @@ var _undefined = undefined,
$number = 'number',
$object = 'object',
$string = 'string',
$value = 'value',
$selected = 'selected',
$undefined = 'undefined',
NG_EXCEPTION = 'ng-exception',
NG_VALIDATION_ERROR = 'ng-validation-error',
Expand Down
13 changes: 13 additions & 0 deletions src/Compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Template.prototype = {
if (this.newScope) {
childScope = createScope(scope);
scope.$onEval(childScope.$eval);
element.data($$scope, childScope);
}
foreach(this.inits, function(fn) {
queue.push(function() {
Expand Down Expand Up @@ -68,6 +69,17 @@ Template.prototype = {
}
};

/*
* Function walks up the element chain looking for the scope associated with the give element.
*/
function retrieveScope(element) {
var scope;
while (element && !(scope = element.data($$scope))) {
element = element.parent();
}
return scope;
}

///////////////////////////////////
//Compiler
//////////////////////////////////
Expand Down Expand Up @@ -97,6 +109,7 @@ Compiler.prototype = {
element = jqLite(element);
var scope = parentScope && parentScope.$eval ?
parentScope : createScope(parentScope);
element.data($$scope, scope);
return extend(scope, {
$element:element,
$init: function() {
Expand Down
27 changes: 0 additions & 27 deletions src/Scope.js
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,6 @@ function createScope(parent, providers, instanceCache) {
parent = Parent.prototype = (parent || {});
var instance = new Parent();
var evalLists = {sorted:[]};
var postList = [], postHash = {}, postId = 0;

extend(instance, {
'this': instance,
Expand Down Expand Up @@ -371,11 +370,6 @@ function createScope(parent, providers, instanceCache) {
instance.$tryEval(queue[j].fn, queue[j].handler);
}
}
while(postList.length) {
fn = postList.shift();
delete postHash[fn.$postEvalId];
instance.$tryEval(fn);
}
} else if (type === $function) {
return exp.call(instance);
} else if (type === 'string') {
Expand Down Expand Up @@ -549,27 +543,6 @@ function createScope(parent, providers, instanceCache) {
});
},

/**
* @workInProgress
* @ngdoc function
* @name angular.scope.$postEval
* @function
*/
$postEval: function(expr) {
if (expr) {
var fn = expressionCompile(expr);
var id = fn.$postEvalId;
if (!id) {
id = '$' + instance.$id + "_" + (postId++);
fn.$postEvalId = id;
}
if (!postHash[id]) {
postList.push(postHash[id] = fn);
}
}
},


/**
* @workInProgress
* @ngdoc function
Expand Down
21 changes: 13 additions & 8 deletions src/directives.js
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,8 @@ angularDirective("ng:bind-template", function(expression, element){
var REMOVE_ATTRIBUTES = {
'disabled':'disabled',
'readonly':'readOnly',
'checked':'checked'
'checked':'checked',
'selected':'selected'
};
/**
* @workInProgress
Expand Down Expand Up @@ -359,27 +360,31 @@ var REMOVE_ATTRIBUTES = {
angularDirective("ng:bind-attr", function(expression){
return function(element){
var lastValue = {};
var updateFn = element.parent().data('$update');
var updateFn = element.data($$update) || noop;
this.$onEval(function(){
var values = this.$eval(expression);
var values = this.$eval(expression),
dirty = noop;
for(var key in values) {
var value = compileBindTemplate(values[key]).call(this, element),
specialName = REMOVE_ATTRIBUTES[lowercase(key)];
if (lastValue[key] !== value) {
lastValue[key] = value;
if (specialName) {
if (element[specialName] = toBoolean(value)) {
element.attr(specialName, value);
if (toBoolean(value)) {
element.attr(specialName, specialName);
element.attr('ng-' + specialName, value);
} else {
element.removeAttr(key);
element.removeAttr(specialName);
element.removeAttr('ng-' + specialName);
}
(element.data('$validate')||noop)();
(element.data($$validate)||noop)();
} else {
element.attr(key, value);
}
this.$postEval(updateFn);
dirty = updateFn;
}
}
dirty();
}, element);
};
});
Expand Down
2 changes: 1 addition & 1 deletion src/validators.js
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ extend(angularValidator, {
element.removeClass('ng-input-indicator-wait');
scope.$invalidWidgets.markValid(element);
}
element.data('$validate')();
element.data($$validate)();
scope.$root.$eval();
});
} else if (inputState.inFlight) {
Expand Down
49 changes: 40 additions & 9 deletions src/widgets.js
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ function valueAccessor(scope, element) {
required = requiredExpr === '';
}

element.data('$validate', validate);
element.data($$validate, validate);
return {
get: function(){
if (lastError)
Expand Down Expand Up @@ -391,6 +391,7 @@ var textWidget = inputWidget('keyup change', modelAccessor, valueAccessor, initW
// 'file': fileWidget???
};


function initWidgetValue(initValue) {
return function (model, view) {
var value = view.get();
Expand Down Expand Up @@ -461,18 +462,13 @@ function inputWidget(events, modelAccessor, viewAccessor, initFn) {
this.$eval(element.attr('ng:init')||'');
// Don't register a handler if we are a button (noopAccessor) and there is no action
if (action || modelAccessor !== noopAccessor) {
element.bind(events, function(event){
element.bind(events, function (){
model.set(view.get());
lastValue = model.get();
scope.$tryEval(action, element);
scope.$root.$eval();
});
}
function updateView(){
view.set(lastValue = model.get());
}
updateView();
element.data('$update', updateView);
scope.$watch(model.get, function(value){
if (lastValue !== value) {
view.set(lastValue = value);
Expand All @@ -494,15 +490,50 @@ angularWidget('select', function(element){
return inputWidgetSelector.call(this, element);
});


/*
* Consider this:
* <select name="selection">
* <option ng:repeat="x in [1,2]">{{x}}</option>
* </select>
*
* The issue is that the select gets evaluated before option is unrolled.
* This means that the selection is undefined, but the browser
* default behavior is to show the top selection in the list.
* To fix that we register a $update function on the select element
* and the option creation then calls the $update function when it is
* unrolled. The $update function then calls this update function, which
* then tries to determine if the model is unassigned, and if so it tries to
* chose one of the options from the list.
*/
angularWidget('option', function(){
this.descend(true);
this.directives(true);
return function(element) {
this.$postEval(element.parent().data('$update'));
var select = element.parent();
var scope = retrieveScope(select);
var model = modelFormattedAccessor(scope, select);
var view = valueAccessor(scope, select);
var option = element;
var lastValue = option.attr($value);
var lastSelected = option.attr('ng-' + $selected);
element.data($$update, function(){
var value = option.attr($value);
var selected = option.attr('ng-' + $selected);
var modelValue = model.get();
if (lastSelected != selected || lastValue != value) {
lastSelected = selected;
lastValue = value;
if (selected || modelValue == _null || modelValue == _undefined)
model.set(value);
if (value == modelValue) {
view.set(lastValue);
}
}
});
};
});


/**
* @workInProgress
* @ngdoc widget
Expand Down
1 change: 1 addition & 0 deletions test/AngularSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ describe('Angular', function(){
scope.$init();
scope.$eval();
expect(onUpdateView).wasCalled();
dealoc(scope);
});
});

Expand Down
1 change: 1 addition & 0 deletions test/BinderTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ BinderTest.prototype.setUp = function(){

this.compile = function(html, initialScope, parent) {
var compiler = new Compiler(angularTextMarkup, angularAttrMarkup, angularDirective, angularWidget);
if (self.element) dealoc(self.element);
var element = self.element = jqLite(html);
var scope = compiler.compile(element)(element);

Expand Down
25 changes: 15 additions & 10 deletions test/CompilerSpec.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
describe('compiler', function(){
var compiler, markup, directives, widgets, compile, log;
var compiler, markup, directives, widgets, compile, log, scope;

beforeEach(function(){
log = "";
Expand Down Expand Up @@ -32,6 +32,10 @@ describe('compiler', function(){
return scope;
};
});

afterEach(function(){
dealoc(scope);
});

it('should recognize a directive', function(){
var e = jqLite('<div directive="expr" ignore="me"></div>');
Expand All @@ -44,20 +48,21 @@ describe('compiler', function(){
};
};
var template = compiler.compile(e);
var init = template(e).$init;
scope = template(e);
var init = scope.$init;
expect(log).toEqual("found");
init();
expect(e.hasClass('ng-directive')).toEqual(true);
expect(log).toEqual("found:init");
});

it('should recurse to children', function(){
var scope = compile('<div><span hello="misko"/></div>');
scope = compile('<div><span hello="misko"/></div>');
expect(log).toEqual("hello misko");
});

it('should watch scope', function(){
var scope = compile('<span watch="name"/>');
scope = compile('<span watch="name"/>');
expect(log).toEqual("");
scope.$eval();
scope.$set('name', 'misko');
Expand All @@ -71,7 +76,7 @@ describe('compiler', function(){

it('should prevent descend', function(){
directives.stop = function(){ this.descend(false); };
var scope = compile('<span hello="misko" stop="true"><span hello="adam"/></span>');
scope = compile('<span hello="misko" stop="true"><span hello="adam"/></span>');
expect(log).toEqual("hello misko");
});

Expand All @@ -87,7 +92,7 @@ describe('compiler', function(){
});
};
};
var scope = compile('before<span duplicate="expr">x</span>after');
scope = compile('before<span duplicate="expr">x</span>after');
expect(sortedHtml(scope.$element)).toEqual('<div>before<#comment></#comment><span>x</span>after</div>');
scope.$eval();
expect(sortedHtml(scope.$element)).toEqual('<div>before<#comment></#comment><span>x</span><span>x</span>after</div>');
Expand All @@ -103,7 +108,7 @@ describe('compiler', function(){
textNode[0].nodeValue = 'replaced';
}
});
var scope = compile('before<span>middle</span>after');
scope = compile('before<span>middle</span>after');
expect(sortedHtml(scope.$element[0], true)).toEqual('<div>before<span class="ng-directive" hello="middle">replaced</span>after</div>');
expect(log).toEqual("hello middle");
});
Expand All @@ -116,7 +121,7 @@ describe('compiler', function(){
log += 'init';
};
};
var scope = compile('<ng:button>push me</ng:button>');
scope = compile('<ng:button>push me</ng:button>');
expect(lowercase(scope.$element[0].innerHTML)).toEqual('<div>button</div>');
expect(log).toEqual('init');
});
Expand All @@ -135,7 +140,7 @@ describe('compiler', function(){
if (text == '{{1+2}}')
parent.text('3');
});
var scope = compile('<div><h1>ignore me</h1></div>');
scope = compile('<div><h1>ignore me</h1></div>');
expect(scope.$element.text()).toEqual('3');
});

Expand All @@ -158,7 +163,7 @@ describe('compiler', function(){
textNode.remove();
}
});
var scope = compile('A---B---C===D');
scope = compile('A---B---C===D');
expect(sortedHtml(scope.$element)).toEqual('<div>A<hr></hr>B<hr></hr>C<p></p>D</div>');
});

Expand Down
Loading

0 comments on commit 5a8ad8f

Please sign in to comment.