diff options
| author | Misko Hevery | 2011-10-25 10:30:27 -0700 | 
|---|---|---|
| committer | Misko Hevery | 2011-11-14 16:39:31 -0800 | 
| commit | 93b777c916ccff243c5a6080bf5f39860ac7bf39 (patch) | |
| tree | 65e70f8f767849506983060f52f8c864993777cd /src/Scope.js | |
| parent | 5c70ff72e2d733e1f5deb72d296f118555b0019d (diff) | |
| download | angular.js-93b777c916ccff243c5a6080bf5f39860ac7bf39.tar.bz2 | |
move(scope): appease the History God
-	renamed:    src/Scope.js -> src/service/scope.js
-	renamed:    test/ScopeSpec.js -> test/service/scopeSpec.js
Diffstat (limited to 'src/Scope.js')
| -rw-r--r-- | src/Scope.js | 679 | 
1 files changed, 0 insertions, 679 deletions
| diff --git a/src/Scope.js b/src/Scope.js deleted file mode 100644 index c4b9513b..00000000 --- a/src/Scope.js +++ /dev/null @@ -1,679 +0,0 @@ -'use strict'; - -/** - * DESIGN NOTES - * - * The design decisions behind the scope ware heavily favored for speed and memory consumption. - * - * The typical use of scope is to watch the expressions, which most of the time return the same - * value as last time so we optimize the operation. - * - * Closures construction is expensive from speed as well as memory: - *   - no closures, instead ups prototypical inheritance for API - *   - Internal state needs to be stored on scope directly, which means that private state is - *     exposed as $$____ properties - * - * Loop operations are optimized by using while(count--) { ... } - *   - this means that in order to keep the same order of execution as addition we have to add - *     items to the array at the begging (shift) instead of at the end (push) - * - * Child scopes are created and removed often - *   - Using array would be slow since inserts in meddle are expensive so we use linked list - * - * There are few watches then a lot of observers. This is why you don't want the observer to be - * implemented in the same way as watch. Watch requires return of initialization function which - * are expensive to construct. - */ - - -function createScope(providers, instanceCache) { -  var scope = new Scope(); -  (scope.$service = createInjector(scope, providers, instanceCache)).eager(); -  return scope; -} - - -/** - * @ngdoc function - * @name angular.scope - * - * @description - * A root scope can be created by calling {@link angular.scope angular.scope()}. Child scopes - * are created using the {@link angular.scope.$new $new()} method. - * (Most scopes are created automatically when compiled HTML template is executed.) - * - * Here is a simple scope snippet to show how you can interact with the scope. - * <pre> -       var scope = angular.scope(); -       scope.salutation = 'Hello'; -       scope.name = 'World'; - -       expect(scope.greeting).toEqual(undefined); - -       scope.$watch('name', function() { -         this.greeting = this.salutation + ' ' + this.name + '!'; -       }); // initialize the watch - -       expect(scope.greeting).toEqual(undefined); -       scope.name = 'Misko'; -       // still old value, since watches have not been called yet -       expect(scope.greeting).toEqual(undefined); - -       scope.$digest(); // fire all  the watches -       expect(scope.greeting).toEqual('Hello Misko!'); - * </pre> - * - * # Inheritance - * A scope can inherit from a parent scope, as in this example: - * <pre> -     var parent = angular.scope(); -     var child = parent.$new(); - -     parent.salutation = "Hello"; -     child.name = "World"; -     expect(child.salutation).toEqual('Hello'); - -     child.salutation = "Welcome"; -     expect(child.salutation).toEqual('Welcome'); -     expect(parent.salutation).toEqual('Hello'); - * </pre> - * - * # Dependency Injection - * See {@link guide/dev_guide.di dependency injection}. - * - * - * @param {Object.<string, function()>=} providers Map of service factory which need to be provided - *     for the current scope. Defaults to {@link angular.service}. - * @param {Object.<string, *>=} instanceCache Provides pre-instantiated services which should - *     append/override services provided by `providers`. This is handy when unit-testing and having - *     the need to override a default service. - * @returns {Object} Newly created scope. - * - */ -function Scope() { -  this.$id = nextUid(); -  this.$$phase = this.$parent = this.$$watchers = -                 this.$$nextSibling = this.$$prevSibling = -                 this.$$childHead = this.$$childTail = null; -  this.$destructor = noop; -  this['this'] = this.$root =  this; -  this.$$asyncQueue = []; -  this.$$listeners = {}; -} - -/** - * @ngdoc property - * @name angular.scope.$id - * @returns {number} Unique scope ID (monotonically increasing alphanumeric sequence) useful for - *   debugging. - */ - -/** - * @ngdoc property - * @name angular.scope.$service - * @function - * - * @description - * Provides reference to an instance of {@link angular.injector injector} which can be used to - * retrieve {@link angular.service services}. In general the use of this api is discouraged, - * in favor of proper {@link guide/dev_guide.di dependency injection}. - * - * @returns {function} {@link angular.injector injector} - */ - -/** - * @ngdoc property - * @name angular.scope.$root - * @returns {Scope} The root scope of the current scope hierarchy. - */ - -/** - * @ngdoc property - * @name angular.scope.$parent - * @returns {Scope} The parent scope of the current scope. - */ - - -Scope.prototype = { -  /** -   * @ngdoc function -   * @name angular.scope.$new -   * @function -   * -   * @description -   * Creates a new child {@link angular.scope scope}. The new scope can optionally behave as a -   * controller. The parent scope will propagate the {@link angular.scope.$digest $digest()} and -   * {@link angular.scope.$digest $digest()} events. The scope can be removed from the scope -   * hierarchy using {@link angular.scope.$destroy $destroy()}. -   * -   * {@link angular.scope.$destroy $destroy()} must be called on a scope when it is desired for -   * the scope and its child scopes to be permanently detached from the parent and thus stop -   * participating in model change detection and listener notification by invoking. -   * -   * @param {function()=} Class Constructor function which the scope should be applied to the scope. -   * @param {...*} curryArguments Any additional arguments which are curried into the constructor. -   *        See {@link guide/dev_guide.di dependency injection}. -   * @returns {Object} The newly created child scope. -   * -   */ -  $new: function(Class, curryArguments) { -    var Child = function() {}; // should be anonymous; This is so that when the minifier munges -      // the name it does not become random set of chars. These will then show up as class -      // name in the debugger. -    var child; -    Child.prototype = this; -    child = new Child(); -    child['this'] = child; -    child.$$listeners = {}; -    child.$parent = this; -    child.$id = nextUid(); -    child.$$asyncQueue = []; -    child.$$phase = child.$$watchers = -      child.$$nextSibling = child.$$childHead = child.$$childTail = null; -    child.$$prevSibling = this.$$childTail; -    if (this.$$childHead) { -      this.$$childTail.$$nextSibling = child; -      this.$$childTail = child; -    } else { -      this.$$childHead = this.$$childTail = child; -    } -    // short circuit if we have no class -    if (Class) { -      // can't use forEach, we need speed! -      var ClassPrototype = Class.prototype; -      for(var key in ClassPrototype) { -        child[key] = bind(child, ClassPrototype[key]); -      } -      this.$service.invoke(child, Class, curryArguments); -    } -    return child; -  }, - -  /** -   * @ngdoc function -   * @name angular.scope.$watch -   * @function -   * -   * @description -   * Registers a `listener` callback to be executed whenever the `watchExpression` changes. -   * -   * - The `watchExpression` is called on every call to {@link angular.scope.$digest $digest()} and -   *   should return the value which will be watched. (Since {@link angular.scope.$digest $digest()} -   *   reruns when it detects changes the `watchExpression` can execute multiple times per -   *   {@link angular.scope.$digest $digest()} and should be idempotent.) -   * - The `listener` is called only when the value from the current `watchExpression` and the -   *   previous call to `watchExpression' are not equal. The inequality is determined according to -   *   {@link angular.equals} function. To save the value of the object for later comparison -   *   {@link angular.copy} function is used. It also means that watching complex options will -   *   have adverse memory and performance implications. -   * - The watch `listener` may change the model, which may trigger other `listener`s to fire. This -   *   is achieved by rerunning the watchers until no changes are detected. The rerun iteration -   *   limit is 100 to prevent infinity loop deadlock. -   * -   * -   * If you want to be notified whenever {@link angular.scope.$digest $digest} is called, -   * you can register an `watchExpression` function with no `listener`. (Since `watchExpression`, -   * can execute multiple times per {@link angular.scope.$digest $digest} cycle when a change is -   * detected, be prepared for multiple calls to your listener.) -   * -   * -   * # Example -     <pre> -       var scope = angular.scope(); -       scope.name = 'misko'; -       scope.counter = 0; - -       expect(scope.counter).toEqual(0); -       scope.$watch('name', function(scope, newValue, oldValue) { counter = counter + 1; }); -       expect(scope.counter).toEqual(0); - -       scope.$digest(); -       // no variable change -       expect(scope.counter).toEqual(0); - -       scope.name = 'adam'; -       scope.$digest(); -       expect(scope.counter).toEqual(1); -     </pre> -   * -   * -   * -   * @param {(function()|string)} watchExpression Expression that is evaluated on each -   *    {@link angular.scope.$digest $digest} cycle. A change in the return value triggers a -   *    call to the `listener`. -   * -   *    - `string`: Evaluated as {@link guide/dev_guide.expressions expression} -   *    - `function(scope)`: called with current `scope` as a parameter. -   * @param {(function()|string)=} listener Callback called whenever the return value of -   *   the `watchExpression` changes. -   * -   *    - `string`: Evaluated as {@link guide/dev_guide.expressions expression} -   *    - `function(scope, newValue, oldValue)`: called with current `scope` an previous and -   *       current values as parameters. -   * @returns {function()} Returns a deregistration function for this listener. -   */ -  $watch: function(watchExp, listener) { -    var scope = this, -        get = compileToFn(watchExp, 'watch'), -        listenFn = compileToFn(listener || noop, 'listener'), -        array = scope.$$watchers, -        watcher = { -          fn: listenFn, -          last: Number.NaN, // NaN !== NaN. We used this to force $watch to fire on first run. -          get: get, -          exp: watchExp -        }; - -    if (!array) { -      array = scope.$$watchers = []; -    } -    // we use unshift since we use a while loop in $digest for speed. -    // the while loop reads in reverse order. -    array.unshift(watcher); - -    return function() { -      angularArray.remove(array, watcher); -    }; -  }, - -  /** -   * @ngdoc function -   * @name angular.scope.$digest -   * @function -   * -   * @description -   * Process all of the {@link angular.scope.$watch watchers} of the current scope and its children. -   * Because a {@link angular.scope.$watch watcher}'s listener can change the model, the -   * `$digest()` keeps calling the {@link angular.scope.$watch watchers} until no more listeners are -   * firing. This means that it is possible to get into an infinite loop. This function will throw -   * `'Maximum iteration limit exceeded.'` if the number of iterations exceeds 100. -   * -   * Usually you don't call `$digest()` directly in -   * {@link angular.directive.ng:controller controllers} or in {@link angular.directive directives}. -   * Instead a call to {@link angular.scope.$apply $apply()} (typically from within a -   * {@link angular.directive directive}) will force a `$digest()`. -   * -   * If you want to be notified whenever `$digest()` is called, -   * you can register a `watchExpression` function  with {@link angular.scope.$watch $watch()} -   * with no `listener`. -   * -   * You may have a need to call `$digest()` from within unit-tests, to simulate the scope -   * life-cycle. -   * -   * # Example -     <pre> -       var scope = angular.scope(); -       scope.name = 'misko'; -       scope.counter = 0; - -       expect(scope.counter).toEqual(0); -       scope.$digest('name', function(scope, newValue, oldValue) { counter = counter + 1; }); -       expect(scope.counter).toEqual(0); - -       scope.$digest(); -       // no variable change -       expect(scope.counter).toEqual(0); - -       scope.name = 'adam'; -       scope.$digest(); -       expect(scope.counter).toEqual(1); -     </pre> -   * -   */ -  $digest: function() { -    var watch, value, last, -        watchers, -        asyncQueue, -        length, -        dirty, ttl = 100, -        next, current, target = this, -        watchLog = []; - -    if (target.$$phase) { -      throw Error(target.$$phase + ' already in progress'); -    } -    do { - -      dirty = false; -      current = target; -      do { -        current.$$phase = '$digest'; -        asyncQueue = current.$$asyncQueue; -        while(asyncQueue.length) { -          try { -            current.$eval(asyncQueue.shift()); -          } catch (e) { -            current.$service('$exceptionHandler')(e); -          } -        } -        if ((watchers = current.$$watchers)) { -          // process our watches -          length = watchers.length; -          while (length--) { -            try { -              watch = watchers[length]; -              // Most common watches are on primitives, in which case we can short -              // circuit it with === operator, only when === fails do we use .equals -              if ((value = watch.get(current)) !== (last = watch.last) && !equals(value, last)) { -                dirty = true; -                watch.last = copy(value); -                watch.fn(current, value, last); -                if (ttl < 5) { -                  if (!watchLog[4-ttl]) watchLog[4-ttl] = []; -                  if (isFunction(watch.exp)) { -                    watchLog[4-ttl].push('fn: ' + (watch.exp.name || watch.exp.toString())); -                  } else { -                    watchLog[4-ttl].push(watch.exp); -                  } -                } -              } -            } catch (e) { -              current.$service('$exceptionHandler')(e); -            } -          } -        } - -        current.$$phase = null; - -        // Insanity Warning: scope depth-first traversal -        // yes, this code is a bit crazy, but it works and we have tests to prove it! -        // this piece should be kept in sync with the traversal in $broadcast -        if (!(next = (current.$$childHead || (current !== target && current.$$nextSibling)))) { -          while(current !== target && !(next = current.$$nextSibling)) { -            current = current.$parent; -          } -        } -      } while ((current = next)); - -      if(!(ttl--)) { -        throw Error('100 $digest() iterations reached. Aborting!\n' + -            'Watchers fired in the last 5 iterations: ' + toJson(watchLog)); -      } -    } while (dirty); -  }, - -  /** -   * @ngdoc function -   * @name angular.scope.$destroy -   * @function -   * -   * @description -   * Remove the current scope (and all of its children) from the parent scope. Removal implies -   * that calls to {@link angular.scope.$digest $digest()} will no longer propagate to the current -   * scope and its children. Removal also implies that the current scope is eligible for garbage -   * collection. -   * -   * The destructing scope emits an `$destroy` {@link angular.scope.$emit event}. -   * -   * The `$destroy()` is usually used by directives such as -   * {@link angular.widget.@ng:repeat ng:repeat} for managing the unrolling of the loop. -   * -   */ -  $destroy: function() { -    if (this.$root == this) return; // we can't remove the root node; -    this.$emit('$destroy'); -    var parent = this.$parent; - -    if (parent.$$childHead == this) parent.$$childHead = this.$$nextSibling; -    if (parent.$$childTail == this) parent.$$childTail = this.$$prevSibling; -    if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling; -    if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling; -  }, - -  /** -   * @ngdoc function -   * @name angular.scope.$eval -   * @function -   * -   * @description -   * Executes the `expression` on the current scope returning the result. Any exceptions in the -   * expression are propagated (uncaught). This is useful when evaluating engular expressions. -   * -   * # Example -     <pre> -       var scope = angular.scope(); -       scope.a = 1; -       scope.b = 2; - -       expect(scope.$eval('a+b')).toEqual(3); -       expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3); -     </pre> -   * -   * @param {(string|function())=} expression An angular expression to be executed. -   * -   *    - `string`: execute using the rules as defined in  {@link guide/dev_guide.expressions expression}. -   *    - `function(scope)`: execute the function with the current `scope` parameter. -   * -   * @returns {*} The result of evaluating the expression. -   */ -  $eval: function(expr) { -    var fn = isString(expr) -      ? expressionCompile(expr) -      : expr || noop; -    return fn(this); -  }, - -  /** -   * @ngdoc function -   * @name angular.scope.$evalAsync -   * @function -   * -   * @description -   * Executes the expression on the current scope at a later point in time. -   * -   * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only that: -   * -   *   - it will execute in the current script execution context (before any DOM rendering). -   *   - at least one {@link angular.scope.$digest $digest cycle} will be performed after -   *     `expression` execution. -   * -   * Any exceptions from the execution of the expression are forwarded to the -   * {@link angular.service.$exceptionHandler $exceptionHandler} service. -   * -   * @param {(string|function())=} expression An angular expression to be executed. -   * -   *    - `string`: execute using the rules as defined in  {@link guide/dev_guide.expressions expression}. -   *    - `function(scope)`: execute the function with the current `scope` parameter. -   * -   */ -  $evalAsync: function(expr) { -    this.$$asyncQueue.push(expr); -  }, - -  /** -   * @ngdoc function -   * @name angular.scope.$apply -   * @function -   * -   * @description -   * `$apply()` is used to execute an expression in angular from outside of the angular framework. -   * (For example from browser DOM events, setTimeout, XHR or third party libraries). -   * Because we are calling into the angular framework we need to perform proper scope life-cycle -   * of {@link angular.service.$exceptionHandler exception handling}, -   * {@link angular.scope.$digest executing watches}. -   * -   * ## Life cycle -   * -   * # Pseudo-Code of `$apply()` -      function $apply(expr) { -        try { -          return $eval(expr); -        } catch (e) { -          $exceptionHandler(e); -        } finally { -          $root.$digest(); -        } -      } -   * -   * -   * Scope's `$apply()` method transitions through the following stages: -   * -   * 1. The {@link guide/dev_guide.expressions expression} is executed using the -   *    {@link angular.scope.$eval $eval()} method. -   * 2. Any exceptions from the execution of the expression are forwarded to the -   *    {@link angular.service.$exceptionHandler $exceptionHandler} service. -   * 3. The {@link angular.scope.$watch watch} listeners are fired immediately after the expression -   *    was executed using the {@link angular.scope.$digest $digest()} method. -   * -   * -   * @param {(string|function())=} exp An angular expression to be executed. -   * -   *    - `string`: execute using the rules as defined in {@link guide/dev_guide.expressions expression}. -   *    - `function(scope)`: execute the function with current `scope` parameter. -   * -   * @returns {*} The result of evaluating the expression. -   */ -  $apply: function(expr) { -    try { -      return this.$eval(expr); -    } catch (e) { -      this.$service('$exceptionHandler')(e); -    } finally { -      this.$root.$digest(); -    } -  }, - -  /** -   * @ngdoc function -   * @name angular.scope.$on -   * @function -   * -   * @description -   * Listen on events of a given type. See {@link angular.scope.$emit $emit} for discussion of -   * event life cycle. -   * -   * @param {string} name Event name to listen on. -   * @param {function(event)} listener Function to call when the event is emitted. -   * @returns {function()} Returns a deregistration function for this listener. -   * -   * The event listener function format is: `function(event)`. The `event` object passed into the -   * listener has the following attributes -   *   - `targetScope` - {Scope}: the scope on which the event was `$emit`-ed or `$broadcast`-ed. -   *   - `currentScope` - {Scope}: the current scope which is handling the event. -   *   - `name` - {string}: Name of the event. -   *   - `cancel` - {function=}: calling `cancel` function will cancel further event propagation -   *     (available only for events that were `$emit`-ed). -   */ -  $on: function(name, listener) { -    var namedListeners = this.$$listeners[name]; -    if (!namedListeners) { -      this.$$listeners[name] = namedListeners = []; -    } -    namedListeners.push(listener); - -    return function() { -      angularArray.remove(namedListeners, listener); -    }; -  }, - - -  /** -   * @ngdoc function -   * @name angular.scope.$emit -   * @function -   * -   * @description -   * Dispatches an event `name` upwards through the scope hierarchy notifying the -   * registered {@link angular.scope.$on} listeners. -   * -   * The event life cycle starts at the scope on which `$emit` was called. All -   * {@link angular.scope.$on listeners} listening for `name` event on this scope get notified. -   * Afterwards, the event traverses upwards toward the root scope and calls all registered -   * listeners along the way. The event will stop propagating if one of the listeners cancels it. -   * -   * Any exception emmited from the {@link angular.scope.$on listeners} will be passed -   * onto the {@link angular.service.$exceptionHandler $exceptionHandler} service. -   * -   * @param {string} name Event name to emit. -   * @param {...*} args Optional set of arguments which will be passed onto the event listeners. -   */ -  $emit: function(name, args) { -    var empty = [], -        namedListeners, -        canceled = false, -        scope = this, -        event = { -          name: name, -          targetScope: scope, -          cancel: function() {canceled = true;} -        }, -        listenerArgs = concat([event], arguments, 1), -        i, length; - -    do { -      namedListeners = scope.$$listeners[name] || empty; -      event.currentScope = scope; -      for (i=0, length=namedListeners.length; i<length; i++) { -        try { -          namedListeners[i].apply(null, listenerArgs); -          if (canceled) return; -        } catch (e) { -          scope.$service('$exceptionHandler')(e); -        } -      } -      //traverse upwards -      scope = scope.$parent; -    } while (scope); -  }, - - -  /** -   * @ngdoc function -   * @name angular.scope.$broadcast -   * @function -   * -   * @description -   * Dispatches an event `name` downwards to all child scopes (and their children) notifying the -   * registered {@link angular.scope.$on} listeners. -   * -   * The event life cycle starts at the scope on which `$broadcast` was called. All -   * {@link angular.scope.$on listeners} listening for `name` event on this scope get notified. -   * Afterwards, the event propagates to all direct and indirect scopes of the current scope and -   * calls all registered listeners along the way. The event cannot be canceled. -   * -   * Any exception emmited from the {@link angular.scope.$on listeners} will be passed -   * onto the {@link angular.service.$exceptionHandler $exceptionHandler} service. -   * -   * @param {string} name Event name to emit. -   * @param {...*} args Optional set of arguments which will be passed onto the event listeners. -   */ -  $broadcast: function(name, args) { -    var target = this, -        current = target, -        next = target, -        event = { name: name, -                  targetScope: target }, -        listenerArgs = concat([event], arguments, 1); - -    //down while you can, then up and next sibling or up and next sibling until back at root -    do { -      current = next; -      event.currentScope = current; -      forEach(current.$$listeners[name], function(listener) { -        try { -          listener.apply(null, listenerArgs); -        } catch(e) { -          current.$service('$exceptionHandler')(e); -        } -      }); - -      // Insanity Warning: scope depth-first traversal -      // yes, this code is a bit crazy, but it works and we have tests to prove it! -      // this piece should be kept in sync with the traversal in $digest -      if (!(next = (current.$$childHead || (current !== target && current.$$nextSibling)))) { -        while(current !== target && !(next = current.$$nextSibling)) { -          current = current.$parent; -        } -      } -    } while ((current = next)); -  } -}; - - -function compileToFn(exp, name) { -  var fn = isString(exp) -    ? expressionCompile(exp) -    : exp; -  assertArgFn(fn, name); -  return fn; -} | 
