forked from scalyr/angular
-
Notifications
You must be signed in to change notification settings - Fork 0
/
scalyr.js
924 lines (853 loc) · 35.6 KB
/
scalyr.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
/**
* @license scalyr v1.0
* (c) 2013 Scalyr, Inc. http://scalyr.com
* License: MIT
*/
// You may just depend on the 'sly' module to pull in all of the
// dependencies.
angular.module('sly', ['slyEvaluate', 'slyRepeat']);
/**
* @fileoverview
* Defines core functions used throughout the Scalyr javascript
* code base. This file is included on every page.
*
* @author Steven Czerwinski <[email protected]>
*/
/**
* @param {Object} value The value to check
* @returns {Boolean} True if value is an Array
*/
function isArray(value) {
return Object.prototype.toString.call(value) === '[object Array]';
}
/**
* @param {*} value The value to check
* @returns {Boolean} True if value is a Boolean
*/
function isBoolean(value) {
return typeof value == 'boolean';
}
/**
* @param {Object} value The value to check
* @returns {Boolean} True if value is a Date object
*/
function isDate(value) {
return Object.prototype.toString.call(value) === '[object Date]';
}
/**
* @param {*} value The value to check
* @returns {Boolean} True if value is undefined
*/
function isDefined(value) {
return typeof value != 'undefined';
}
/**
* @param {*} value The value to check
* @returns {Boolean} True if value is a Function
*/
function isFunction(value) {
return typeof value == 'function';
}
/**
* @param {*} value The value to check
* @returns {Boolean} True if value is null
*/
function isNull(value) {
return value === null;
}
/**
* @param {*} value The value to check
* @returns {Boolean} True if value is a Number
*/
function isNumber(value) {
return typeof value == 'number';
}
/**
* @param {*} value The value to check
* @returns {Boolean} True if value is an Object, not including null
*/
function isObject(value) {
return value !== null && typeof value == 'object';
}
/**
* @param {*} value The value to check
* @returns {Boolean} True if value is a string
*/
function isString(value) {
return typeof value == 'string';
}
/**
* @param {*} value The value to check
* @returns {Boolean} True if value is undefined
*/
function isUndefined(value) {
return typeof value == 'undefined';
}
/**
* Converts a String or Boolean value to a Boolean.
*
* @param {String|Boolean} value The value to convert
* @returns {Boolean} Returns true for any String that is not
* null, empty String, or 'false'. If value is a Boolean,
* returns value
*/
function convertToBoolean(value) {
if (isBoolean(value))
return value;
return value !== null && value !== '' && value !== 'false';
}
/**
* Determines if obj has a property named prop.
*
* @param {Object} obj The object to check
* @returns {Boolean} Returns true if obj has a property named
* prop. Only considers the object's own properties
*/
function hasProperty(obj, prop) {
return obj.hasOwnProperty(prop);
}
/**
* @param {*} value The value to check
* @returns {Boolean} Returns true if value is a String
* and has zero length, or if null or undefined
*/
function isStringEmpty(value) {
return isNull(value) || isUndefined(value) ||
(isString(value) && (value.length == 0));
}
/**
* @param {*} value The value to check
* @returns {Boolean} Returns true if value is a String
* and has non-zero length
*/
function isStringNonempty(value) {
return isString(value) && (value.length > 0);
}
/**
* Returns input with the first letter capitalized.
* The input may not be zero length.
*
* @param {String} input The String to capitalize.
* @returns {String} Returns input with the first letter
* capitalized.
*/
function upperCaseFirstLetter(input) {
return input.charAt(0).toUpperCase() + input.slice(1);
}
/**
* Returns true if obj1 and obj2 are equal. This should
* only be used for Arrays, Objects, and value types. This is a deep
* comparison, comparing each property and recursive property to
* be equal (not just ===).
*
* Two Objects or values are considered equivalent if at least one of the following is true:
* - Both objects or values pass `===` comparison.
* - Both objects or values are of the same type and all of their properties pass areEqual
* comparison.
* - Both values are NaN. (In JavasScript, NaN == NaN => false. But we consider two NaN as equal).
*
* Note, during property comparision, properties with function values are ignores as are property
* names beginning with '$'.
*
* See angular.equal for more details.
*
* @param {Object|Array|value} obj1 The first object
* @param {Object|Array|value} obj2 The second object
* @returns {Boolean} True if the two objects are equal using a deep
* comparison.
*/
function areEqual(obj1, obj2) {
return angular.equals(obj1, obj2);
}
/**
* @param {Number} a The first Number
* @param {Number} b The second Number
* @returns {Number} The minimum of a and b
*/
function min(a, b) {
return a < b ? a : b;
}
/**
* @param {Number} a The first Number
* @param {Number} b The second Number
* @returns {Number} The maximum of a and b
*/
function max(a, b) {
return a > b ? a : b;
}
/**
* Returns true if the specified String begins with prefix.
*
* @param {*} input The input to check
@ @param {String} prefix The prefix
* @returns {Boolean} True if input is a string that begins with prefix
*/
function beginsWith(input, prefix) {
return isString(input) && input.lastIndexOf(prefix, 0) == 0;
}
/**
* Returns true if the specified String ends with prefix.
*
* @param {*} input The input to check
@ @param {String} postfix The postfix
* @returns {Boolean} True if input is a string that ends with postfix
*/
function endsWith(input, postfix) {
return isString(input) && input.indexOf(postfix, input.length - postfix.length) !== -1;
}
/**
* Returns a deep copy of source, where source can be an Object or an Array. If a destination is
* provided, all of its elements (for Array) or properties (for Objects) are deleted and then all
* elements/properties from the source are copied to it. If source is not an Object or Array,
* source is returned.
*
* See angular.copy for more details.
* @param {Object|Array} source The source
* @param {Object|Array} destination Optional object to copy the elements to
* @returns {Object|Array} The deep copy of source
*/
function copy(source, destination) {
return angular.copy(source, destination);
}
/**
* Removes property from obj.
*
* @param {Object} obj The object
* @param {String} property The property name to delete
*/
function removeProperty(obj, property) {
delete obj[property];
}
/**
* Removes all properties in the array from obj.
*
* @param {Object} obj The object
* @param {Array} properties The properties to remove
*/
function removeProperties(obj, properties) {
for (var i = 0; i < properties.length; ++i)
delete obj[properties[i]];
}
/**
* Invokes the iterator function once for each item in obj collection, which can be either
* an Object or an Array. The iterator function is invoked with iterator(value, key),
* where value is the value of an object property or an array element and key is the
* object property key or array element index. Specifying a context for the function is
* optional. If specified, it becomes 'this' when iterator function is invoked.
*
* See angular.forEach for more details.
*
* @param {Object|Array} The Object or Array over which to iterate
* @param {Function} iterator The iterator function to invoke
* @param {Object} context The value to set for 'this' when invoking the
* iterator function. This is optional
*/
function forEach(obj, iterator, context) {
return angular.forEach(obj, iterator, context);
}
/**
* Used to define a Scalyr javascript library and optionally declare
* dependencies on other libraries. All javascript code not defined in
* this file should be defined as part of a library.
*
* The first argument is the name to call the library. The second argument
* is either a Constructor object for the library or an array where the last
* element is the Constructor for the library and the first to N-1 are string
* names of the libraries this one depends on. If you do declare dependencies,
* the libraries are passed in the Constructor create method in the same order
* as the strings are defined.
*
* Example:
* defineScalyrJsLibrary('myUtils', function() {
* var fooFunction = function(a, b) {
* return a + b;
* };
* return {
* foo: fooFunction
* };
* });
*
* defineScalyrJsLibrary('anotherUtils', [ 'myUtils', function(myUtils) {
* var barFunction = function(a, b) {
* return myUtils.foo(a, b);
* };
* return {
* bar: barFunction
* };
* });
*
* @param {String} libraryName The name for the library
* @param {Constructor|Array} libraryExporter The exporter for the
* library. See above for details
*/
function defineScalyrJsLibrary(libraryName, libraryExporter) {
var moduleDependencies = [];
if (libraryExporter instanceof Array) {
for (var i = 0; i < libraryExporter.length - 1; ++i)
moduleDependencies.push(libraryExporter[i]);
}
return angular.module(libraryName, moduleDependencies)
.factory(libraryName, libraryExporter);
}
/**
* Similar to defineScalyrJsLibary but instead of declaring
* a purely javascript library, this declares an Angular module
* library. The moduleName should be a string used to identify
* this module. The dependencies is an array with the string
* names of Angular modules, Scalyr Angular modules, or Scalyr
* javascript libraries to depend on. The returned object
* can be used to define directives, etc similar to angular.module.
*
* Example:
* defineScalyrAngularModule('slyMyModule', [ 'myTextUtils'])
* .filter('camelCase', function(myTextUtils) {
* return function(input) {
* return myTextUtils.camelCase(input);
* };
* });
*
* @param {String} moduleName The name of the module
* @param {Array} dependencies The names of modules to depend on
*/
function defineScalyrAngularModule(moduleName, dependencies) {
return angular.module(moduleName, dependencies);
}
/**
* @fileoverview
* Module: slyEvaluate
*
* Defines several directives related to preventing evaluating watchers
* on scopes under certain conditions. Here's a list of the directives
* and brief descriptions. See down below for more details.
*
* slyEvaluateOnlyWhen: A directive that prevents updating / evaluating
* all bindings for the current element and its children unless
* the expression has changed values. It currently assumes the
* expression evaluates to an object and detects changes only by
* a change in object reference.
*
* slyAlwaysEvaluate: Can only be used in conjunction with the
* slyEvaluateOnlyWhen directive. This directive will ensure that
* any expression that is being watched will always be evaluated
* if it contains the specified string (i.e., it will ignore whether
* or not the slyEvaluateOnlyWhen expression has changed.) This
* is useful when you wish to check some expressions all the time.
*
* slyPreventEvaluationWhenHidden: Will only evaluate the bindings
* for the current element and its children if the current element
* is not hidden (detected by the element having the ng-hide CSS class.)
*
* slyShow: Will hide the element if the expression evaluates to false.
* Uses ng-hide to hide the element. This is almost exactly the same
* as ngShow, but it has the advantage that it works better with
* slyPreventEvaluationWhenHidden by guaranteeing it will always evaluate
* its show expression to determine if it should or should not be hidden.
*/
defineScalyrAngularModule('slyEvaluate', ['gatedScope'])
/**
* Directive for preventing all bound expressions in the current element and its children
* from being evaluated unless the specified expression evaluates to a different object.
* Currently, the value assigned to the 'slyEvaluateOnlyWhen' must evaluate to an object.
* Also, reference equality is used to determine if the expression has changed.
* TODO: Make this more versatile, similar to $watch. For now, this is all we need.
*/
.directive('slyEvaluateOnlyWhen', ['$parse', function ($parse) {
return {
// We create a new scope just because it helps segment the gated watchers
// from the parent scope. Unclear if this is that important for perf.
scope: true,
restrict: 'A',
compile: function compile(tElement, tAttrs) {
return {
// We need a separate pre-link function because we want to modify the scope before any of the
// children are passed it.
pre: function preLink(scope, element, attrs) {
var previousValue = null;
var initialized = false;
var expressionToCheck = $parse(attrs['slyEvaluateOnlyWhen']);
var alwaysEvaluateString = null;
if (hasProperty(attrs, 'slyAlwaysEvaluate')) {
alwaysEvaluateString = attrs['slyAlwaysEvaluate'];
if (isStringEmpty(alwaysEvaluateString))
throw new Exception('Empty string is illegal for value of slyAlwaysEvaluate');
}
scope.$addWatcherGate(function evaluteOnlyWhenChecker() {
// We should only return true if expressionToCheck evaluates to a value different
// than previousValue.
var currentValue = expressionToCheck(scope);
if (!initialized) {
initialized = true;
previousValue = currentValue;
return true;
}
var result = previousValue !== currentValue;
previousValue = currentValue;
return result;
}, function shouldGateWatcher(watchExpression) {
// Should return true if the given watcher that's about to be registered should
// be gated.
return isNull(alwaysEvaluateString) ||
!(isStringNonempty(watchExpression) && (watchExpression.indexOf(alwaysEvaluateString) >= 0));
});
},
};
},
};
}])
/**
* Directive for overriding the 'slyEvaluateOnlyWhen' expression for the current element.
* This directive takes a single string value. If this string value is found anywhere in
* an expression that normally would not be evaluated due to the 'slyEvaluateOnlyWhen'
* directive, it is evaluated, regardless of whether or not the value for the expression in
* 'slyEvaluateOnlyWhen' has changed. This is very useful when a certain expression used by
* one of the children of the current element should always be evaluated and is not affected
* by the expression specified in slyEvaluateOnlyWhen.
*/
.directive('slyAlwaysEvaluate', function() {
// This is just a place holder to show that slyAlwaysEvaluate is a legal
// directive. The real work for this directive is done in slyEvaluateOnlyWhen.
return {
restrict: 'A',
link: function(scope, element, attrs) {
},
};
})
/**
* Directive for showing an element, very similar to ngShow. However, this directive
* works better with slyPreventEvaluationWhenHidden because it is ensure it always
* will evaluate the show expression to determine if it should be shown or hidden
* even if slyPreventEvaluationWhenHidden is in effect. This directive also uses
* the ng-hide css class to actually hide the element.
*
* NOTE: We might be able to get better performance if we have this directive directly
* perform a callback on slyPreventEvaluationWhenHidden when it is shown/hidden rather
* than having that directive register a watcher on the css class.
*/
.directive('slyShow', ['$animate', function($animate) {
/**
* @param {*} value The input
* @return {Boolean} True if the value is truthy as determined by angular rules.
*
* Note: This is copied from the Angular source because it is not exposed by Angular
* but we want our directive to behave the same as ngShow. Think about moving this
* to core.js.
*/
function toBoolean(value) {
if (value && value.length !== 0) {
var v = ("" + value);
v = isString(v) ? v.toLowerCase() : v;
value = !(v == 'f' || v == '0' || v == 'false' || v == 'no' || v == 'n' || v == '[]');
} else {
value = false;
}
return value;
}
return {
restrict: 'A',
link: function slyShowLink(scope, element, attr) {
scope.$watch(attr.slyShow, function ngSlyShowAction(value){
$animate[toBoolean(value) ? 'removeClass' : 'addClass'](element, 'ng-hide');
}, false, 'slyShow'); },
};
}])
/**
* Directive for preventing all bound expressions in the current element and its children
* from being evaluated if the current element is hidden as determined by whether or not
* it has the ng-hide class.
*/
.directive('slyPreventEvaluationWhenHidden', function () {
return {
restrict: 'A',
// We create a new scope just because it helps segment the gated watchers
// from the parent scope. Unclear if this is that important for perf.
scope: true,
compile: function compile(tElement, tAttrs) {
return {
// We need a separate pre-link function because we want to modify the scope before any of the
// children are passed it.
pre: function preLink(scope, element, attrs) {
scope.$addWatcherGate(function hiddenChecker() {
// Should only return true if the element is not hidden.
return !element.hasClass('ng-hide');
}, function hiddenDecider(watchExpression, listener, equality, directiveName) {
// Make an exception for slyShow.. do not gate its watcher.
if (isDefined(directiveName) && (directiveName == 'slyShow'))
return false;
return true;
});
},
};
},
};
});
/**
* @fileoverview
* Module: slyRepeat
*
* Contains the slyRepeat directive, which is is a modified version of the
* ngRepeat directive that is meant to be more efficient for creating and
* recreating large lists of bound elements. In particular, it has an
* optimization that will prevent DOM elements from being constantly created
* and destroyed as the contents of the repeated elements change. It does this
* by not destroying DOM elements when they are no longer needed, but instead,
* just hiding them. This might not work for all use cases, but for it does
* for the ones we do wish to heavily optimize. For eample, through profiling,
* we found that destroying DOM elements when flipping through log view pages
* represented a large chunk of CPU time.
*
* Cavaets: The collection expression must evaluate to an array. Animators
* will not work. Track By does not work. Use at your own peril.
*
* @author Steven Czerwinski <[email protected]>
*/
defineScalyrAngularModule('slyRepeat', ['gatedScope'])
.directive('slyRepeat', ['$animate', '$parse', function ($animate, $parse) {
/**
* Sets the scope contained in elementScope to gate all its
* watchers based on the isActiveForRepeat proprety.
*
* @param {Object} elementScope The object containing the
* scope and isActiveForRepeat properties.
*/
function gateWatchersForScope(elementScope) {
elementScope.scope.$addWatcherGate(function() {
return elementScope.isActiveForRepeat;
});
}
return {
restrict: 'A',
scope: true,
transclude: 'element',
priority: 1000,
terminal: true,
compile: function(element, attr, linker) {
// Most of the work is done in the post-link function.
return function($scope, $element, $attr) {
// This code is largely based on ngRepeat.
// Parse the expression. It should look like:
// x in some-expression
var expression = $attr.slyRepeat;
var match = expression.match(/^\s*(.+)\s+in\s+(.*?)$/);
if (!match) {
throw Error("Expected slyRepeat in form of '_item_ in _collection_' but got '" +
expression + "'.");
}
var iterVar = match[1];
var collectionExpr = match[2];
match = iterVar.match(/^(?:([\$\w]+))$/);
if (!match) {
throw Error("'item' in 'item in collection' should be identifier but got '" +
lhs + "'.");
}
// previousElements will store references to the already existing (DOM) elements
// that were last used for the last rendering of this repeat and were visible.
// We will re-use these elements when executing the next rendering of the repeat when
// the iteration value changes.
var previousElements = [];
// previousElementsBuffer will store references to the already existing (DOM) elements
// that are in the page but were not used for the last rendering of this repeat and were
// therefore marked as inactive and not visible. This happens if the length of the repeat
// iteration goes down over time, since we do not remove the elements. If the repeat length
// was first 10, then 5, we will end up with the last 5 elements in the previousElementBuffer.
// We keep this in case the length increases again.
var previousElementBuffer = [];
var deregisterCallback = $scope.$watchCollection(collectionExpr, function(collection) {
if (!isArray(collection))
throw Error("'collection' did not evaluate to an array. expression was " + collectionExpr);
var originalPreviousElementsLength = previousElements.length;
// First, reconcile previousElements and collection with respect to the previousElementBuffer.
// Basically, try to grow previousElements to collection.length if we can.
if ((previousElements.length < collection.length) && (previousElementBuffer.length > 0)) {
var limit = previousElements.length + previousElementBuffer.length;
if (limit > collection.length)
limit = collection.length;
previousElements = previousElements.concat(previousElementBuffer.splice(0, limit - previousElements.length));
}
var currentElements = null;
var currentElementBuffer = [];
var newElements = [];
if (collection.length > previousElements.length) {
// Add in enough elements to account for the larger collection.
for (var i = previousElements.length; i < collection.length; ++i) {
// Need to add in an element for each new item in the collection.
var newElement = {
scope: $scope.$new(),
isActiveForRepeat: true,
};
gateWatchersForScope(newElement);
newElement.scope.$index = i;
newElement.scope.$first = (i == 0);
newElements.push(newElement);
}
currentElements = previousElements.concat(newElements);
currentElementBuffer = previousElementBuffer;
} else if (collection.length < previousElements.length) {
for (var i = collection.length; i < previousElements.length; ++i)
previousElements[i].isActiveForRepeat = false;
currentElementBuffer = previousElements.splice(collection.length, previousElements.length - collection.length).concat(
previousElementBuffer);
currentElements = previousElements;
} else {
currentElements = previousElements;
currentElementBuffer = previousElementBuffer;
}
// We have to fix up the last and middle values in the scope for each element in
// currentElements, since their roles may have changed with the new length.
// We always have to fix the last element.
if (currentElements.length > 0) {
var firstIndexToFix = currentElements.length - 1;
var lastIndexToFix = currentElements.length - 1;
// We also have to fix any new elements that were added.
if (originalPreviousElementsLength < currentElements.length) {
firstIndexToFix = originalPreviousElementsLength;
}
// And we usually have to fix the element before the first element we modified
// in case it used to be last.
if (firstIndexToFix > 0) {
firstIndexToFix = firstIndexToFix - 1;
}
for (var i = firstIndexToFix; i <= lastIndexToFix; ++i) {
currentElements[i].scope.$last = (i == (currentElements.length - 1));
currentElements[i].scope.$middle = ((i != 0) && (i != (currentElements.length - 1)));
if (!currentElements[i].isActiveForRepeat) {
// If it is not marked as active, make it active. This is also indicates that
// the element is currently hidden, so we have to unhide it.
currentElements[i].isActiveForRepeat = true;
currentElements[i].element.css('display', '');
}
}
}
// Hide all elements that have recently become inactive.
for (var i = 0; i < currentElementBuffer.length; ++i) {
if (currentElementBuffer[i].isActiveForRepeat)
break;
currentElementBuffer[i].element.css('display', 'none');
}
// Assign the new value for the iter variable for each scope.
for (var i = 0; i < currentElements.length; ++i) {
currentElements[i].scope[iterVar] = collection[i];
}
// We have to go back now and clone the DOM element for any new elements we
// added and link them in. We clone the last DOM element we had created already
// for this Repeat.
var prevElement = $element;
if (previousElements.length > 0)
prevElement = previousElements[previousElements.length - 1].element;
for (var i = 0; i < newElements.length; ++i) {
linker(newElements[i].scope, function(clone) {
$animate.enter(clone, null, prevElement);
prevElement = clone;
newElements[i].element = clone;
});
}
previousElements = currentElements;
previousElementBuffer = currentElementBuffer;
});
$scope.$on('$destroy', function() {
deregisterCallback();
});
};
}
};
}]);
/**
* @fileoverview
* Defines an extension to angular.Scope that allows for registering
* 'gating functions' on a scope that will prevent all future watchers
* registered on the scope from being evaluated unless the gating function
* returns true.
*
* By depending on this module, the $rootScope instance and angular.Scope
* class are automatically extended to implement this new capability.
*
* Warning, this implementation depends on protected/private variables
* in the angular.Scope implementation and therefore can break in the
* future due to changes in the angular.Scope implementation. Use at
* your own risk.
*/
defineScalyrAngularModule('gatedScope', [])
.config(['$provide', function($provide) {
// We use a decorator to override methods in $rootScope.
$provide.decorator('$rootScope', ['$delegate', '$exceptionHandler',
function ($rootScope, $exceptionHandler) {
// Make a copy of $rootScope's original methods so that we can access
// them to invoke super methods in the ones we override.
scopePrototype = {};
for (var key in $rootScope) {
if (isFunction($rootScope[key]))
scopePrototype[key] = $rootScope[key];
}
var Scope = $rootScope.constructor;
// Hold all of our new methods.
var methodsToAdd = {
};
// A constant value that the $digest loop implementation depends on. We
// grab it down below.
var initWatchVal;
/**
* @param {Boolean} isolate Whether or not the new scope should be isolated.
* @returns {Scope} A new child scope
*/
methodsToAdd.$new = function(isolate) {
// Because of how scope.$new works, the returned result
// should already have our new methods.
var result = scopePrototype.$new.call(this, isolate);
// We just have to do the work that normally a child class's
// constructor would perform -- initializing our instance vars.
result.$$gatingFunction = this.$$gatingFunction;
result.$$shouldGateFunction = this.$$shouldGateFunction;
result.$$gatedWatchers = [];
return result;
};
/**
* Digests all of the gated watchers for the specified gating function.
*
* @param {Function} targetGatingFunction The gating function associated
* with the watchers that should be digested
* @returns {Boolean} True if any of the watchers were dirty
*/
methodsToAdd.$digestGated = function gatedScopeDigest(targetGatingFunction) {
// Note, most of this code was stolen from angular's Scope.$digest method.
var watch, value,
watchers,
length,
next, current = this, target = this,
dirty = false;
do { // "traverse the scopes" loop
if (watchers = current.$$gatedWatchers) {
// process our watches
length = watchers.length;
while (length--) {
try {
watch = watchers[length];
// Scalyr edit: We do not process a watch function if it is does not
// have the same gating function for which $digestGated was invoked.
if (watch.gatingFunction !== targetGatingFunction)
continue;
// Most common watches are on primitives, in which case we can short
// circuit it with === operator, only when === fails do we use .equals
if (watch && (value = watch.get(current)) !== (last = watch.last) &&
!(watch.eq
? equals(value, last)
: (typeof value == 'number' && typeof last == 'number'
&& isNaN(value) && isNaN(last)))) {
dirty = true;
watch.last = watch.eq ? copy(value) : value;
watch.fn(value, ((last === initWatchVal) ? value : last), current);
// Scalyr edit: Removed the logging code for when the ttl is reached
// here because we don't have access to the ttl in this method.
}
} catch (e) {
$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!
// Scalyr edit: This insanity warning was from angular. We only modified this
// code by checking the $$gatingFunction because it's a good optimization to only go
// down a child of a parent that has the same gating function as what we are processing
// (since if a parent already has a different gating function, there's no way any
// of its children will have the right one).
if (!(next = ((current.$$gatingFunction === targetGatingFunction && current.$$childHead)
|| (current !== target && current.$$nextSibling)))) {
while(current !== target && !(next = current.$$nextSibling)) {
current = current.$parent;
}
}
} while ((current = next));
return dirty;
};
/**
* @inherited $watch
* @param directiveName The fourth parameter is a new optional parameter that allows
* directives aware of this abstraction to pass in their own names to identify
* which directive is registering the watch. This is then passed to the
* shouldGateFunction to help determine if the watcher should be gated by the current
* gatingFunction.
*/
methodsToAdd.$watch = function gatedWatch(watchExpression, listener, objectEquality,
directiveName) {
// Determine if we should gate this watcher.
if (!isNull(this.$$gatingFunction) && (isNull(this.$$shouldGateFunction) ||
this.$$shouldGateFunction(watchExpression, listener, objectEquality, directiveName))) {
// We do a hack here to just switch out the watchers array with our own
// gated list and then invoke the original watch function.
var tmp = this.$$watchers;
this.$$watchers = this.$$gatedWatchers;
// Invoke original watch function.
var result = scopePrototype.$watch.call(this, watchExpression, listener, objectEquality);
this.$$watchers = tmp;
this.$$gatedWatchers[0].gatingFunction = this.$$gatingFunction;
// We know that the last field of the watcher object will be set to initWatchVal, so we
// grab it here.
initWatchVal = this.$$gatedWatchers[0].last;
return result;
} else {
return scopePrototype.$watch.call(this, watchExpression, listener, objectEquality);
}
};
/**
* Modifies this scope so that all future watchers registered by $watch will
* only be evaluated if gatingFunction returns true. Optionally, you may specify
* a function that will be evaluted on every new call to $watch with the arguments
* passed to it, and that watcher will only be gated if the function returns true.
*
* @param {Function} gatingFunction The gating function which controls whether or not all future
* watchers registered on this scope and its children will be evaluated on a given
* digest cycle. The function will be invoked (with no arguments) on every digest
* and if it returns a truthy result, will cause all gated watchers to be evaluated.
* @param {Function} shouldGateFunction The function that controls whether or not
* a new watcher will be gated using gatingFunction. It is evaluated with the
* arguments to $watch and should return true if the watcher created by those
* arguments should be gated
*/
methodsToAdd.$addWatcherGate = function(gatingFunction, shouldGateFunction) {
var changeCount = 0;
var self = this;
// Set a watcher that sees if our gating function is true, and if so, digests
// all of our associated watchers. Note, this.$watch could already have a
// gating function associated with it, which means this watch won't be executed
// unless all gating functions before us have evaluated to true. We take special
// care of this nested case below.
// We handle nested gating function in a special way. If we are a nested gating
// function (meaning there is already one or more gating functions on this scope and
// our parent scopes), then if those parent gating functions every all evaluate to
// true (which we can tell if the watcher we register here is evaluated), then
// we always evaluate our watcher until our gating function returns true.
var hasNestedGates = !isNull(this.$$gatingFunction);
var promotedWatcher = null;
this.$watch(function() {
if (gatingFunction()) {
if (self.$digestGated(gatingFunction))
++changeCount;
} else if (hasNestedGates && isNull(promotedWatcher)) {
promotedWatcher = scopePrototype.$watch.call(self, function() {
if (gatingFunction()) {
promotedWatcher();
promotedWatcher = null;
if (self.$digestGated(gatingFunction))
++changeCount;
}
return changeCount;
});
}
return changeCount;
});
if (isUndefined(shouldGateFunction))
shouldGateFunction = null;
this.$$gatingFunction = gatingFunction;
this.$$shouldGateFunction = shouldGateFunction;
};
// Extend the original Scope object so that when
// new instances are created, it has the new methods.
angular.extend(Scope.prototype, methodsToAdd);
// Also extend the $rootScope instance since it was created
// before we got a chance to extend Scope.prototype.
angular.extend($rootScope, methodsToAdd);
$rootScope.$$gatingFunction = null;
$rootScope.$$shouldGateFunction = null;
$rootScope.$$gatedWatchers = [];
return $rootScope;
}]);
}]);