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