arnaucube
arnaucube/commonroutesAppPublic Self-hosted repository
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);