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