/components/foundation/javascripts/jquery.foundation.forms.js
JavaScript | 486 lines | 365 code | 28 blank | 93 comment | 6 complexity | 67a3f0d91d5fc4142fe71cda2de49271 MD5 | raw file
1/*
2 * jQuery Custom Forms Plugin 1.0
3 * www.ZURB.com
4 * Copyright 2010, ZURB
5 * Free to use under the MIT license.
6 * http://www.opensource.org/licenses/mit-license.php
7*/
8
9(function( $ ){
10
11 /**
12 * Helper object used to quickly adjust all hidden parent element's, display and visibility properties.
13 * This is currently used for the custom drop downs. When the dropdowns are contained within a reveal modal
14 * we cannot accurately determine the list-item elements width property, since the modal's display property is set
15 * to 'none'.
16 *
17 * This object will help us work around that problem.
18 *
19 * NOTE: This could also be plugin.
20 *
21 * @function hiddenFix
22 */
23 var hiddenFix = function() {
24
25 return {
26 /**
27 * Sets all hidden parent elements and self to visibile.
28 *
29 * @method adjust
30 * @param {jQuery Object} $child
31 */
32
33 // We'll use this to temporarily store style properties.
34 tmp : [],
35
36 // We'll use this to set hidden parent elements.
37 hidden : null,
38
39 adjust : function( $child ) {
40 // Internal reference.
41 var _self = this;
42
43 // Set all hidden parent elements, including this element.
44 _self.hidden = $child.parents().andSelf().filter( ":hidden" );
45
46 // Loop through all hidden elements.
47 _self.hidden.each( function() {
48
49 // Cache the element.
50 var $elem = $( this );
51
52 // Store the style attribute.
53 // Undefined if element doesn't have a style attribute.
54 _self.tmp.push( $elem.attr( 'style' ) );
55
56 // Set the element's display property to block,
57 // but ensure it's visibility is hidden.
58 $elem.css( { 'visibility' : 'hidden', 'display' : 'block' } );
59 });
60
61 }, // end adjust
62
63 /**
64 * Resets the elements previous state.
65 *
66 * @method reset
67 */
68 reset : function() {
69 // Internal reference.
70 var _self = this;
71 // Loop through our hidden element collection.
72 _self.hidden.each( function( i ) {
73 // Cache this element.
74 var $elem = $( this ),
75 _tmp = _self.tmp[ i ]; // Get the stored 'style' value for this element.
76
77 // If the stored value is undefined.
78 if( _tmp === undefined )
79 // Remove the style attribute.
80 $elem.removeAttr( 'style' );
81 else
82 // Otherwise, reset the element style attribute.
83 $elem.attr( 'style', _tmp );
84
85 });
86 // Reset the tmp array.
87 _self.tmp = [];
88 // Reset the hidden elements variable.
89 _self.hidden = null;
90
91 } // end reset
92
93 }; // end return
94
95 };
96
97 jQuery.foundation = jQuery.foundation || {};
98 jQuery.foundation.customForms = jQuery.foundation.customForms || {};
99
100 $.foundation.customForms.appendCustomMarkup = function ( options ) {
101
102 var defaults = {
103 disable_class: "js-disable-custom"
104 };
105
106 options = $.extend( defaults, options );
107
108 function appendCustomMarkup(idx, sel) {
109 var $this = $(sel).hide(),
110 type = $this.attr('type'),
111 $span = $this.next('span.custom.' + type);
112
113 if ($span.length === 0) {
114 $span = $('<span class="custom ' + type + '"></span>').insertAfter($this);
115 }
116
117 $span.toggleClass('checked', $this.is(':checked'));
118 $span.toggleClass('disabled', $this.is(':disabled'));
119 }
120
121 function appendCustomSelect(idx, sel) {
122 var hiddenFixObj = hiddenFix();
123 //
124 // jQueryify the <select> element and cache it.
125 //
126 var $this = $( sel ),
127 //
128 // Find the custom drop down element.
129 //
130 $customSelect = $this.next( 'div.custom.dropdown' ),
131 //
132 // Find the custom select element within the custom drop down.
133 //
134 $customList = $customSelect.find( 'ul' ),
135 //
136 // Find the custom a.current element.
137 //
138 $selectCurrent = $customSelect.find( ".current" ),
139 //
140 // Find the custom a.selector element (the drop-down icon).
141 //
142 $selector = $customSelect.find( ".selector" ),
143 //
144 // Get the <options> from the <select> element.
145 //
146 $options = $this.find( 'option' ),
147 //
148 // Filter down the selected options
149 //
150 $selectedOption = $options.filter( ':selected' ),
151 //
152 // Initial max width.
153 //
154 maxWidth = 0,
155 //
156 // We'll use this variable to create the <li> elements for our custom select.
157 //
158 liHtml = '',
159 //
160 // We'll use this to cache the created <li> elements within our custom select.
161 //
162 $listItems
163 ;
164 var $currentSelect = false;
165 //
166 // Should we not create a custom list?
167 //
168 if ( $this.hasClass( 'no-custom' ) ) return;
169
170 //
171 // Did we not create a custom select element yet?
172 //
173 if ( $customSelect.length === 0 ) {
174 //
175 // Let's create our custom select element!
176 //
177
178 //
179 // Determine what select size to use.
180 //
181 var customSelectSize = $this.hasClass( 'small' ) ? 'small' :
182 $this.hasClass( 'medium' ) ? 'medium' :
183 $this.hasClass( 'large' ) ? 'large' :
184 $this.hasClass( 'expand' ) ? 'expand' : ''
185 ;
186 //
187 // Build our custom list.
188 //
189 $customSelect = $('<div class="' + ['custom', 'dropdown', customSelectSize ].join( ' ' ) + '"><a href="#" class="selector"></a><ul /></div>"');
190 //
191 // Grab the selector element
192 //
193 $selector = $customSelect.find( ".selector" );
194 //
195 // Grab the unordered list element from the custom list.
196 //
197 $customList = $customSelect.find( "ul" );
198 //
199 // Build our <li> elements.
200 //
201 liHtml = $options.map( function() { return "<li>" + $( this ).html() + "</li>"; } ).get().join( '' );
202 //
203 // Append our <li> elements to the custom list (<ul>).
204 //
205 $customList.append( liHtml );
206 //
207 // Insert the the currently selected list item before all other elements.
208 // Then, find the element and assign it to $currentSelect.
209 //
210
211 $currentSelect = $customSelect.prepend( '<a href="#" class="current">' + $selectedOption.html() + '</a>' ).find( ".current" );
212 //
213 // Add the custom select element after the <select> element.
214 //
215 $this.after( $customSelect )
216 //
217 //then hide the <select> element.
218 //
219 .hide();
220
221 } else {
222 //
223 // Create our list item <li> elements.
224 //
225 liHtml = $options.map( function() { return "<li>" + $( this ).html() + "</li>"; } ).get().join( '' );
226 //
227 // Refresh the ul with options from the select in case the supplied markup doesn't match.
228 // Clear what's currently in the <ul> element.
229 //
230 $customList.html( '' )
231 //
232 // Populate the list item <li> elements.
233 //
234 .append( liHtml );
235
236 } // endif $customSelect.length === 0
237
238 //
239 // Determine whether or not the custom select element should be disabled.
240 //
241 $customSelect.toggleClass( 'disabled', $this.is( ':disabled' ) );
242 //
243 // Cache our List item elements.
244 //
245 $listItems = $customList.find( 'li' );
246
247 //
248 // Determine which elements to select in our custom list.
249 //
250 $options.each( function ( index ) {
251
252 if ( this.selected ) {
253 //
254 // Add the selected class to the current li element
255 //
256 $listItems.eq( index ).addClass( 'selected' );
257 //
258 // Update the current element with the option value.
259 //
260 if ($currentSelect) {
261 $currentSelect.html( $( this ).html() );
262 }
263
264 }
265
266 });
267
268 //
269 // Update the custom <ul> list width property.
270 //
271 $customList.css( 'width', 'inherit' );
272 //
273 // Set the custom select width property.
274 //
275 $customSelect.css( 'width', 'inherit' );
276
277 //
278 // If we're not specifying a predetermined form size.
279 //
280 if ( !$customSelect.is( '.small, .medium, .large, .expand' ) ) {
281
282 // ------------------------------------------------------------------------------------
283 // This is a work-around for when elements are contained within hidden parents.
284 // For example, when custom-form elements are inside of a hidden reveal modal.
285 //
286 // We need to display the current custom list element as well as hidden parent elements
287 // in order to properly calculate the list item element's width property.
288 // -------------------------------------------------------------------------------------
289
290 //
291 // Show the drop down.
292 // This should ensure that the list item's width values are properly calculated.
293 //
294 $customSelect.addClass( 'open' );
295 //
296 // Quickly, display all parent elements.
297 // This should help us calcualate the width of the list item's within the drop down.
298 //
299 hiddenFixObj.adjust( $customList );
300 //
301 // Grab the largest list item width.
302 //
303 maxWidth = ( $listItems.outerWidth() > maxWidth ) ? $listItems.outerWidth() : maxWidth;
304 //
305 // Okay, now reset the parent elements.
306 // This will hide them again.
307 //
308 hiddenFixObj.reset();
309 //
310 // Finally, hide the drop down.
311 //
312 $customSelect.removeClass( 'open' );
313 //
314 // Set the custom list width.
315 //
316 $customSelect.width( maxWidth + 18);
317 //
318 // Set the custom list element (<ul />) width.
319 //
320 $customList.width( maxWidth + 16 );
321
322 } // endif
323
324 }
325
326 $('form.custom input:radio[data-customforms!=disabled]').each(appendCustomMarkup);
327 $('form.custom input:checkbox[data-customforms!=disabled]').each(appendCustomMarkup);
328 $('form.custom select[data-customforms!=disabled]').each(appendCustomSelect);
329 };
330
331 var refreshCustomSelect = function($select) {
332 var maxWidth = 0,
333 $customSelect = $select.next();
334 $options = $select.find('option');
335 $customSelect.find('ul').html('');
336
337 $options.each(function () {
338 $li = $('<li>' + $(this).html() + '</li>');
339 $customSelect.find('ul').append($li);
340 });
341
342 // re-populate
343 $options.each(function (index) {
344 if (this.selected) {
345 $customSelect.find('li').eq(index).addClass('selected');
346 $customSelect.find('.current').html($(this).html());
347 }
348 });
349
350 // fix width
351 $customSelect.removeAttr('style')
352 .find('ul').removeAttr('style');
353 $customSelect.find('li').each(function () {
354 $customSelect.addClass('open');
355 if ($(this).outerWidth() > maxWidth) {
356 maxWidth = $(this).outerWidth();
357 }
358 $customSelect.removeClass('open');
359 });
360 $customSelect.css('width', maxWidth + 18 + 'px');
361 $customSelect.find('ul').css('width', maxWidth + 16 + 'px');
362
363 };
364
365 var toggleCheckbox = function($element) {
366 var $input = $element.prev(),
367 input = $input[0];
368
369 if (false === $input.is(':disabled')) {
370 input.checked = ((input.checked) ? false : true);
371 $element.toggleClass('checked');
372
373 $input.trigger('change');
374 }
375 };
376
377 var toggleRadio = function($element) {
378 var $input = $element.prev(),
379 input = $input[0];
380
381 if (false === $input.is(':disabled')) {
382
383 $('input:radio[name="' + $input.attr('name') + '"]').next().not($element).removeClass('checked');
384 if ( !$element.hasClass('checked') ) {
385 $element.toggleClass('checked');
386 }
387 input.checked = $element.hasClass('checked');
388
389 $input.trigger('change');
390 }
391 };
392
393 $(document).on('click', 'form.custom span.custom.checkbox', function (event) {
394 event.preventDefault();
395 event.stopPropagation();
396
397 toggleCheckbox($(this));
398 });
399
400 $(document).on('click', 'form.custom span.custom.radio', function (event) {
401 event.preventDefault();
402 event.stopPropagation();
403
404 toggleRadio($(this));
405 });
406
407 $(document).on('change', 'form.custom select[data-customforms!=disabled]', function (event) {
408 refreshCustomSelect($(this));
409 });
410
411 $(document).on('click', 'form.custom label', function (event) {
412 var $associatedElement = $('#' + $(this).attr('for') + '[data-customforms!=disabled]'),
413 $customCheckbox,
414 $customRadio;
415 if ($associatedElement.length !== 0) {
416 if ($associatedElement.attr('type') === 'checkbox') {
417 event.preventDefault();
418 $customCheckbox = $(this).find('span.custom.checkbox');
419 toggleCheckbox($customCheckbox);
420 } else if ($associatedElement.attr('type') === 'radio') {
421 event.preventDefault();
422 $customRadio = $(this).find('span.custom.radio');
423 toggleRadio($customRadio);
424 }
425 }
426 });
427
428 $(document).on('click', 'form.custom div.custom.dropdown a.current, form.custom div.custom.dropdown a.selector', function (event) {
429 var $this = $(this),
430 $dropdown = $this.closest('div.custom.dropdown'),
431 $select = $dropdown.prev();
432
433 event.preventDefault();
434 $('div.dropdown').removeClass('open');
435
436 if (false === $select.is(':disabled')) {
437 $dropdown.toggleClass('open');
438
439 if ($dropdown.hasClass('open')) {
440 $(document).bind('click.customdropdown', function (event) {
441 $dropdown.removeClass('open');
442 $(document).unbind('.customdropdown');
443 });
444 } else {
445 $(document).unbind('.customdropdown');
446 }
447 return false;
448 }
449 });
450
451 $(document).on('click', 'form.custom div.custom.dropdown li', function (event) {
452 var $this = $(this),
453 $customDropdown = $this.closest('div.custom.dropdown'),
454 $select = $customDropdown.prev(),
455 selectedIndex = 0;
456
457 event.preventDefault();
458 event.stopPropagation();
459 $('div.dropdown').removeClass('open');
460
461 $this
462 .closest('ul')
463 .find('li')
464 .removeClass('selected');
465 $this.addClass('selected');
466
467 $customDropdown
468 .removeClass('open')
469 .find('a.current')
470 .html($this.html());
471
472 $this.closest('ul').find('li').each(function (index) {
473 if ($this[0] == this) {
474 selectedIndex = index;
475 }
476
477 });
478 $select[0].selectedIndex = selectedIndex;
479
480 $select.trigger('change');
481 });
482
483
484 $.fn.foundationCustomForms = $.foundation.customForms.appendCustomMarkup;
485
486})( jQuery );