diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/Browser.js | 15 | ||||
| -rw-r--r-- | src/Compiler.js | 1 | ||||
| -rw-r--r-- | src/Injector.js | 10 | ||||
| -rw-r--r-- | src/directives.js | 12 | ||||
| -rw-r--r-- | src/scenario/Scenario.js | 2 | ||||
| -rw-r--r-- | src/services.js | 62 | ||||
| -rw-r--r-- | src/validators.js | 2 | ||||
| -rw-r--r-- | src/widgets.js | 22 |
8 files changed, 95 insertions, 31 deletions
diff --git a/src/Browser.js b/src/Browser.js index c93f115c..377c740c 100644 --- a/src/Browser.js +++ b/src/Browser.js @@ -136,9 +136,7 @@ function Browser(window, document, body, XHR, $log) { * @methodOf angular.service.$browser */ self.poll = function() { - foreach(pollFns, function(pollFn){ - pollFn(); - }); + foreach(pollFns, function(pollFn){ pollFn(); }); }; /** @@ -319,22 +317,23 @@ function Browser(window, document, body, XHR, $log) { /** * @workInProgress - * @ngdoc + * @ngdoc method * @name angular.service.$browser#defer * @methodOf angular.service.$browser + * @param {function()} fn A function, who's execution should be defered. + * @param {int=} [delay=0] of milliseconds to defer the function execution. * * @description - * Executes a fn asynchroniously via `setTimeout(fn, 0)`. + * Executes a fn asynchroniously via `setTimeout(fn, delay)`. * * Unlike when calling `setTimeout` directly, in test this function is mocked and instead of using * `setTimeout` in tests, the fns are queued in an array, which can be programaticaly flushed via * `$browser.defer.flush()`. * - * @param {function()} fn A function, who's execution should be defered. */ - self.defer = function(fn) { + self.defer = function(fn, delay) { outstandingRequestCount++; - setTimeout(function() { completeOutstandingRequest(fn); }, 0); + setTimeout(function() { completeOutstandingRequest(fn); }, delay || 0); }; ////////////////////////////////////////////////////////////// diff --git a/src/Compiler.js b/src/Compiler.js index eea36263..a18341f4 100644 --- a/src/Compiler.js +++ b/src/Compiler.js @@ -74,6 +74,7 @@ Template.prototype = { */ function retrieveScope(element) { var scope; + element = jqLite(element); while (element && !(scope = element.data($$scope))) { element = element.parent(); } diff --git a/src/Injector.js b/src/Injector.js index 78858e86..f6cb897f 100644 --- a/src/Injector.js +++ b/src/Injector.js @@ -67,4 +67,12 @@ function createInjector(providerScope, providers, cache) { } return returnValue; }; -}
\ No newline at end of file +} + +function injectService(services, fn) { + return extend(fn, {$inject:services});; +} + +function injectUpdateView(fn) { + return injectService(['$updateView'], fn); +} diff --git a/src/directives.js b/src/directives.js index d40d6120..bf215cc8 100644 --- a/src/directives.js +++ b/src/directives.js @@ -423,14 +423,14 @@ angularDirective("ng:bind-attr", function(expression){ * TODO: maybe we should consider allowing users to control event propagation in the future. */ angularDirective("ng:click", function(expression, element){ - return function(element){ + return injectUpdateView(function($updateView, element){ var self = this; element.bind('click', function(event){ self.$tryEval(expression, element); - self.$root.$eval(); + $updateView(); event.stopPropagation(); }); - }; + }); }); @@ -471,14 +471,14 @@ angularDirective("ng:click", function(expression, element){ * server and reloading the current page). */ angularDirective("ng:submit", function(expression, element) { - return function(element) { + return injectUpdateView(function($updateView, element) { var self = this; element.bind('submit', function(event) { self.$tryEval(expression, element); - self.$root.$eval(); + $updateView(); event.preventDefault(); }); - }; + }); }); diff --git a/src/scenario/Scenario.js b/src/scenario/Scenario.js index 1723412e..a2926b5b 100644 --- a/src/scenario/Scenario.js +++ b/src/scenario/Scenario.js @@ -285,7 +285,7 @@ function browserTrigger(element, type) { (function(fn){ var parentTrigger = fn.trigger; fn.trigger = function(type) { - if (/(click|change|keyup)/.test(type)) { + if (/(click|change|keydown)/.test(type)) { return this.each(function(index, node) { browserTrigger(node, type); }); diff --git a/src/services.js b/src/services.js index 91bd226d..1a2aada6 100644 --- a/src/services.js +++ b/src/services.js @@ -408,6 +408,62 @@ angularServiceInject('$exceptionHandler', function($log){ /** * @workInProgress * @ngdoc service + * @name angular.service.$updateView + * @requires $browser + * + * @description + * Calling `$updateView` enqueues the eventual update of the view. (Update the DOM to reflect the + * model). The update is eventual, since there are often multiple updates to the model which may + * be deferred. The default update delayed is 25 ms. This means that the view lags the model by + * that time. (25ms is small enough that it is perceived as instantaneous by the user). The delay + * can be adjusted by setting the delay property of the service. + * + * <pre>angular.service('$updateView').delay = 10</pre> + * + * The delay is there so that multiple updates to the model which occur sufficiently close + * together can be merged into a single update. + * + * You don't usually call '$updateView' directly since angular does it for you in most cases, + * but there are some cases when you need to call it. + * + * - `$updateView()` called automatically by angular: + * - Your Application Controllers: Your controller code is called by angular and hence + * angular is aware that you may have changed the model. + * - Your Services: Your service is usually called by your controller code, hence same rules + * apply. + * - May need to call `$updateView()` manually: + * - Widgets / Directives: If you listen to any DOM events or events on any third party + * libraries, then angular is not aware that you may have changed state state of the + * model, and hence you need to call '$updateView()' manually. + * - 'setTimeout'/'XHR': If you call 'setTimeout' (instead of {@link angular.service.$defer}) + * or 'XHR' (instead of {@link angular.service.$xhr}) then you may be changing the model + * without angular knowledge and you may need to call '$updateView()' directly. + * + * NOTE: if you wish to update the view immediately (without delay), you can do so by calling + * {@link scope.$eval} at any time from your code: + * <pre>scope.$root.$eval()</pre> + * + * In unit-test mode the update is instantaneous and synchronous to simplify writing tests. + * + */ +angularServiceInject('$updateView', extend(function factory($browser){ + var rootScope = this; + var scheduled; + function update(){ + scheduled = false; + rootScope.$eval(); + } + return $browser.isMock ? update : function(){ + if (!scheduled) { + scheduled = true; + $browser.defer(update, factory.delay); + } + }; +}, {delay:25}), ['$browser']); + +/** + * @workInProgress + * @ngdoc service * @name angular.service.$hover * @requires $browser * @requires $document @@ -815,7 +871,7 @@ angularServiceInject('$xhr.bulk', function($xhr, $error, $log){ * * @param {function()} fn A function, who's execution should be deferred. */ -angularServiceInject('$defer', function($browser, $exceptionHandler) { +angularServiceInject('$defer', function($browser, $exceptionHandler, $updateView) { var scope = this; return function(fn) { @@ -825,11 +881,11 @@ angularServiceInject('$defer', function($browser, $exceptionHandler) { } catch(e) { $exceptionHandler(e); } finally { - scope.$eval(); + $updateView(); } }); }; -}, ['$browser', '$exceptionHandler']); +}, ['$browser', '$exceptionHandler', '$updateView']); /** diff --git a/src/validators.js b/src/validators.js index 3de98d61..8030c0d0 100644 --- a/src/validators.js +++ b/src/validators.js @@ -398,7 +398,7 @@ extend(angularValidator, { $invalidWidgets.markValid(element); } element.data($$validate)(); - scope.$root.$eval(); + scope.$service('$updateView')(); }); } else if (inputState.inFlight) { // request in flight, mark widget invalid, but don't show it to user diff --git a/src/widgets.js b/src/widgets.js index 05979281..5ff4a28f 100644 --- a/src/widgets.js +++ b/src/widgets.js @@ -376,7 +376,7 @@ function optionsAccessor(scope, element) { function noopAccessor() { return { get: noop, set: noop }; } -var textWidget = inputWidget('keyup change', modelAccessor, valueAccessor, initWidgetValue(), true), +var textWidget = inputWidget('keydown change', modelAccessor, valueAccessor, initWidgetValue(), true), buttonWidget = inputWidget('click', noopAccessor, noopAccessor, noop), INPUT_TYPE = { 'text': textWidget, @@ -454,8 +454,8 @@ function radioInit(model, view, element) { expect(binding('checkboxCount')).toBe('1'); }); */ -function inputWidget(events, modelAccessor, viewAccessor, initFn, dirtyChecking) { - return function(element) { +function inputWidget(events, modelAccessor, viewAccessor, initFn, textBox) { + return injectService(['$updateView', '$defer'], function($updateView, $defer, element) { var scope = this, model = modelAccessor(scope, element), view = viewAccessor(scope, element), @@ -464,25 +464,25 @@ function inputWidget(events, modelAccessor, viewAccessor, initFn, dirtyChecking) if (model) { initFn.call(scope, model, view, element); 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 (){ + element.bind(events, function(event){ + function handler(){ var value = view.get(); - if (!dirtyChecking || value != lastValue) { + if (!textBox || value != lastValue) { model.set(value); lastValue = model.get(); scope.$tryEval(action, element); - scope.$root.$eval(); + $updateView(); } - }); - } + } + event.type == 'keydown' ? $defer(handler) : handler(); + }); scope.$watch(model.get, function(value){ if (lastValue !== value) { view.set(lastValue = value); } }); } - }; + }); } function inputWidgetSelector(element){ |
