2b02aa540e410a64a3b0b89c3211911f9575903f
[vnfsdk/refrepo.git] /
1 /*!
2  * Angular Material Design
3  * https://github.com/angular/material
4  * @license MIT
5  * v1.1.3
6  */
7 (function( window, angular, undefined ){
8 "use strict";
9
10 /**
11  * @ngdoc module
12  * @name material.components.radioButton
13  * @description radioButton module!
14  */
15 mdRadioGroupDirective['$inject'] = ["$mdUtil", "$mdConstant", "$mdTheming", "$timeout"];
16 mdRadioButtonDirective['$inject'] = ["$mdAria", "$mdUtil", "$mdTheming"];
17 angular.module('material.components.radioButton', [
18   'material.core'
19 ])
20   .directive('mdRadioGroup', mdRadioGroupDirective)
21   .directive('mdRadioButton', mdRadioButtonDirective);
22
23 /**
24  * @ngdoc directive
25  * @module material.components.radioButton
26  * @name mdRadioGroup
27  *
28  * @restrict E
29  *
30  * @description
31  * The `<md-radio-group>` directive identifies a grouping
32  * container for the 1..n grouped radio buttons; specified using nested
33  * `<md-radio-button>` tags.
34  *
35  * As per the [material design spec](http://www.google.com/design/spec/style/color.html#color-ui-color-application)
36  * the radio button is in the accent color by default. The primary color palette may be used with
37  * the `md-primary` class.
38  *
39  * Note: `<md-radio-group>` and `<md-radio-button>` handle tabindex differently
40  * than the native `<input type='radio'>` controls. Whereas the native controls
41  * force the user to tab through all the radio buttons, `<md-radio-group>`
42  * is focusable, and by default the `<md-radio-button>`s are not.
43  *
44  * @param {string} ng-model Assignable angular expression to data-bind to.
45  * @param {boolean=} md-no-ink Use of attribute indicates flag to disable ink ripple effects.
46  *
47  * @usage
48  * <hljs lang="html">
49  * <md-radio-group ng-model="selected">
50  *
51  *   <md-radio-button
52  *        ng-repeat="d in colorOptions"
53  *        ng-value="d.value" aria-label="{{ d.label }}">
54  *
55  *          {{ d.label }}
56  *
57  *   </md-radio-button>
58  *
59  * </md-radio-group>
60  * </hljs>
61  *
62  */
63 function mdRadioGroupDirective($mdUtil, $mdConstant, $mdTheming, $timeout) {
64   RadioGroupController.prototype = createRadioGroupControllerProto();
65
66   return {
67     restrict: 'E',
68     controller: ['$element', RadioGroupController],
69     require: ['mdRadioGroup', '?ngModel'],
70     link: { pre: linkRadioGroup }
71   };
72
73   function linkRadioGroup(scope, element, attr, ctrls) {
74     element.addClass('_md');     // private md component indicator for styling
75     $mdTheming(element);
76
77     var rgCtrl = ctrls[0];
78     var ngModelCtrl = ctrls[1] || $mdUtil.fakeNgModel();
79
80     rgCtrl.init(ngModelCtrl);
81
82     scope.mouseActive = false;
83
84     element
85       .attr({
86         'role': 'radiogroup',
87         'tabIndex': element.attr('tabindex') || '0'
88       })
89       .on('keydown', keydownListener)
90       .on('mousedown', function(event) {
91         scope.mouseActive = true;
92         $timeout(function() {
93           scope.mouseActive = false;
94         }, 100);
95       })
96       .on('focus', function() {
97         if(scope.mouseActive === false) {
98           rgCtrl.$element.addClass('md-focused');
99         }
100       })
101       .on('blur', function() {
102         rgCtrl.$element.removeClass('md-focused');
103       });
104
105     /**
106      *
107      */
108     function setFocus() {
109       if (!element.hasClass('md-focused')) { element.addClass('md-focused'); }
110     }
111
112     /**
113      *
114      */
115     function keydownListener(ev) {
116       var keyCode = ev.which || ev.keyCode;
117
118       // Only listen to events that we originated ourselves
119       // so that we don't trigger on things like arrow keys in
120       // inputs.
121
122       if (keyCode != $mdConstant.KEY_CODE.ENTER &&
123           ev.currentTarget != ev.target) {
124         return;
125       }
126
127       switch (keyCode) {
128         case $mdConstant.KEY_CODE.LEFT_ARROW:
129         case $mdConstant.KEY_CODE.UP_ARROW:
130           ev.preventDefault();
131           rgCtrl.selectPrevious();
132           setFocus();
133           break;
134
135         case $mdConstant.KEY_CODE.RIGHT_ARROW:
136         case $mdConstant.KEY_CODE.DOWN_ARROW:
137           ev.preventDefault();
138           rgCtrl.selectNext();
139           setFocus();
140           break;
141
142         case $mdConstant.KEY_CODE.ENTER:
143           var form = angular.element($mdUtil.getClosest(element[0], 'form'));
144           if (form.length > 0) {
145             form.triggerHandler('submit');
146           }
147           break;
148       }
149
150     }
151   }
152
153   function RadioGroupController($element) {
154     this._radioButtonRenderFns = [];
155     this.$element = $element;
156   }
157
158   function createRadioGroupControllerProto() {
159     return {
160       init: function(ngModelCtrl) {
161         this._ngModelCtrl = ngModelCtrl;
162         this._ngModelCtrl.$render = angular.bind(this, this.render);
163       },
164       add: function(rbRender) {
165         this._radioButtonRenderFns.push(rbRender);
166       },
167       remove: function(rbRender) {
168         var index = this._radioButtonRenderFns.indexOf(rbRender);
169         if (index !== -1) {
170           this._radioButtonRenderFns.splice(index, 1);
171         }
172       },
173       render: function() {
174         this._radioButtonRenderFns.forEach(function(rbRender) {
175           rbRender();
176         });
177       },
178       setViewValue: function(value, eventType) {
179         this._ngModelCtrl.$setViewValue(value, eventType);
180         // update the other radio buttons as well
181         this.render();
182       },
183       getViewValue: function() {
184         return this._ngModelCtrl.$viewValue;
185       },
186       selectNext: function() {
187         return changeSelectedButton(this.$element, 1);
188       },
189       selectPrevious: function() {
190         return changeSelectedButton(this.$element, -1);
191       },
192       setActiveDescendant: function (radioId) {
193         this.$element.attr('aria-activedescendant', radioId);
194       },
195       isDisabled: function() {
196         return this.$element[0].hasAttribute('disabled');
197       }
198     };
199   }
200   /**
201    * Change the radio group's selected button by a given increment.
202    * If no button is selected, select the first button.
203    */
204   function changeSelectedButton(parent, increment) {
205     // Coerce all child radio buttons into an array, then wrap then in an iterator
206     var buttons = $mdUtil.iterator(parent[0].querySelectorAll('md-radio-button'), true);
207
208     if (buttons.count()) {
209       var validate = function (button) {
210         // If disabled, then NOT valid
211         return !angular.element(button).attr("disabled");
212       };
213
214       var selected = parent[0].querySelector('md-radio-button.md-checked');
215       var target = buttons[increment < 0 ? 'previous' : 'next'](selected, validate) || buttons.first();
216
217       // Activate radioButton's click listener (triggerHandler won't create a real click event)
218       angular.element(target).triggerHandler('click');
219     }
220   }
221
222 }
223
224 /**
225  * @ngdoc directive
226  * @module material.components.radioButton
227  * @name mdRadioButton
228  *
229  * @restrict E
230  *
231  * @description
232  * The `<md-radio-button>`directive is the child directive required to be used within `<md-radio-group>` elements.
233  *
234  * While similar to the `<input type="radio" ng-model="" value="">` directive,
235  * the `<md-radio-button>` directive provides ink effects, ARIA support, and
236  * supports use within named radio groups.
237  *
238  * @param {string} ngModel Assignable angular expression to data-bind to.
239  * @param {string=} ngChange Angular expression to be executed when input changes due to user
240  *    interaction with the input element.
241  * @param {string} ngValue Angular expression which sets the value to which the expression should
242  *    be set when selected.
243  * @param {string} value The value to which the expression should be set when selected.
244  * @param {string=} name Property name of the form under which the control is published.
245  * @param {string=} aria-label Adds label to radio button for accessibility.
246  * Defaults to radio button's text. If no text content is available, a warning will be logged.
247  *
248  * @usage
249  * <hljs lang="html">
250  *
251  * <md-radio-button value="1" aria-label="Label 1">
252  *   Label 1
253  * </md-radio-button>
254  *
255  * <md-radio-button ng-model="color" ng-value="specialValue" aria-label="Green">
256  *   Green
257  * </md-radio-button>
258  *
259  * </hljs>
260  *
261  */
262 function mdRadioButtonDirective($mdAria, $mdUtil, $mdTheming) {
263
264   var CHECKED_CSS = 'md-checked';
265
266   return {
267     restrict: 'E',
268     require: '^mdRadioGroup',
269     transclude: true,
270     template: '<div class="md-container" md-ink-ripple md-ink-ripple-checkbox>' +
271                 '<div class="md-off"></div>' +
272                 '<div class="md-on"></div>' +
273               '</div>' +
274               '<div ng-transclude class="md-label"></div>',
275     link: link
276   };
277
278   function link(scope, element, attr, rgCtrl) {
279     var lastChecked;
280
281     $mdTheming(element);
282     configureAria(element, scope);
283
284     // ngAria overwrites the aria-checked inside a $watch for ngValue.
285     // We should defer the initialization until all the watches have fired.
286     // This can also be fixed by removing the `lastChecked` check, but that'll
287     // cause more DOM manipulation on each digest.
288     if (attr.ngValue) {
289       $mdUtil.nextTick(initialize, false);
290     } else {
291       initialize();
292     }
293
294     /**
295      * Initializes the component.
296      */
297     function initialize() {
298       if (!rgCtrl) {
299         throw 'RadioButton: No RadioGroupController could be found.';
300       }
301
302       rgCtrl.add(render);
303       attr.$observe('value', render);
304
305       element
306         .on('click', listener)
307         .on('$destroy', function() {
308           rgCtrl.remove(render);
309         });
310     }
311
312     /**
313      * On click functionality.
314      */
315     function listener(ev) {
316       if (element[0].hasAttribute('disabled') || rgCtrl.isDisabled()) return;
317
318       scope.$apply(function() {
319         rgCtrl.setViewValue(attr.value, ev && ev.type);
320       });
321     }
322
323     /**
324      *  Add or remove the `.md-checked` class from the RadioButton (and conditionally its parent).
325      *  Update the `aria-activedescendant` attribute.
326      */
327     function render() {
328       var checked = rgCtrl.getViewValue() == attr.value;
329
330       if (checked === lastChecked) return;
331
332       if (element[0].parentNode.nodeName.toLowerCase() !== 'md-radio-group') {
333         // If the radioButton is inside a div, then add class so highlighting will work
334         element.parent().toggleClass(CHECKED_CSS, checked);
335       }
336
337       if (checked) {
338         rgCtrl.setActiveDescendant(element.attr('id'));
339       }
340
341       lastChecked = checked;
342
343       element
344         .attr('aria-checked', checked)
345         .toggleClass(CHECKED_CSS, checked);
346     }
347
348     /**
349      * Inject ARIA-specific attributes appropriate for each radio button
350      */
351     function configureAria(element, scope){
352       element.attr({
353         id: attr.id || 'radio_' + $mdUtil.nextUid(),
354         role: 'radio',
355         'aria-checked': 'false'
356       });
357
358       $mdAria.expectWithText(element, 'aria-label');
359     }
360   }
361 }
362
363 })(window, window.angular);