peterhudec/authomatic

View on GitHub
examples/gae/showcase/static/js/foundation/foundation.forms.js

Summary

Maintainability
D
2 days
Test Coverage
/*jslint unparam: true, browser: true, indent: 2 */

;(function ($, window, document, undefined) {
  'use strict';

  Foundation.libs.forms = {
    name : 'forms',

    version : '4.0.4',

    settings : {
      disable_class: 'no-custom'
    },

    init : function (scope, method, options) {
      this.scope = scope || this.scope;

      if (typeof method === 'object') {
        $.extend(true, this.settings, method);
      }

      if (typeof method != 'string') {
        if (!this.settings.init) {
          this.events();
        }

        this.assemble();

        return this.settings.init;
      } else {
        return this[method].call(this, options);
      }
    },

    assemble : function () {
      $('form.custom input[type="radio"]', $(this.scope)).not('[data-customforms="disabled"]')
        .each(this.append_custom_markup);
      $('form.custom input[type="checkbox"]', $(this.scope)).not('[data-customforms="disabled"]')
        .each(this.append_custom_markup);
      $('form.custom select', $(this.scope)).not('[data-customforms="disabled"]')
        .each(this.append_custom_select);
    },

    events : function () {
      var self = this;

      $(this.scope)
        .on('change.fndtn.forms', 'form.custom select:not([data-customforms="disabled"])', function (e) {
          self.refresh_custom_select($(this));
        })
        .on('click.fndtn.forms', 'form.custom label', function (e) {
          var $associatedElement = $('#' + self.escape($(this).attr('for')) + ':not([data-customforms="disabled"])'),
              $customCheckbox,
              $customRadio;
          if ($associatedElement.length !== 0) {
            if ($associatedElement.attr('type') === 'checkbox') {
              e.preventDefault();
              $customCheckbox = $(this).find('span.custom.checkbox');

              //the checkbox might be outside after the label
              if ($customCheckbox.length == 0) {
                $customCheckbox = $(this).next('span.custom.checkbox');
              }
              //the checkbox might be outside before the label
              if ($customCheckbox.length == 0) {
                $customCheckbox = $(this).prev('span.custom.checkbox');
              }
              self.toggle_checkbox($customCheckbox);
            } else if ($associatedElement.attr('type') === 'radio') {
              e.preventDefault();
              $customRadio = $(this).find('span.custom.radio');
              //the radio might be outside after the label
              if ($customRadio.length == 0) {
                $customRadio = $(this).next('span.custom.radio');
              }
              //the radio might be outside before the label
              if ($customRadio.length == 0) {
                $customRadio = $(this).prev('span.custom.radio');
              }
              self.toggle_radio($customRadio);
            }
          }
        })
        .on('click.fndtn.forms', 'form.custom div.custom.dropdown a.current, form.custom div.custom.dropdown a.selector', function (e) {
          var $this = $(this),
              $dropdown = $this.closest('div.custom.dropdown'),
              $select = $dropdown.prev();

          // make sure other dropdowns close
          if(!$dropdown.hasClass('open'))
            $(self.scope).trigger('click');

          e.preventDefault();
          if (false === $select.is(':disabled')) {
            $dropdown.toggleClass('open');

            if ($dropdown.hasClass('open')) {
              $(self.scope).on('click.fndtn.forms.customdropdown', function () {
                $dropdown.removeClass('open');
                $(self.scope).off('.fndtn.forms.customdropdown');
              });
            } else {
              $(self.scope).on('.fndtn.forms.customdropdown');
            }
            return false;
          }
        })
        .on('click.fndtn.forms touchend.fndtn.forms', 'form.custom div.custom.dropdown li', function (e) {
          var $this = $(this),
              $customDropdown = $this.closest('div.custom.dropdown'),
              $select = $customDropdown.prev(),
              selectedIndex = 0;

          e.preventDefault();
          e.stopPropagation();

          if ( ! $(this).hasClass('disabled')) {
            $('div.dropdown').not($customDropdown).removeClass('open');

            var $oldThis= $this
              .closest('ul')
              .find('li.selected');
            $oldThis.removeClass('selected');

            $this.addClass('selected');

            $customDropdown
              .removeClass('open')
              .find('a.current')
              .html($this.html());

            $this.closest('ul').find('li').each(function (index) {
              if ($this[0] == this) {
                selectedIndex = index;
              }

            });
            $select[0].selectedIndex = selectedIndex;

            //store the old value in data
            $select.data('prevalue', $oldThis.html());
            $select.trigger('change');
          }
        });

      this.settings.init = true;
    },

    append_custom_markup : function (idx, sel) {
      var $this = $(sel).hide(),
          type  = $this.attr('type'),
          $span = $this.next('span.custom.' + type);

      if ($span.length === 0) {
        $span = $('<span class="custom ' + type + '"></span>').insertAfter($this);
      }

      $span.toggleClass('checked', $this.is(':checked'));
      $span.toggleClass('disabled', $this.is(':disabled'));
    },

    append_custom_select : function (idx, sel) {
      var self = Foundation.libs.forms,
          $this = $( sel ),
          $customSelect = $this.next( 'div.custom.dropdown' ),
          $customList = $customSelect.find( 'ul' ),
          $selectCurrent = $customSelect.find( ".current" ),
          $selector = $customSelect.find( ".selector" ),
          $options = $this.find( 'option' ),
          $selectedOption = $options.filter( ':selected' ),
          copyClasses = $this.attr('class') ? $this.attr('class').split(' ') : [],
          maxWidth = 0,
          liHtml = '',
          $listItems,
          $currentSelect = false;

      if ($this.hasClass(self.settings.disable_class)) return;

      if ($customSelect.length === 0) {
        var customSelectSize = $this.hasClass( 'small' )   ? 'small'   :
                               $this.hasClass( 'medium' )  ? 'medium'  :
                               $this.hasClass( 'large' )   ? 'large'   :
                               $this.hasClass( 'expand' )  ? 'expand'  : '';

        $customSelect = $('<div class="' + ['custom', 'dropdown', customSelectSize ].concat(copyClasses).filter(function(item, idx,arr){ if(item == '') return false; return arr.indexOf(item) == idx; }).join( ' ' ) + '"><a href="#" class="selector"></a><ul /></div>');
        $selector = $customSelect.find(".selector");
        $customList = $customSelect.find("ul");
        liHtml = $options.map(function() { return "<li>" + $( this ).html() + "</li>"; } ).get().join( '' );
        $customList.append(liHtml);
        $currentSelect = $customSelect.prepend('<a href="#" class="current">' + $selectedOption.html() + '</a>' ).find( ".current" );
        $this
          .after( $customSelect )
          .hide();

      } else {
        liHtml = $options.map(function() {
            return "<li>" + $( this ).html() + "</li>";
          })
          .get().join('');
        $customList
          .html('')
          .append(liHtml);

      } // endif $customSelect.length === 0
      $customSelect.toggleClass('disabled', $this.is( ':disabled' ) );
      $listItems = $customList.find( 'li' );

      $options.each( function ( index ) {
        if ( this.selected ) {
          $listItems.eq( index ).addClass( 'selected' );

          if ($currentSelect) {
            $currentSelect.html( $( this ).html() );
          }

        }
        if ($(this).is(':disabled')) {
          $listItems.eq( index ).addClass( 'disabled' );
        }
      });

      //
      // If we're not specifying a predetermined form size.
      //
      if (!$customSelect.is('.small, .medium, .large, .expand')) {

        // ------------------------------------------------------------------------------------
        // This is a work-around for when elements are contained within hidden parents.
        // For example, when custom-form elements are inside of a hidden reveal modal.
        //
        // We need to display the current custom list element as well as hidden parent elements
        // in order to properly calculate the list item element's width property.
        // -------------------------------------------------------------------------------------

        $customSelect.addClass( 'open' );
        //
        // Quickly, display all parent elements.
        // This should help us calculate the width of the list item's within the drop down.
        //
        var self = Foundation.libs.forms;
        self.hidden_fix.adjust( $customList );

        maxWidth = ( self.outerWidth($listItems) > maxWidth ) ? self.outerWidth($listItems) : maxWidth;

        Foundation.libs.forms.hidden_fix.reset();

        $customSelect.removeClass( 'open' );

      } // endif

    },

    refresh_custom_select : function ($select) {
      var self = this;
      var maxWidth = 0,
        $customSelect = $select.next(),
        $options = $select.find('option');

      $customSelect.find('ul').html('');

      $options.each(function () {
        var $li = $('<li>' + $(this).html() + '</li>');
        $customSelect.find('ul').append($li);
      });

      // re-populate
      $options.each(function (index) {
        if (this.selected) {
          $customSelect.find('li').eq(index).addClass('selected');
          $customSelect.find('.current').html($(this).html());
        }
        if ($(this).is(':disabled')) {
          $customSelect.find('li').eq(index).addClass('disabled');
        }
      });

      // fix width
      $customSelect.removeAttr('style')
        .find('ul').removeAttr('style');
      $customSelect.find('li').each(function () {
        $customSelect.addClass('open');
        if (self.outerWidth($(this)) > maxWidth) {
          maxWidth = self.outerWidth($(this));
        }
        $customSelect.removeClass('open');
      });
    },

    toggle_checkbox : function ($element) {
      var $input = $element.prev(),
          input = $input[0];

      if (false === $input.is(':disabled')) {
        input.checked = ((input.checked) ? false : true);
        $element.toggleClass('checked');

        $input.trigger('change');
      }
    },

    toggle_radio : function ($element) {
      var $input = $element.prev(),
          $form = $input.closest('form.custom'),
          input = $input[0];

      if (false === $input.is(':disabled')) {
        $form.find('input[type="radio"][name="' + this.escape($input.attr('name')) + '"]').next().not($element).removeClass('checked');
        if ( !$element.hasClass('checked') ) {
          $element.toggleClass('checked');
        }
        input.checked = $element.hasClass('checked');

        $input.trigger('change');
      }
    },

    escape : function (text) {
      return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
    },

    hidden_fix : {
      /**
       * Sets all hidden parent elements and self to visible.
       *
       * @method adjust
       * @param {jQuery Object} $child
       */

      // We'll use this to temporarily store style properties.
      tmp : [],

      // We'll use this to set hidden parent elements.
      hidden : null,

      adjust : function( $child ) {
        // Internal reference.
        var _self = this;

        // Set all hidden parent elements, including this element.
        _self.hidden = $child.parents().andSelf().filter( ":hidden" );

        // Loop through all hidden elements.
        _self.hidden.each( function() {

          // Cache the element.
          var $elem = $( this );

          // Store the style attribute.
          // Undefined if element doesn't have a style attribute.
          _self.tmp.push( $elem.attr( 'style' ) );

          // Set the element's display property to block,
          // but ensure it's visibility is hidden.
          $elem.css( { 'visibility' : 'hidden', 'display' : 'block' } );
        });

      }, // end adjust

      /**
       * Resets the elements previous state.
       *
       * @method reset
       */
      reset : function() {
        // Internal reference.
        var _self = this;
        // Loop through our hidden element collection.
        _self.hidden.each( function( i ) {
          // Cache this element.
          var $elem = $( this ),
              _tmp = _self.tmp[ i ]; // Get the stored 'style' value for this element.

          // If the stored value is undefined.
          if( _tmp === undefined )
            // Remove the style attribute.
            $elem.removeAttr( 'style' );
          else
            // Otherwise, reset the element style attribute.
            $elem.attr( 'style', _tmp );

        });
        // Reset the tmp array.
        _self.tmp = [];
        // Reset the hidden elements variable.
        _self.hidden = null;

      } // end reset

    },

    off : function () {
      $(this.scope).off('.fndtn.forms');
    }
  };
}(Foundation.zj, this, this.document));