Skip to content

Commit

Permalink
performace refactoring: intermediate commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Tobias Bosch committed May 9, 2012
1 parent f1f5b07 commit 664f29d
Show file tree
Hide file tree
Showing 9 changed files with 545 additions and 66 deletions.
26 changes: 0 additions & 26 deletions src/main/webapp/integration/angularNgModel.js

This file was deleted.

142 changes: 142 additions & 0 deletions src/main/webapp/integration/angularNgOptions.js
@@ -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);
113 changes: 113 additions & 0 deletions src/main/webapp/integration/jqmAngularWidgets.js
@@ -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);
21 changes: 0 additions & 21 deletions src/main/webapp/integration/jqmWidgets.js

This file was deleted.

36 changes: 36 additions & 0 deletions src/main/webapp/integration/scopeEvents.js
@@ -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);

0 comments on commit 664f29d

Please sign in to comment.