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

Commit d717020

Browse files
committed
widgets now work properly
1 parent 85f13d6 commit d717020

File tree

7 files changed

+134
-49
lines changed

7 files changed

+134
-49
lines changed

scenario/widgets.html

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
<td>radio</td>
3333
<td>
3434
<input type="radio" name="gender" value="female"/> Female <br/>
35-
<input type="radio" name="gender" value="male"/> Male
35+
<input type="radio" name="gender" value="male" checked="checked"/> Male
3636
</td>
3737
<td>gender={{gender}}</td>
3838
</tr>
@@ -42,7 +42,9 @@
4242
<input type="checkbox" name="checkbox.tea" checked value="on"/> Tea<br/>
4343
<input type="checkbox" name="checkbox.coffee" value="on"/> Coffe
4444
</td>
45-
<td>checkbox={{checkbox}}</td>
45+
<td>
46+
<pre>checkbox={{checkbox}}</pre>
47+
</td>
4648
</tr>
4749
<tr>
4850
<td>select</td>
@@ -71,10 +73,10 @@
7173
<td>ng-action</td>
7274
<td>
7375
<form ng-init="button.count = 0">
74-
<input type="button" value="button" ng-action="button.count = button.count + 1"/> <br/>
75-
<input type="submit" value="submit" ng-action="button.count = button.count + 1"/><br/>
76-
<input type="image" src="" ng-action="button.count = button.count + 1"/><br/>
77-
<a href="" ng-action="button.count = button.count + 1">action</a>
76+
<input type="button" value="button" ng-change="button.count = button.count + 1"/> <br/>
77+
<input type="submit" value="submit" ng-change="button.count = button.count + 1"/><br/>
78+
<input type="image" src="" ng-change="button.count = button.count + 1"/><br/>
79+
<a href="" ng-click="button.count = button.count + 1">action</a>
7880
</form>
7981
</td>
8082
<td>button={{button}}</td>

src/Angular.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,14 @@ function parseKeyValue(keyValue) {
398398
return obj;
399399
}
400400

401+
function toKeyValue(obj) {
402+
var parts = [];
403+
foreach(obj, function(value, key){
404+
parts.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));
405+
});
406+
return parts.length ? parts.join('&') : '';
407+
};
408+
401409
function angularInit(config){
402410
if (config.autobind) {
403411
compile(window.document, config).$init();

src/Scope.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ function createScope(parent, Class) {
102102
instance = new Behavior();
103103

104104
extend(api, {
105+
'this': instance,
105106
$parent: parent,
106107
$bind: bind(instance, bind, instance),
107108
$get: bind(instance, getter, instance),
@@ -162,7 +163,7 @@ function createScope(parent, Class) {
162163
behavior.$root = instance;
163164
behavior.$parent = instance;
164165
}
165-
166+
(parent.$onEval || noop)(instance.$eval);
166167
Class.apply(instance, slice.call(arguments, 2, arguments.length));
167168

168169
return instance;

src/Widgets.js

Lines changed: 66 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,8 @@ function optionsAccessor(scope, element) {
8585

8686
function noopAccessor() { return { get: noop, set: noop }; }
8787

88-
var textWidget = inputWidget('keyup change', modelAccessor, valueAccessor, ''),
89-
buttonWidget = inputWidget('click', noopAccessor, noopAccessor, undefined),
88+
var textWidget = inputWidget('keyup change', modelAccessor, valueAccessor, initWidgetValue('')),
89+
buttonWidget = inputWidget('click', noopAccessor, noopAccessor, noop),
9090
INPUT_TYPE = {
9191
'text': textWidget,
9292
'textarea': textWidget,
@@ -96,29 +96,42 @@ var textWidget = inputWidget('keyup change', modelAccessor, valueAccessor, ''),
9696
'submit': buttonWidget,
9797
'reset': buttonWidget,
9898
'image': buttonWidget,
99-
'checkbox': inputWidget('click', modelAccessor, checkedAccessor, false),
100-
'radio': inputWidget('click', modelAccessor, radioAccessor, undefined),
101-
'select-one': inputWidget('click', modelAccessor, valueAccessor, null),
102-
'select-multiple': inputWidget('click', modelAccessor, optionsAccessor, [])
99+
'checkbox': inputWidget('click', modelAccessor, checkedAccessor, initWidgetValue(false)),
100+
'radio': inputWidget('click', modelAccessor, radioAccessor, radioInit),
101+
'select-one': inputWidget('change', modelAccessor, valueAccessor, initWidgetValue(null)),
102+
'select-multiple': inputWidget('change', modelAccessor, optionsAccessor, initWidgetValue([]))
103103
// 'file': fileWidget???
104104
};
105105

106-
function inputWidget(events, modelAccessor, viewAccessor, initValue) {
106+
function initWidgetValue(initValue) {
107+
return function (model, view) {
108+
var value = view.get() || copy(initValue);
109+
if (isUndefined(model.get()) && isDefined(value))
110+
model.set(value);
111+
};
112+
}
113+
114+
function radioInit(model, view) {
115+
var modelValue = model.get(), viewValue = view.get();
116+
if (isUndefined(modelValue)) model.set(null);
117+
if (viewValue != null) model.set(viewValue);
118+
}
119+
120+
function inputWidget(events, modelAccessor, viewAccessor, initFn) {
107121
return function(element) {
108122
var scope = this,
109123
model = modelAccessor(scope, element),
110124
view = viewAccessor(scope, element),
111-
action = element.attr('ng-change') || '',
112-
value = view.get() || copy(initValue);
113-
if (isUndefined(model.get()) && isDefined(value)) model.set(value);
125+
action = element.attr('ng-change') || '';
126+
initFn(model, view);
114127
this.$eval(element.attr('ng-init')||'');
115128
element.bind(events, function(){
116129
model.set(view.get());
117130
scope.$tryEval(action, element);
118131
scope.$root.$eval();
119-
// if we have no initValue than we are just a button,
132+
// if we have noop initFn than we are just a button,
120133
// therefore we want to prevent default action
121-
return isDefined(initValue);
134+
return initFn != noop;
122135
});
123136
view.set(model.get());
124137
scope.$watch(model.get, view.set);
@@ -137,3 +150,44 @@ angularWidget('SELECT', function(element){
137150
this.descend(true);
138151
return inputWidgetSelector.call(this, element);
139152
});
153+
154+
155+
angularWidget('INLINE', function(element){
156+
element.replaceWith(this.element("div"));
157+
var compiler = this,
158+
behavior = element.attr("behavior"),
159+
template = element.attr("template"),
160+
initExpr = element.attr("init");
161+
return function(boundElement){
162+
var scope = this;
163+
boundElement.load(template, function(){
164+
var templateScope = compiler.compile(boundElement)(boundElement, scope);
165+
templateScope.$tryEval(initExpr, boundElement);
166+
templateScope.$init();
167+
});
168+
};
169+
});
170+
171+
angularWidget('INCLUDE', function(element){
172+
element.replaceWith(this.element("div"));
173+
var matches = [];
174+
element.find("INLINE").each(function(){
175+
matches.push({match: jQuery(this).attr("match"), element: jQuery(this)});
176+
});
177+
var compiler = this,
178+
watchExpr = element.attr("watch");
179+
return function(boundElement){
180+
var scope = this;
181+
this.$watch(watchExpr, function(value){
182+
foreach(matches, function(inline){
183+
if(inline.match == value) {
184+
var template = inline.element.attr("template");
185+
boundElement.load(template, function(){
186+
var templateScope = compiler.compile(boundElement)(boundElement, scope);
187+
templateScope.$init();
188+
});
189+
}
190+
});
191+
});
192+
};
193+
});

src/services.js

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,40 @@
11
angularService("$window", bind(window, identity, window));
22

3-
var URL_MATCH = /^(file|ftp|http|https):\/\/(\w+:{0,1}\w*@)?([\w\.]+)(:([0-9]+))?([^\?#]+)?(\?([^#]*))((#([^\?]*))(\?([^\?]*))?)$/;
3+
var URL_MATCH = /^(file|ftp|http|https):\/\/(\w+:{0,1}\w*@)?([\w\.]*)(:([0-9]+))?([^\?#]+)(\?([^#]*))?((#([^\?]*))?(\?([^\?]*))?)$/;
4+
var DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp':21};
45
angularService("$location", function(){
56
var scope = this;
67
function location(url){
78
if (isDefined(url)) {
89
var match = URL_MATCH.exec(url);
9-
dump(match);
10-
location.href = url;
11-
location.protocol = match[1];
12-
location.host = match[3];
13-
location.port = match[5];
14-
location.path = match[6];
15-
location.search = parseKeyValue(match[8]);
16-
location.hash = match[9];
17-
location.hashPath = match[11];
18-
location.hashSearch = parseKeyValue(match[13]);
19-
foreach(location, dump);
10+
if (match) {
11+
location.href = url;
12+
location.protocol = match[1];
13+
location.host = match[3] || '';
14+
location.port = match[5] || DEFAULT_PORTS[location.href] || null;
15+
location.path = match[6];
16+
location.search = parseKeyValue(match[8]);
17+
location.hash = match[9];
18+
if (location.hash) location.hash = location.hash.substr(1);
19+
location.hashPath = match[11] || '';
20+
location.hashSearch = parseKeyValue(match[13]);
21+
}
2022
}
21-
var params = [];
22-
foreach(location.param, function(value, key){
23-
params.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));
24-
});
25-
return (location.path ? location.path : '') + (params.length ? '?' + params.join('&') : '');
23+
var hashKeyValue = toKeyValue(location.hashSearch);
24+
return location.href +
25+
(location.hashPath ? location.hashPath : '') +
26+
(hashKeyValue ? '?' + hashKeyValue : '');
2627
};
2728
this.$config.location.watch(function(url){
2829
location(url);
2930
});
31+
location(this.$config.location.get());
3032
this.$onEval(PRIORITY_LAST, function(){
31-
scope.$config.location.set(location());
33+
var href = location();
34+
if (href != location.href) {
35+
scope.$config.location.set(location());
36+
location.href = href;
37+
}
3238
});
3339
return location;
3440
});

test/servicesSpec.js

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,35 @@ describe("services", function(){
1414
});
1515

1616
it("should inject $location", function(){
17-
scope.$location('http://host:1234/p/a/t/h?query=value#path?key=value');
18-
expect(scope.$location.href).toEqual("http://host:123/p/a/t/h?query=value#path?key=value");
17+
scope.$location('http://host:123/p/a/t/h.html?query=value#path?key=value');
18+
expect(scope.$location.href).toEqual("http://host:123/p/a/t/h.html?query=value#path?key=value");
1919
expect(scope.$location.protocol).toEqual("http");
2020
expect(scope.$location.host).toEqual("host");
21-
expect(scope.$location.port).toEqual("1234");
22-
expect(scope.$location.path).toEqual("/p/a/t/h");
21+
expect(scope.$location.port).toEqual("123");
22+
expect(scope.$location.path).toEqual("/p/a/t/h.html");
2323
expect(scope.$location.search).toEqual({query:'value'});
2424
expect(scope.$location.hash).toEqual('path?key=value');
2525
expect(scope.$location.hashPath).toEqual('path');
2626
expect(scope.$location.hashSearch).toEqual({key:'value'});
2727

28-
scope.$anchor.path = 'page=http://path';
29-
scope.$anchor.param = {k:'a=b'};
28+
scope.$location.hashPath = 'page=http://path';
29+
scope.$location.hashSearch = {k:'a=b'};
3030

31-
expect(scope.$anchor()).toEqual('page=http://path?k=a%3Db');
31+
expect(scope.$location()).toEqual('http://host:123/p/a/t/h.html?query=value#path?key=valuepage=http://path?k=a%3Db');
32+
});
33+
34+
it('should parse file://', function(){
35+
scope.$location('file:///Users/Shared/misko/work/angular.js/scenario/widgets.html');
36+
expect(scope.$location.href).toEqual("file:///Users/Shared/misko/work/angular.js/scenario/widgets.html");
37+
expect(scope.$location.protocol).toEqual("file");
38+
expect(scope.$location.host).toEqual("");
39+
expect(scope.$location.port).toEqual(null);
40+
expect(scope.$location.path).toEqual("/Users/Shared/misko/work/angular.js/scenario/widgets.html");
41+
expect(scope.$location.search).toEqual({});
42+
expect(scope.$location.hash).toEqual('');
43+
expect(scope.$location.hashPath).toEqual('');
44+
expect(scope.$location.hashSearch).toEqual({});
3245

46+
expect(scope.$location()).toEqual('file:///Users/Shared/misko/work/angular.js/scenario/widgets.html');
3347
});
3448
});

test/widgetsSpec.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -114,23 +114,23 @@ describe("input widget", function(){
114114

115115
it('should type="checkbox"', function(){
116116
compile('<input type="checkbox" name="checkbox" checked ng-change="action = true"/>');
117-
expect(scope.$get('checkbox')).toEqual(true);
117+
expect(scope.checkbox).toEqual(true);
118118
element.click();
119-
expect(scope.$get('checkbox')).toEqual(false);
120-
expect(scope.$get('action')).toEqual(true);
119+
expect(scope.checkbox).toEqual(false);
120+
expect(scope.action).toEqual(true);
121121
element.click();
122-
expect(scope.$get('checkbox')).toEqual(true);
122+
expect(scope.checkbox).toEqual(true);
123123
});
124124

125125
it('should type="radio"', function(){
126126
compile('<div>' +
127127
'<input type="radio" name="chose" value="A" ng-change="clicked = 1"/>' +
128128
'<input type="radio" name="chose" value="B" checked ng-change="clicked = 2"/>' +
129+
'<input type="radio" name="chose" value="C" ng-change="clicked = 3"/>' +
129130
'</div>');
130131
var a = element[0].childNodes[0];
131132
var b = element[0].childNodes[1];
132133
expect(scope.chose).toEqual('B');
133-
expect(scope.clicked).not.toBeDefined();
134134
scope.chose = 'A';
135135
scope.$eval();
136136
expect(a.checked).toEqual(true);

0 commit comments

Comments
 (0)