source: trip-planner-front/node_modules/angular-material/modules/closure/radioButton/radioButton.js@ 6a3a178

Last change on this file since 6a3a178 was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

  • Property mode set to 100644
File size: 13.1 KB
Line 
1/*!
2 * AngularJS Material Design
3 * https://github.com/angular/material
4 * @license MIT
5 * v1.2.3
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 * @type {Readonly<{NEXT: number, CURRENT: number, PREVIOUS: number}>}
24 */
25var incrementSelection = Object.freeze({PREVIOUS: -1, CURRENT: 0, NEXT: 1});
26
27/**
28 * @ngdoc directive
29 * @module material.components.radioButton
30 * @name mdRadioGroup
31 *
32 * @restrict E
33 *
34 * @description
35 * The `<md-radio-group>` directive identifies a grouping
36 * container for the 1..n grouped radio buttons; specified using nested
37 * `<md-radio-button>` elements.
38 *
39 * The radio button uses the accent color by default. The primary color palette may be used with
40 * the `md-primary` class.
41 *
42 * Note: `<md-radio-group>` and `<md-radio-button>` handle `tabindex` differently
43 * than the native `<input type="radio">` controls. Whereas the native controls
44 * force the user to tab through all the radio buttons, `<md-radio-group>`
45 * is focusable and by default the `<md-radio-button>`s are not.
46 *
47 * @param {string} ng-model Assignable angular expression to data-bind to.
48 * @param {string=} ng-change AngularJS expression to be executed when input changes due to user
49 * interaction.
50 * @param {boolean=} md-no-ink If present, disables ink ripple effects.
51 *
52 * @usage
53 * <hljs lang="html">
54 * <md-radio-group ng-model="selected">
55 * <md-radio-button ng-repeat="item in items"
56 * ng-value="item.value" aria-label="{{item.label}}">
57 * {{ item.label }}
58 * </md-radio-button>
59 * </md-radio-group>
60 * </hljs>
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, controllers) {
73 // private md component indicator for styling
74 element.addClass('_md');
75 $mdTheming(element);
76
77 var radioGroupController = controllers[0];
78 var ngModelCtrl = controllers[1] || $mdUtil.fakeNgModel();
79
80 radioGroupController.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() {
91 scope.mouseActive = true;
92 $timeout(function() {
93 scope.mouseActive = false;
94 }, 100);
95 })
96 .on('focus', function() {
97 if (scope.mouseActive === false) {
98 radioGroupController.$element.addClass('md-focused');
99 }
100 })
101 .on('blur', function() {
102 radioGroupController.$element.removeClass('md-focused');
103 });
104
105 // Initially set the first radio button as the aria-activedescendant. This will be overridden
106 // if a 'checked' radio button gets rendered. We need to wait for the nextTick here so that the
107 // radio buttons have their id values assigned.
108 $mdUtil.nextTick(function () {
109 var radioButtons = getRadioButtons(radioGroupController.$element);
110 if (radioButtons.count() &&
111 !radioGroupController.$element[0].hasAttribute('aria-activedescendant')) {
112 radioGroupController.setActiveDescendant(radioButtons.first().id);
113 }
114 });
115
116 /**
117 * Apply the md-focused class if it isn't already applied.
118 */
119 function setFocus() {
120 if (!element.hasClass('md-focused')) { element.addClass('md-focused'); }
121 }
122
123 /**
124 * @param {KeyboardEvent} keyboardEvent
125 */
126 function keydownListener(keyboardEvent) {
127 var keyCode = keyboardEvent.which || keyboardEvent.keyCode;
128
129 // Only listen to events that we originated ourselves
130 // so that we don't trigger on things like arrow keys in inputs.
131 if (keyCode !== $mdConstant.KEY_CODE.ENTER &&
132 keyboardEvent.currentTarget !== keyboardEvent.target) {
133 return;
134 }
135
136 switch (keyCode) {
137 case $mdConstant.KEY_CODE.LEFT_ARROW:
138 case $mdConstant.KEY_CODE.UP_ARROW:
139 keyboardEvent.preventDefault();
140 radioGroupController.selectPrevious();
141 setFocus();
142 break;
143
144 case $mdConstant.KEY_CODE.RIGHT_ARROW:
145 case $mdConstant.KEY_CODE.DOWN_ARROW:
146 keyboardEvent.preventDefault();
147 radioGroupController.selectNext();
148 setFocus();
149 break;
150
151 case $mdConstant.KEY_CODE.SPACE:
152 keyboardEvent.preventDefault();
153 radioGroupController.selectCurrent();
154 break;
155
156 case $mdConstant.KEY_CODE.ENTER:
157 var form = angular.element($mdUtil.getClosest(element[0], 'form'));
158 if (form.length > 0) {
159 form.triggerHandler('submit');
160 }
161 break;
162 }
163 }
164 }
165
166 /**
167 * @param {JQLite} $element
168 * @constructor
169 */
170 function RadioGroupController($element) {
171 this._radioButtonRenderFns = [];
172 this.$element = $element;
173 }
174
175 function createRadioGroupControllerProto() {
176 return {
177 init: function(ngModelCtrl) {
178 this._ngModelCtrl = ngModelCtrl;
179 this._ngModelCtrl.$render = angular.bind(this, this.render);
180 },
181 add: function(rbRender) {
182 this._radioButtonRenderFns.push(rbRender);
183 },
184 remove: function(rbRender) {
185 var index = this._radioButtonRenderFns.indexOf(rbRender);
186 if (index !== -1) {
187 this._radioButtonRenderFns.splice(index, 1);
188 }
189 },
190 render: function() {
191 this._radioButtonRenderFns.forEach(function(rbRender) {
192 rbRender();
193 });
194 },
195 setViewValue: function(value, eventType) {
196 this._ngModelCtrl.$setViewValue(value, eventType);
197 // update the other radio buttons as well
198 this.render();
199 },
200 getViewValue: function() {
201 return this._ngModelCtrl.$viewValue;
202 },
203 selectCurrent: function() {
204 return changeSelectedButton(this.$element, incrementSelection.CURRENT);
205 },
206 selectNext: function() {
207 return changeSelectedButton(this.$element, incrementSelection.NEXT);
208 },
209 selectPrevious: function() {
210 return changeSelectedButton(this.$element, incrementSelection.PREVIOUS);
211 },
212 setActiveDescendant: function (radioId) {
213 this.$element.attr('aria-activedescendant', radioId);
214 },
215 isDisabled: function() {
216 return this.$element[0].hasAttribute('disabled');
217 }
218 };
219 }
220
221 /**
222 * Coerce all child radio buttons into an array, then wrap them in an iterator.
223 * @param parent {!JQLite}
224 * @return {{add: add, next: (function()), last: (function(): any|null), previous: (function()), count: (function(): number), hasNext: (function(*=): Array.length|*|number|boolean), inRange: (function(*): boolean), remove: remove, contains: (function(*=): *|boolean), itemAt: (function(*=): any|null), findBy: (function(*, *): *[]), hasPrevious: (function(*=): Array.length|*|number|boolean), items: (function(): *[]), indexOf: (function(*=): number), first: (function(): any|null)}}
225 */
226 function getRadioButtons(parent) {
227 return $mdUtil.iterator(parent[0].querySelectorAll('md-radio-button'), true);
228 }
229
230 /**
231 * Change the radio group's selected button by a given increment.
232 * If no button is selected, select the first button.
233 * @param {JQLite} parent the md-radio-group
234 * @param {incrementSelection} increment enum that determines whether the next or
235 * previous button is clicked. For current, only the first button is selected, otherwise the
236 * current selection is maintained (by doing nothing).
237 */
238 function changeSelectedButton(parent, increment) {
239 var buttons = getRadioButtons(parent);
240 var target;
241
242 if (buttons.count()) {
243 var validate = function (button) {
244 // If disabled, then NOT valid
245 return !angular.element(button).attr("disabled");
246 };
247
248 var selected = parent[0].querySelector('md-radio-button.md-checked');
249 if (!selected) {
250 target = buttons.first();
251 } else if (increment === incrementSelection.PREVIOUS ||
252 increment === incrementSelection.NEXT) {
253 target = buttons[
254 increment === incrementSelection.PREVIOUS ? 'previous' : 'next'
255 ](selected, validate);
256 }
257
258 if (target) {
259 // Activate radioButton's click listener (triggerHandler won't create a real click event)
260 angular.element(target).triggerHandler('click');
261 }
262 }
263 }
264}
265
266/**
267 * @ngdoc directive
268 * @module material.components.radioButton
269 * @name mdRadioButton
270 *
271 * @restrict E
272 *
273 * @description
274 * The `<md-radio-button>`directive is the child directive required to be used within `<md-radio-group>` elements.
275 *
276 * While similar to the `<input type="radio" ng-model="" value="">` directive,
277 * the `<md-radio-button>` directive provides ink effects, ARIA support, and
278 * supports use within named radio groups.
279 *
280 * One of `value` or `ng-value` must be set so that the `md-radio-group`'s model is set properly when the
281 * `md-radio-button` is selected.
282 *
283 * @param {string} value The value to which the model should be set when selected.
284 * @param {string} ng-value AngularJS expression which sets the value to which the model should
285 * be set when selected.
286 * @param {string=} name Property name of the form under which the control is published.
287 * @param {string=} aria-label Adds label to radio button for accessibility.
288 * Defaults to radio button's text. If no text content is available, a warning will be logged.
289 *
290 * @usage
291 * <hljs lang="html">
292 *
293 * <md-radio-button value="1" aria-label="Label 1">
294 * Label 1
295 * </md-radio-button>
296 *
297 * <md-radio-button ng-value="specialValue" aria-label="Green">
298 * Green
299 * </md-radio-button>
300 *
301 * </hljs>
302 *
303 */
304function mdRadioButtonDirective($mdAria, $mdUtil, $mdTheming) {
305
306 var CHECKED_CSS = 'md-checked';
307
308 return {
309 restrict: 'E',
310 require: '^mdRadioGroup',
311 transclude: true,
312 template: '<div class="md-container" md-ink-ripple md-ink-ripple-checkbox>' +
313 '<div class="md-off"></div>' +
314 '<div class="md-on"></div>' +
315 '</div>' +
316 '<div ng-transclude class="md-label"></div>',
317 link: link
318 };
319
320 function link(scope, element, attr, radioGroupController) {
321 var lastChecked;
322
323 $mdTheming(element);
324 configureAria(element);
325 element.addClass('md-auto-horizontal-margin');
326
327 // ngAria overwrites the aria-checked inside a $watch for ngValue.
328 // We should defer the initialization until all the watches have fired.
329 // This can also be fixed by removing the `lastChecked` check, but that'll
330 // cause more DOM manipulation on each digest.
331 if (attr.ngValue) {
332 $mdUtil.nextTick(initialize, false);
333 } else {
334 initialize();
335 }
336
337 /**
338 * Initializes the component.
339 */
340 function initialize() {
341 if (!radioGroupController) {
342 throw 'RadioButton: No RadioGroupController could be found.';
343 }
344
345 radioGroupController.add(render);
346 attr.$observe('value', render);
347
348 element
349 .on('click', listener)
350 .on('$destroy', function() {
351 radioGroupController.remove(render);
352 });
353 }
354
355 /**
356 * On click functionality.
357 */
358 function listener(ev) {
359 if (element[0].hasAttribute('disabled') || radioGroupController.isDisabled()) return;
360
361 scope.$apply(function() {
362 radioGroupController.setViewValue(attr.value, ev && ev.type);
363 });
364 }
365
366 /**
367 * Add or remove the `.md-checked` class from the RadioButton (and conditionally its parent).
368 * Update the `aria-activedescendant` attribute.
369 */
370 function render() {
371 var checked = radioGroupController.getViewValue() == attr.value;
372
373 if (checked === lastChecked) return;
374
375 if (element[0] && element[0].parentNode &&
376 element[0].parentNode.nodeName.toLowerCase() !== 'md-radio-group') {
377 // If the radioButton is inside a div, then add class so highlighting will work.
378 element.parent().toggleClass(CHECKED_CSS, checked);
379 }
380
381 if (checked) {
382 radioGroupController.setActiveDescendant(element.attr('id'));
383 }
384
385 lastChecked = checked;
386
387 element
388 .attr('aria-checked', checked)
389 .toggleClass(CHECKED_CSS, checked);
390 }
391
392 /**
393 * Inject ARIA-specific attributes appropriate for each radio button
394 */
395 function configureAria(element) {
396 element.attr({
397 id: attr.id || 'radio_' + $mdUtil.nextUid(),
398 role: 'radio',
399 'aria-checked': 'false'
400 });
401
402 $mdAria.expectWithText(element, 'aria-label');
403 }
404 }
405}
406
407ngmaterial.components.radioButton = angular.module("material.components.radioButton");
Note: See TracBrowser for help on using the repository browser.