2  * Angular Material Design
 
   3  * https://github.com/angular/material
 
   7 goog.provide('ngmaterial.components.radioButton');
 
   8 goog.require('ngmaterial.core');
 
  11  * @name material.components.radioButton
 
  12  * @description radioButton module!
 
  14 mdRadioGroupDirective['$inject'] = ["$mdUtil", "$mdConstant", "$mdTheming", "$timeout"];
 
  15 mdRadioButtonDirective['$inject'] = ["$mdAria", "$mdUtil", "$mdTheming"];
 
  16 angular.module('material.components.radioButton', [
 
  19   .directive('mdRadioGroup', mdRadioGroupDirective)
 
  20   .directive('mdRadioButton', mdRadioButtonDirective);
 
  24  * @module material.components.radioButton
 
  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.
 
  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.
 
  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.
 
  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.
 
  48  * <md-radio-group ng-model="selected">
 
  51  *        ng-repeat="d in colorOptions"
 
  52  *        ng-value="d.value" aria-label="{{ d.label }}">
 
  62 function mdRadioGroupDirective($mdUtil, $mdConstant, $mdTheming, $timeout) {
 
  63   RadioGroupController.prototype = createRadioGroupControllerProto();
 
  67     controller: ['$element', RadioGroupController],
 
  68     require: ['mdRadioGroup', '?ngModel'],
 
  69     link: { pre: linkRadioGroup }
 
  72   function linkRadioGroup(scope, element, attr, ctrls) {
 
  73     element.addClass('_md');     // private md component indicator for styling
 
  76     var rgCtrl = ctrls[0];
 
  77     var ngModelCtrl = ctrls[1] || $mdUtil.fakeNgModel();
 
  79     rgCtrl.init(ngModelCtrl);
 
  81     scope.mouseActive = false;
 
  86         'tabIndex': element.attr('tabindex') || '0'
 
  88       .on('keydown', keydownListener)
 
  89       .on('mousedown', function(event) {
 
  90         scope.mouseActive = true;
 
  92           scope.mouseActive = false;
 
  95       .on('focus', function() {
 
  96         if(scope.mouseActive === false) {
 
  97           rgCtrl.$element.addClass('md-focused');
 
 100       .on('blur', function() {
 
 101         rgCtrl.$element.removeClass('md-focused');
 
 107     function setFocus() {
 
 108       if (!element.hasClass('md-focused')) { element.addClass('md-focused'); }
 
 114     function keydownListener(ev) {
 
 115       var keyCode = ev.which || ev.keyCode;
 
 117       // Only listen to events that we originated ourselves
 
 118       // so that we don't trigger on things like arrow keys in
 
 121       if (keyCode != $mdConstant.KEY_CODE.ENTER &&
 
 122           ev.currentTarget != ev.target) {
 
 127         case $mdConstant.KEY_CODE.LEFT_ARROW:
 
 128         case $mdConstant.KEY_CODE.UP_ARROW:
 
 130           rgCtrl.selectPrevious();
 
 134         case $mdConstant.KEY_CODE.RIGHT_ARROW:
 
 135         case $mdConstant.KEY_CODE.DOWN_ARROW:
 
 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');
 
 152   function RadioGroupController($element) {
 
 153     this._radioButtonRenderFns = [];
 
 154     this.$element = $element;
 
 157   function createRadioGroupControllerProto() {
 
 159       init: function(ngModelCtrl) {
 
 160         this._ngModelCtrl = ngModelCtrl;
 
 161         this._ngModelCtrl.$render = angular.bind(this, this.render);
 
 163       add: function(rbRender) {
 
 164         this._radioButtonRenderFns.push(rbRender);
 
 166       remove: function(rbRender) {
 
 167         var index = this._radioButtonRenderFns.indexOf(rbRender);
 
 169           this._radioButtonRenderFns.splice(index, 1);
 
 173         this._radioButtonRenderFns.forEach(function(rbRender) {
 
 177       setViewValue: function(value, eventType) {
 
 178         this._ngModelCtrl.$setViewValue(value, eventType);
 
 179         // update the other radio buttons as well
 
 182       getViewValue: function() {
 
 183         return this._ngModelCtrl.$viewValue;
 
 185       selectNext: function() {
 
 186         return changeSelectedButton(this.$element, 1);
 
 188       selectPrevious: function() {
 
 189         return changeSelectedButton(this.$element, -1);
 
 191       setActiveDescendant: function (radioId) {
 
 192         this.$element.attr('aria-activedescendant', radioId);
 
 194       isDisabled: function() {
 
 195         return this.$element[0].hasAttribute('disabled');
 
 200    * Change the radio group's selected button by a given increment.
 
 201    * If no button is selected, select the first button.
 
 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);
 
 207     if (buttons.count()) {
 
 208       var validate = function (button) {
 
 209         // If disabled, then NOT valid
 
 210         return !angular.element(button).attr("disabled");
 
 213       var selected = parent[0].querySelector('md-radio-button.md-checked');
 
 214       var target = buttons[increment < 0 ? 'previous' : 'next'](selected, validate) || buttons.first();
 
 216       // Activate radioButton's click listener (triggerHandler won't create a real click event)
 
 217       angular.element(target).triggerHandler('click');
 
 225  * @module material.components.radioButton
 
 226  * @name mdRadioButton
 
 231  * The `<md-radio-button>`directive is the child directive required to be used within `<md-radio-group>` elements.
 
 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.
 
 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.
 
 250  * <md-radio-button value="1" aria-label="Label 1">
 
 254  * <md-radio-button ng-model="color" ng-value="specialValue" aria-label="Green">
 
 261 function mdRadioButtonDirective($mdAria, $mdUtil, $mdTheming) {
 
 263   var CHECKED_CSS = 'md-checked';
 
 267     require: '^mdRadioGroup',
 
 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>' +
 
 273               '<div ng-transclude class="md-label"></div>',
 
 277   function link(scope, element, attr, rgCtrl) {
 
 281     configureAria(element, scope);
 
 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.
 
 288       $mdUtil.nextTick(initialize, false);
 
 294      * Initializes the component.
 
 296     function initialize() {
 
 298         throw 'RadioButton: No RadioGroupController could be found.';
 
 302       attr.$observe('value', render);
 
 305         .on('click', listener)
 
 306         .on('$destroy', function() {
 
 307           rgCtrl.remove(render);
 
 312      * On click functionality.
 
 314     function listener(ev) {
 
 315       if (element[0].hasAttribute('disabled') || rgCtrl.isDisabled()) return;
 
 317       scope.$apply(function() {
 
 318         rgCtrl.setViewValue(attr.value, ev && ev.type);
 
 323      *  Add or remove the `.md-checked` class from the RadioButton (and conditionally its parent).
 
 324      *  Update the `aria-activedescendant` attribute.
 
 327       var checked = rgCtrl.getViewValue() == attr.value;
 
 329       if (checked === lastChecked) return;
 
 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);
 
 337         rgCtrl.setActiveDescendant(element.attr('id'));
 
 340       lastChecked = checked;
 
 343         .attr('aria-checked', checked)
 
 344         .toggleClass(CHECKED_CSS, checked);
 
 348      * Inject ARIA-specific attributes appropriate for each radio button
 
 350     function configureAria(element, scope){
 
 352         id: attr.id || 'radio_' + $mdUtil.nextUid(),
 
 354         'aria-checked': 'false'
 
 357       $mdAria.expectWithText(element, 'aria-label');
 
 362 ngmaterial.components.radioButton = angular.module("material.components.radioButton");