Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
performace refactoring: intermediate commit
- Loading branch information
Tobias Bosch
committed
May 9, 2012
1 parent
f1f5b07
commit 664f29d
Showing
9 changed files
with
545 additions
and
66 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
(function ($, angular) { | ||
|
||
function watchJQueryDomChangesInSubtree(element, callback) { | ||
for (var fnName in jqFnWatchers) { | ||
jqFnWatchers[fnName](element, fnName, callback); | ||
} | ||
} | ||
|
||
var jqFnWatchers = { | ||
append: watchDomAddingFn, | ||
after: watchAttributeChangingFn, | ||
text: watchAttributeChangingFn, | ||
val: watchAttributeChangingFn, | ||
prop: watchAttributeChangingFn, | ||
attr: watchAttributeChangingFn, | ||
remove: watchAttributeChangingFn | ||
}; | ||
|
||
function watchDomAddingFn(node, fnName, callback) { | ||
var _old = node[fnName]; | ||
node[fnName] = function (otherNode) { | ||
watchJQueryDomChangesInSubtree(otherNode, callback); | ||
var res = _old.apply(this, arguments); | ||
callback(); | ||
return res; | ||
}; | ||
} | ||
|
||
function watchAttributeChangingFn(node, fnName, callback) { | ||
var _old = node[fnName]; | ||
node[fnName] = function (otherNode) { | ||
var res = _old.apply(this, arguments); | ||
callback(); | ||
return res; | ||
}; | ||
} | ||
|
||
|
||
/** | ||
* Modify the original repeat: Make sure that all elements are added under the same parent. | ||
* This is important, as some jquery mobile widgets wrap the elements into new elements, | ||
* and angular just uses element.after(). | ||
* See angular issue 831 | ||
*/ | ||
function instrumentNodeForNgRepeat(scope, parent, node, fnName) { | ||
var _old = node[fnName]; | ||
node[fnName] = function (otherNode) { | ||
var target = this; | ||
while (target.parent()[0] !== parent) { | ||
target = target.parent(); | ||
if (target.length === 0) { | ||
throw new Error("Could not find the expected parent in the node path", this, parent); | ||
} | ||
} | ||
instrumentNodeForNgRepeat(scope, parent, otherNode, fnName); | ||
var res = _old.call(target, otherNode); | ||
scope.$emit("$childrenChanged"); | ||
return res; | ||
}; | ||
} | ||
|
||
var mod = angular.module('ng'); | ||
mod.directive('ngRepeat', function () { | ||
return { | ||
priority:1000, // same as original repeat | ||
compile:function (element, attr, linker) { | ||
return { | ||
pre:function (scope, iterStartElement, attr) { | ||
instrumentNodeForNgRepeat(scope, iterStartElement.parent()[0], iterStartElement, 'after'); | ||
} | ||
}; | ||
} | ||
}; | ||
}); | ||
|
||
function sortedKeys(obj) { | ||
var keys = []; | ||
for (var key in obj) { | ||
if (obj.hasOwnProperty(key)) { | ||
keys.push(key); | ||
} | ||
} | ||
return keys.sort(); | ||
} | ||
|
||
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+(.*)$/; | ||
mod.directive('ngOptions', ['$parse', function ($parse) { | ||
return { | ||
require: ['select', '?ngModel'], | ||
link:function (scope, element, attr, ctrls) { | ||
// if ngModel is not defined, we don't need to do anything | ||
if (!ctrls[1]) return; | ||
|
||
var match; | ||
var optionsExp = attr.ngOptions; | ||
|
||
if (! (match = optionsExp.match(NG_OPTIONS_REGEXP))) { | ||
throw Error( | ||
"Expected ngOptions in form of '_select_ (as _label_)? for (_key_,)?_value_ in _collection_'" + | ||
" but got '" + optionsExp + "'."); | ||
} | ||
|
||
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]); | ||
|
||
scope.$watch(optionsModel, function() { | ||
scope.$emit("$childrenChanged"); | ||
console.log("now"); | ||
}, true); | ||
|
||
function optionsModel() { | ||
var optionGroups = [], // Temporary location for the option groups before we render them | ||
optionGroupName, | ||
values = valuesFn(scope) || [], | ||
keys = keyName ? sortedKeys(values) : values, | ||
length, | ||
index, | ||
locals = {}; | ||
|
||
// We now build up the list of options we need (we merge later) | ||
for (index = 0; length = keys.length, index < length; index++) { | ||
var value = values[index]; | ||
locals[valueName] = values[keyName ? locals[keyName]=keys[index]:index]; | ||
optionGroupName = groupByFn(scope, locals); | ||
optionGroups.push({ | ||
id: keyName ? keys[index] : index, // either the index into array or key from object | ||
label: displayFn(scope, locals), // what will be seen by the user | ||
optionGroup: optionGroupName | ||
}); | ||
} | ||
return optionGroups; | ||
} | ||
} | ||
}; | ||
}]); | ||
|
||
|
||
})(window.jQuery, window.angular); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
(function(angular) { | ||
// TODO refactor this: Create a config for every jqm widget... | ||
|
||
var ng = angular.module("ng"); | ||
ng.config(["$compileProvider", function($compileProvider) { | ||
$compileProvider.parseSelectorAndRegisterJqmWidget('button', function (scope, iElement, iAttrs) { | ||
disabledHandling('button', scope, iElement, iAttrs); | ||
|
||
}); | ||
$compileProvider.parseSelectorAndRegisterJqmWidget('collapsible', function (scope, iElement, iAttrs) { | ||
disabledHandling('collapsible', scope, iElement, iAttrs); | ||
|
||
}); | ||
|
||
$compileProvider.parseSelectorAndRegisterJqmWidget('textinput', function (scope, iElement, iAttrs) { | ||
disabledHandling('textinput', scope, iElement, iAttrs); | ||
|
||
}); | ||
|
||
$compileProvider.parseSelectorAndRegisterJqmWidget('checkboxradio', function (scope, iElement, iAttrs, ctrls) { | ||
disabledHandling('checkboxradio', scope, iElement, iAttrs); | ||
refreshOnNgModelRender('checkboxradio', iElement, ctrls); | ||
|
||
}); | ||
$compileProvider.parseSelectorAndRegisterJqmWidget('slider', function (scope, iElement, iAttrs, ctrls) { | ||
disabledHandling('slider', scope, iElement, iAttrs); | ||
refreshOnNgModelRender('slider', iElement, ctrls); | ||
|
||
}); | ||
|
||
$compileProvider.parseSelectorAndRegisterJqmWidget('listview', function (scope, iElement, iAttrs, ctrls) { | ||
refreshOnChildrenChange('listview', scope, iElement); | ||
}); | ||
|
||
$compileProvider.parseSelectorAndRegisterJqmWidget('collapsibleset', function (scope, iElement, iAttrs, ctrls) { | ||
refreshOnChildrenChange('collapsibleset', scope, iElement); | ||
}); | ||
|
||
$compileProvider.parseSelectorAndRegisterJqmWidget('selectmenu', function (scope, iElement, iAttrs, ctrls) { | ||
disabledHandling('selectmenu', scope, iElement, iAttrs); | ||
refreshOnNgModelRender('selectmenu', iElement, ctrls); | ||
refreshOnChildrenChange('selectmenu', scope, iElement); | ||
}); | ||
}]); | ||
|
||
function disabledHandling(widget, scope, iElement, iAttrs) { | ||
iAttrs.$observe("disabled", function (value) { | ||
if (value) { | ||
iElement[widget]("disable"); | ||
} else { | ||
iElement[widget]("enable"); | ||
} | ||
}); | ||
} | ||
|
||
function addCtrlFunctionListener(ctrl, ctrlFnName, fn) { | ||
var listenersName = "_listeners"+ctrlFnName; | ||
if (!ctrl[listenersName]) { | ||
ctrl[listenersName] = []; | ||
var oldFn = ctrl[ctrlFnName]; | ||
ctrl[ctrlFnName] = function() { | ||
var res = oldFn.apply(this, arguments); | ||
for (var i=0; i<ctrl[listenersName].length; i++) { | ||
ctrl[listenersName][i](); | ||
} | ||
return res; | ||
}; | ||
} | ||
ctrl[listenersName].push(fn); | ||
} | ||
|
||
function refreshOnNgModelRender(widget, iElement, ctrls) { | ||
var ngModelCtrl = ctrls[0]; | ||
if (ngModelCtrl) { | ||
addCtrlFunctionListener(ngModelCtrl, "$render", function() { | ||
iElement[widget]("refresh"); | ||
}); | ||
} | ||
} | ||
|
||
function refreshOnChildrenChange(widget, scope, iElement) { | ||
scope.$on("$childrenChanged", function() { | ||
triggerRefresh(widget, scope, iElement); | ||
}); | ||
} | ||
|
||
function evalAsync(scope, callback) { | ||
// Note: We cannot use scope.$evalAsync here due to a bug: | ||
// See https://github.com/angular/angular.js/issues/947 | ||
if (!scope._patchedEvalAsync) { | ||
var state = scope._patchedEvalAsync = {changeCount: 0, queue: []}; | ||
scope.$watch('_patchedEvalAsync.changeCount', function() { | ||
while (state.queue.length) { | ||
state.queue.pop()(); | ||
} | ||
}); | ||
} | ||
scope._patchedEvalAsync.queue.push(callback); | ||
scope._patchedEvalAsync.changeCount++; | ||
} | ||
|
||
function triggerRefresh(widget, scope, iElement) { | ||
scope.refreshCount = scope.refreshCount+1 || 1; | ||
evalAsync(scope, function() { | ||
scope.refreshCount--; | ||
if (scope.refreshCount===0) { | ||
iElement[widget]("refresh"); | ||
} | ||
}); | ||
} | ||
|
||
|
||
})(window.angular); |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
(function (angular) { | ||
|
||
var ng = angular.module('ng'); | ||
ng.config(['$provide', function($provide) { | ||
$provide.decorator('$rootScope', ['$delegate', function($rootScope) { | ||
var _$destroy = $rootScope.$destroy; | ||
$rootScope.$destroy = function() { | ||
this.$$destroyed = true; | ||
var res = _$destroy.apply(this, arguments); | ||
this.$$nextSibling = this.$$prevSibling = null; | ||
}; | ||
$rootScope.$reconnect = function() { | ||
var child = this; | ||
if (child===$rootScope) { | ||
// Nothing to do here. | ||
return; | ||
} | ||
if (!child.$$destroyed) { | ||
return; | ||
} | ||
var parent = child.$parent; | ||
child.$$destroyed = false; | ||
// See Scope.$new for this logic... | ||
child.$$prevSibling = parent.$$childTail; | ||
if (parent.$$childHead) { | ||
parent.$$childTail.$$nextSibling = child; | ||
parent.$$childTail = child; | ||
} else { | ||
parent.$$childHead = parent.$$childTail = child; | ||
} | ||
|
||
}; | ||
return $rootScope; | ||
}]); | ||
}]); | ||
})(window.angular); |
Oops, something went wrong.