| OLD | NEW |
| 1 part of angular.directive; | 1 part of angular.directive; |
| 2 | 2 |
| 3 /** | 3 /** |
| 4 * The `ngClass` allows you to set CSS classes on HTML an element, dynamically, | 4 * The `ngClass` allows you to set CSS classes on HTML an element, dynamically, |
| 5 * by databinding an expression that represents all classes to be added. | 5 * by databinding an expression that represents all classes to be added. |
| 6 * | 6 * |
| 7 * The directive won't add duplicate classes if a particular class was | 7 * The directive won't add duplicate classes if a particular class was |
| 8 * already set. | 8 * already set. |
| 9 * | 9 * |
| 10 * When the expression changes, the previously added classes are removed and | 10 * When the expression changes, the previously added classes are removed and |
| (...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 55 * text-decoration: line-through; | 55 * text-decoration: line-through; |
| 56 * } | 56 * } |
| 57 * .bold { | 57 * .bold { |
| 58 * font-weight: bold; | 58 * font-weight: bold; |
| 59 * } | 59 * } |
| 60 * .red { | 60 * .red { |
| 61 * color: red; | 61 * color: red; |
| 62 * } | 62 * } |
| 63 * | 63 * |
| 64 */ | 64 */ |
| 65 @NgDirective( | 65 @Decorator( |
| 66 selector: '[ng-class]', | 66 selector: '[ng-class]', |
| 67 map: const {'ng-class': '@valueExpression'}, | 67 map: const {'ng-class': '@valueExpression'}, |
| 68 exportExpressionAttrs: const ['ng-class']) | 68 exportExpressionAttrs: const ['ng-class']) |
| 69 class NgClassDirective extends _NgClassBase { | 69 class NgClass extends _NgClassBase { |
| 70 NgClassDirective(dom.Element element, Scope scope, NodeAttrs attrs, AstParser
parser) | 70 NgClass(NgElement ngElement, Scope scope, NodeAttrs nodeAttrs) |
| 71 : super(element, scope, null, attrs, parser); | 71 : super(ngElement, scope, nodeAttrs); |
| 72 } | 72 } |
| 73 | 73 |
| 74 /** | 74 /** |
| 75 * The `ngClassOdd` and `ngClassEven` directives work exactly as | 75 * The `ngClassOdd` and `ngClassEven` directives work exactly as |
| 76 * {@link ng.directive:ngClass ngClass}, except it works in | 76 * {@link ng.directive:ngClass ngClass}, except it works in |
| 77 * conjunction with `ngRepeat` and takes affect only on odd (even) rows. | 77 * conjunction with `ngRepeat` and takes affect only on odd (even) rows. |
| 78 * | 78 * |
| 79 * This directive can be applied only within a scope of an `ngRepeat`. | 79 * This directive can be applied only within a scope of an `ngRepeat`. |
| 80 * | 80 * |
| 81 * ##Examples | 81 * ##Examples |
| 82 * | 82 * |
| 83 * index.html: | 83 * index.html: |
| 84 * | 84 * |
| 85 * <li ng-repeat="name in ['John', 'Mary', 'Cate', 'Suz']"> | 85 * <li ng-repeat="name in ['John', 'Mary', 'Cate', 'Suz']"> |
| 86 * <span ng-class-odd="'odd'" ng-class-even="'even'"> | 86 * <span ng-class-odd="'odd'" ng-class-even="'even'"> |
| 87 * {{name}} | 87 * {{name}} |
| 88 * </span> | 88 * </span> |
| 89 * </li> | 89 * </li> |
| 90 * | 90 * |
| 91 * style.css: | 91 * style.css: |
| 92 * | 92 * |
| 93 * .odd { | 93 * .odd { |
| 94 * color: red; | 94 * color: red; |
| 95 * } | 95 * } |
| 96 * .even { | 96 * .even { |
| 97 * color: blue; | 97 * color: blue; |
| 98 * } | 98 * } |
| 99 */ | 99 */ |
| 100 @NgDirective( | 100 @Decorator( |
| 101 selector: '[ng-class-odd]', | 101 selector: '[ng-class-odd]', |
| 102 map: const {'ng-class-odd': '@valueExpression'}, | 102 map: const {'ng-class-odd': '@valueExpression'}, |
| 103 exportExpressionAttrs: const ['ng-class-odd']) | 103 exportExpressionAttrs: const ['ng-class-odd']) |
| 104 class NgClassOddDirective extends _NgClassBase { | 104 class NgClassOdd extends _NgClassBase { |
| 105 NgClassOddDirective(dom.Element element, Scope scope, NodeAttrs attrs, AstPars
er parser) | 105 NgClassOdd(NgElement ngElement, Scope scope, NodeAttrs nodeAttrs) |
| 106 : super(element, scope, 0, attrs, parser); | 106 : super(ngElement, scope, nodeAttrs, 0); |
| 107 } | 107 } |
| 108 | 108 |
| 109 /** | 109 /** |
| 110 * The `ngClassOdd` and `ngClassEven` directives work exactly as | 110 * The `ngClassOdd` and `ngClassEven` directives work exactly as |
| 111 * {@link ng.directive:ngClass ngClass}, except it works in | 111 * {@link ng.directive:ngClass ngClass}, except it works in |
| 112 * conjunction with `ngRepeat` and takes affect only on odd (even) rows. | 112 * conjunction with `ngRepeat` and takes affect only on odd (even) rows. |
| 113 * | 113 * |
| 114 * This directive can be applied only within a scope of an `ngRepeat`. | 114 * This directive can be applied only within a scope of an `ngRepeat`. |
| 115 * | 115 * |
| 116 * ##Examples | 116 * ##Examples |
| 117 * | 117 * |
| 118 * index.html: | 118 * index.html: |
| 119 * | 119 * |
| 120 * <li ng-repeat="name in ['John', 'Mary', 'Cate', 'Suz']"> | 120 * <li ng-repeat="name in ['John', 'Mary', 'Cate', 'Suz']"> |
| 121 * <span ng-class-odd="'odd'" ng-class-even="'even'"> | 121 * <span ng-class-odd="'odd'" ng-class-even="'even'"> |
| 122 * {{name}} | 122 * {{name}} |
| 123 * </span> | 123 * </span> |
| 124 * </li> | 124 * </li> |
| 125 * | 125 * |
| 126 * style.css: | 126 * style.css: |
| 127 * | 127 * |
| 128 * .odd { | 128 * .odd { |
| 129 * color: red; | 129 * color: red; |
| 130 * } | 130 * } |
| 131 * .even { | 131 * .even { |
| 132 * color: blue; | 132 * color: blue; |
| 133 * } | 133 * } |
| 134 */ | 134 */ |
| 135 @NgDirective( | 135 @Decorator( |
| 136 selector: '[ng-class-even]', | 136 selector: '[ng-class-even]', |
| 137 map: const {'ng-class-even': '@valueExpression'}, | 137 map: const {'ng-class-even': '@valueExpression'}, |
| 138 exportExpressionAttrs: const ['ng-class-even']) | 138 exportExpressionAttrs: const ['ng-class-even']) |
| 139 class NgClassEvenDirective extends _NgClassBase { | 139 class NgClassEven extends _NgClassBase { |
| 140 NgClassEvenDirective(dom.Element element, Scope scope, NodeAttrs attrs, AstPar
ser parser) | 140 NgClassEven(NgElement ngElement, Scope scope, NodeAttrs nodeAttrs) |
| 141 : super(element, scope, 1, attrs, parser); | 141 : super(ngElement, scope, nodeAttrs, 1); |
| 142 } | 142 } |
| 143 | 143 |
| 144 abstract class _NgClassBase { | 144 abstract class _NgClassBase { |
| 145 final dom.Element element; | 145 final NgElement _ngElement; |
| 146 final Scope scope; | 146 final Scope _scope; |
| 147 final int mode; | 147 final int _mode; |
| 148 final NodeAttrs nodeAttrs; | 148 Watch _watchExpression; |
| 149 final AstParser _parser; | 149 Watch _watchPosition; |
| 150 var previousSet = []; | 150 var _previousSet = new Set<String>(); |
| 151 var currentSet = []; | 151 var _currentSet = new Set<String>(); |
| 152 bool _first = true; |
| 152 | 153 |
| 153 _NgClassBase(this.element, this.scope, this.mode, this.nodeAttrs, this._parser
) { | 154 _NgClassBase(this._ngElement, this._scope, NodeAttrs nodeAttrs, |
| 154 var prevClass; | 155 [this._mode = null]) |
| 156 { |
| 157 var prevCls; |
| 155 | 158 |
| 156 nodeAttrs.observe('class', (String newValue) { | 159 nodeAttrs.observe('class', (String cls) { |
| 157 if (prevClass != newValue) { | 160 if (prevCls != cls) { |
| 158 prevClass = newValue; | 161 prevCls = cls; |
| 159 _handleChange(scope.context[r'$index']); | 162 _applyChanges(_scope.context[r'$index']); |
| 160 } | 163 } |
| 161 }); | 164 }); |
| 162 } | 165 } |
| 163 | 166 |
| 164 set valueExpression(currentExpression) { | 167 set valueExpression(expression) { |
| 165 // this should be called only once, so we don't worry about cleaning up | 168 if (_watchExpression != null) _watchExpression.remove(); |
| 166 // watcher registrations. | 169 _watchExpression = _scope.watch(expression, (v, _) { |
| 167 scope.watch( | 170 _computeChanges(v); |
| 168 _parser(currentExpression, collection: true), | 171 _applyChanges(_scope.context[r'$index']); |
| 169 (current, _) { | 172 }, |
| 170 currentSet = _flatten(current); | 173 canChangeModel: false, |
| 171 _handleChange(scope.context[r'$index']); | 174 collection: true); |
| 172 }, | 175 |
| 173 readOnly: true | 176 if (_mode != null) { |
| 174 ); | 177 if (_watchPosition != null) _watchPosition.remove(); |
| 175 if (mode != null) { | 178 _watchPosition = _scope.watch(r'$index', (idx, previousIdx) { |
| 176 scope.watch(_parser(r'$index'), (index, oldIndex) { | 179 var mod = idx % 2; |
| 177 var mod = index % 2; | 180 if (previousIdx == null || mod != previousIdx % 2) { |
| 178 if (oldIndex == null || mod != oldIndex % 2) { | 181 if (mod == _mode) { |
| 179 if (mod == mode) { | 182 _currentSet.forEach((cls) => _ngElement.addClass(cls)); |
| 180 element.classes.addAll(currentSet); | |
| 181 } else { | 183 } else { |
| 182 element.classes.removeAll(previousSet); | 184 _previousSet.forEach((cls) => _ngElement.removeClass(cls)); |
| 183 } | 185 } |
| 184 } | 186 } |
| 185 }, readOnly: true); | 187 }, canChangeModel: false); |
| 186 } | 188 } |
| 187 } | 189 } |
| 188 | 190 |
| 189 _handleChange(index) { | 191 void _computeChanges(value) { |
| 190 if (mode == null || (index != null && index % 2 == mode)) { | 192 if (value is CollectionChangeRecord) { |
| 191 element.classes..removeAll(previousSet)..addAll(currentSet); | 193 _computeCollectionChanges(value, _first); |
| 194 } else if (value is MapChangeRecord) { |
| 195 _computeMapChanges(value, _first); |
| 196 } else { |
| 197 if (value is String) { |
| 198 _currentSet..clear()..addAll(value.split(' ')); |
| 199 } else if (value == null) { |
| 200 _currentSet.clear(); |
| 201 } else { |
| 202 throw 'ng-class expects expression value to be List, Map or String, ' |
| 203 'got $value'; |
| 204 } |
| 192 } | 205 } |
| 193 | 206 |
| 194 previousSet = currentSet; | 207 _first = false; |
| 195 } | 208 } |
| 196 | 209 |
| 197 static List<String> _flatten(classes) { | 210 // todo(vicb) refactor once GH-774 gets fixed |
| 198 if (classes == null) return []; | 211 void _computeCollectionChanges(CollectionChangeRecord changes, bool first) { |
| 199 if (classes is CollectionChangeRecord) { | 212 if (first) { |
| 200 classes = (classes as CollectionChangeRecord).iterable.toList(); | 213 changes.iterable.forEach((cls) { |
| 214 _currentSet.add(cls); |
| 215 }); |
| 216 } else { |
| 217 changes.forEachAddition((CollectionChangeItem a) { |
| 218 _currentSet.add(a.item); |
| 219 }); |
| 220 changes.forEachRemoval((CollectionChangeItem r) { |
| 221 _currentSet.remove(r.item); |
| 222 }); |
| 201 } | 223 } |
| 202 if (classes is List) { | 224 } |
| 203 return classes.where((String e) => e != null && e.isNotEmpty) | 225 |
| 204 .toList(growable: false); | 226 // todo(vicb) refactor once GH-774 gets fixed |
| 227 _computeMapChanges(MapChangeRecord changes, first) { |
| 228 if (first) { |
| 229 changes.map.forEach((cls, active) { |
| 230 if (toBool(active)) _currentSet.add(cls); |
| 231 }); |
| 232 } else { |
| 233 changes.forEachChange((MapKeyValue kv) { |
| 234 var cls = kv.key; |
| 235 var active = toBool(kv.currentValue); |
| 236 var wasActive = toBool(kv.previousValue); |
| 237 if (active != wasActive) { |
| 238 if (active) { |
| 239 _currentSet.add(cls); |
| 240 } else { |
| 241 _currentSet.remove(cls); |
| 242 } |
| 243 } |
| 244 }); |
| 245 changes.forEachAddition((MapKeyValue kv) { |
| 246 if (toBool(kv.currentValue)) _currentSet.add(kv.key); |
| 247 }); |
| 248 changes.forEachRemoval((MapKeyValue kv) { |
| 249 if (toBool(kv.previousValue)) _currentSet.remove(kv.key); |
| 250 }); |
| 205 } | 251 } |
| 206 if (classes is MapChangeRecord) { | 252 } |
| 207 classes = (classes as MapChangeRecord).map; | 253 |
| 254 _applyChanges(index) { |
| 255 if (_mode == null || (index != null && index % 2 == _mode)) { |
| 256 _previousSet |
| 257 .where((cls) => cls != null) |
| 258 .forEach((cls) => _ngElement.removeClass(cls)); |
| 259 _currentSet |
| 260 .where((cls) => cls != null) |
| 261 .forEach((cls) => _ngElement.addClass(cls)); |
| 208 } | 262 } |
| 209 if (classes is Map) { | 263 |
| 210 return classes.keys.where((key) => toBool(classes[key])).toList(); | 264 _previousSet = _currentSet.toSet(); |
| 211 } | |
| 212 if (classes is String) return classes.split(' '); | |
| 213 throw 'ng-class expects expression value to be List, Map or String, got $cla
sses'; | |
| 214 } | 265 } |
| 215 } | 266 } |
| OLD | NEW |