commonroutesApp/www/lib/angular-material/modules/closure/radioButton/radioButton.js
www/lib/angular-material/modules/closure/radioButton/radioButton.jsRaw
1/*!
2 * Angular Material Design
3 * https://github.com/angular/material
4 * @license MIT
5 * v1.1.1
6 */
7goog.provide('ngmaterial.components.radioButton');
8goog.require('ngmaterial.core');
9/**
10 * @ngdoc module
11 * @name material.components.radioButton
12 * @description radioButton module!
13 */
14mdRadioGroupDirective.$inject = ["$mdUtil", "$mdConstant", "$mdTheming", "$timeout"];
15mdRadioButtonDirective.$inject = ["$mdAria", "$mdUtil", "$mdTheming"];
16angular.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 */
62function 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
225/**
226 * @ngdoc directive
227 * @module material.components.radioButton
228 * @name mdRadioButton
229 *
230 * @restrict E
231 *
232 * @description
233 * The `<md-radio-button>`directive is the child directive required to be used within `<md-radio-group>` elements.
234 *
235 * While similar to the `<input type="radio" ng-model="" value="">` directive,
236 * the `<md-radio-button>` directive provides ink effects, ARIA support, and
237 * supports use within named radio groups.
238 *
239 * @param {string} ngModel Assignable angular expression to data-bind to.
240 * @param {string=} ngChange Angular expression to be executed when input changes due to user
241 * interaction with the input element.
242 * @param {string} ngValue Angular expression which sets the value to which the expression should
243 * be set when selected.
244 * @param {string} value The value to which the expression should be set when selected.
245 * @param {string=} name Property name of the form under which the control is published.
246 * @param {string=} aria-label Adds label to radio button for accessibility.
247 * Defaults to radio button's text. If no text content is available, a warning will be logged.
248 *
249 * @usage
250 * <hljs lang="html">
251 *
252 * <md-radio-button value="1" aria-label="Label 1">
253 * Label 1
254 * </md-radio-button>
255 *
256 * <md-radio-button ng-model="color" ng-value="specialValue" aria-label="Green">
257 * Green
258 * </md-radio-button>
259 *
260 * </hljs>
261 *
262 */
263function mdRadioButtonDirective($mdAria, $mdUtil, $mdTheming) {
264
265 var CHECKED_CSS = 'md-checked';
266
267 return {
268 restrict: 'E',
269 require: '^mdRadioGroup',
270 transclude: true,
271 template: '<div class="md-container" md-ink-ripple md-ink-ripple-checkbox>' +
272 '<div class="md-off"></div>' +
273 '<div class="md-on"></div>' +
274 '</div>' +
275 '<div ng-transclude class="md-label"></div>',
276 link: link
277 };
278
279 function link(scope, element, attr, rgCtrl) {
280 var lastChecked;
281
282 $mdTheming(element);
283 configureAria(element, scope);
284
285 initialize();
286
287 /**
288 *
289 */
290 function initialize() {
291 if (!rgCtrl) {
292 throw 'RadioButton: No RadioGroupController could be found.';
293 }
294
295 rgCtrl.add(render);
296 attr.$observe('value', render);
297
298 element
299 .on('click', listener)
300 .on('$destroy', function() {
301 rgCtrl.remove(render);
302 });
303 }
304
305 /**
306 *
307 */
308 function listener(ev) {
309 if (element[0].hasAttribute('disabled') || rgCtrl.isDisabled()) return;
310
311 scope.$apply(function() {
312 rgCtrl.setViewValue(attr.value, ev && ev.type);
313 });
314 }
315
316 /**
317 * Add or remove the `.md-checked` class from the RadioButton (and conditionally its parent).
318 * Update the `aria-activedescendant` attribute.
319 */
320 function render() {
321 var checked = (rgCtrl.getViewValue() == attr.value);
322 if (checked === lastChecked) {
323 return;
324 }
325
326 lastChecked = checked;
327 element.attr('aria-checked', checked);
328
329 if (checked) {
330 markParentAsChecked(true);
331 element.addClass(CHECKED_CSS);
332
333 rgCtrl.setActiveDescendant(element.attr('id'));
334
335 } else {
336 markParentAsChecked(false);
337 element.removeClass(CHECKED_CSS);
338 }
339
340 /**
341 * If the radioButton is inside a div, then add class so highlighting will work...
342 */
343 function markParentAsChecked(addClass ) {
344 if ( element.parent()[0].nodeName != "MD-RADIO-GROUP") {
345 element.parent()[ !!addClass ? 'addClass' : 'removeClass'](CHECKED_CSS);
346 }
347
348 }
349 }
350
351 /**
352 * Inject ARIA-specific attributes appropriate for each radio button
353 */
354 function configureAria( element, scope ){
355 scope.ariaId = buildAriaID();
356
357 element.attr({
358 'id' : scope.ariaId,
359 'role' : 'radio',
360 'aria-checked' : 'false'
361 });
362
363 $mdAria.expectWithText(element, 'aria-label');
364
365 /**
366 * Build a unique ID for each radio button that will be used with aria-activedescendant.
367 * Preserve existing ID if already specified.
368 * @returns {*|string}
369 */
370 function buildAriaID() {
371 return attr.id || ( 'radio' + "_" + $mdUtil.nextUid() );
372 }
373 }
374 }
375}
376
377ngmaterial.components.radioButton = angular.module("material.components.radioButton");