/*--------------------------------------------------------------------------
jq-idealforms 2.1
* Author: Cedric Ruiz
* License: GPL or MIT
* Demo: http://elclanrs.github.com/jq-idealforms/
*
--------------------------------------------------------------------------*/
;(function ( $, window, document, undefined ) {
'use strict';
// Global Ideal Forms namespace
$.idealforms= {}
$.idealforms.filters= {}
$.idealforms.errors= {}
$.idealforms.flags= {}
$.idealforms.ajaxRequests= {}
/*--------------------------------------------------------------------------*/
/**
* @namespace A chest for various Utils
*/
varUtils= {
/**
* Get width of widest element in the collection.
* @memberOf Utils
* @param {jQuery object} $elms
* @returns {number}
*/
getMaxWidth: function( $elms ) {
varmaxWidth=0
$elms.each(function() {
varwidth= $(this).outerWidth()
if ( width>maxWidth ) {
maxWidth=width
}
})
return maxWidth
},
/**
* Hacky way of getting LESS variables
* @memberOf Utils
* @param {string} name The name of the LESS class.
* @param {string} prop The css property where the data is stored.
* @returns {number, string}
*/
getLessVar: function( name, prop ) {
varvalue= $('
').hide().appendTo('body').css( prop )$('.' + name).remove()
return ( /^\d+/.test( value ) ? parseInt( value, 10 ) : value )
},
/**
* Like ES5 Object.keys
*/
getKeys: function( obj ) {
varkeys= []
for(var key in obj) {
if ( obj.hasOwnProperty( key ) ) {
keys.push( key )
}
}
return keys
},
// Get lenght of an object
getObjSize: function( obj ) {
varsize=0, key;
for ( key in obj ) {
if ( obj.hasOwnProperty( key ) ) {
size++;
}
}
return size;
},
isFunction: function( obj ) {
return typeofobj=== 'function'
},
isRegex: function( obj ) {
return obj instanceof RegExp
},
isString: function( obj ) {
return typeofobj=== 'string'
},
getByNameOrId: function( str ) {
var $el= $('[name="'+ str +'"]').length
? $('[name="'+ str +'"]') // by name
: $('#'+ str) // by id
return $el.length
? $el
: $.error('The field "'+ str + '" doesn\'t exist.')
},
getFieldsFromArray: function( fields ) {
varf= []
for ( vari=0,l=fields.length; i
f.push( Utils.getByNameOrId( fields[i] ).get(0) )
}
return $( f )
},
convertToArray: function( obj ) {
return Object.prototype.toString.call( obj ) === '[object Array]'
? obj : [ obj ]
},
/**
* Determine type of any Ideal Forms element
* @param $input jQuery $input object
*/
getIdealType: function( $el ) {
vartype= $el.attr('type') || $el[0].tagName.toLowerCase()
return (
/(text|password|email|number|search|url|tel|textarea)/.test( type ) && 'text' ||
/file/.test( type ) && 'file' ||
/select/.test( type ) && 'select' ||
/(radio|checkbox)/.test( type ) && 'radiocheck' ||
/(button|submit|reset)/.test( type ) && 'button' ||
/h\d/.test( type ) && 'heading' ||
/hr/.test( type ) && 'separator' ||
/hidden/.test( type ) && 'hidden'
)
},
/**
* Generates an input
* @param name `name` attribute of the input
* @param type `type` or `tagName` of the input
*/
makeInput: function( name, value, type, list, placeholder ) {
var markup,items= [], item, i, len
function splitValue( str ) {
var item, value, arr
if ( /::/.test( str ) ) {
arr=str.split('::')
item=arr[ 0 ]
value=arr[ 1 ]
} else {
item=value= str
}
return { item: item, value: value }
}
// Text & file
if ( /^(text|password|email|number|search|url|tel|file|hidden)$/.test(type) )
markup='
'type="'+ type +'"'+
'id="'+ name +'"'+
'name="'+ name +'"'+
'value="'+ value +'"'+
(placeholder && 'placeholder="'+ placeholder +'"') +
'/>'
// Textarea
if ( /textarea/.test( type ) ) {
markup=''
}
// Select
if ( /select/.test( type ) ) {
items= []
for (i=0,len=list.length; i
item=splitValue( list[ i ] ).item
value=splitValue( list[ i ] ).value
items.push(''+ item +'')
}
markup=
''+
items.join('') +
''
}
// Radiocheck
if ( /(radio|checkbox)/.test( type ) ) {
items= []
for (i=0,len=list.length; i
item=splitValue( list[ i ] ).item
value=splitValue( list[ i ] ).value
items.push(
''+
''+
item +
''
)
}
markup=items.join('')
}
return markup
}
}
/**
* Custom tabs for Ideal Forms
*/
$.fn.idealTabs=function(container) {
var
// Elements
$contents=this,
$containercontainer= container,
$wrapper= $(''),
$tabs= (function () {
vartabs= []
$contents.each(function () {
varname= $(this).attr('name')
varhtml=
'
'+'' + name + ''+
'0'+
''
tabs.push(html)
})
return $(tabs.join(''))
}()),
Actions= {
getCurIdx: function () {
return $tabs
.filter('.ideal-tabs-tab-active')
.index()
},
getTabIdxByName: function (name) {
varre=newRegExp(name, 'i')
var $tab= $tabs.filter(function () {
return re.test($(this).text())
})
return $tab.index()
}
},
/**
* Public methods
*/
Methods= {
/**
* Switch tab
*/
switchTab: function (nameOrIdx) {
varidx=Utils.isString(nameOrIdx)
? Actions.getTabIdxByName(nameOrIdx)
: nameOrIdx
$tabs.removeClass('ideal-tabs-tab-active')
$tabs.eq(idx).addClass('ideal-tabs-tab-active')
$contents.hide().eq(idx).show()
},
nextTab: function () {
varidx=Actions.getCurIdx() + 1
idx>$tabs.length - 1
? Methods.firstTab()
: Methods.switchTab(idx)
},
prevTab: function () {
Methods.switchTab(Actions.getCurIdx() - 1)
},
firstTab: function () {
Methods.switchTab(0)
},
lastTab: function () {
Methods.switchTab($tabs.length - 1)
},
updateCounter: function (nameOrIdx, text) {
varidx= !isNaN(nameOrIdx) ? nameOrIdx : Actions.getTabIdxByName(name),
$counter= $tabs.eq(idx).find('.ideal-tabs-tab-counter')
$counter.removeClass('ideal-tabs-tab-counter-zero')
if (!text) {
$counter.addClass('ideal-tabs-tab-counter-zero')
}
$counter.html(text)
}
}
// Attach methods
for (var m in Methods)
$contents[m] = Methods[m]
// Init
$tabs.first()
.addClass('ideal-tabs-tab-active')
.end()
.click(function () {
varname= $(this).text()
$contents.switchTab(name)
})
// Insert in DOM & Events
$wrapper.append($tabs).appendTo($container)
$contents.addClass('ideal-tabs-content')
$contents.each(function () {
var $this= $(this),name= $(this).attr('name')
$this.data('ideal-tabs-content-name', name)
.removeAttr('name')
})
$contents.hide().first().show() // Start fresh
return $contents
}
/**
* A custommenu jQuery plugin
* @example `$('select').idealSelect()`
*/
$.fn.idealSelect=function() {
return this.each(function () {
var
$select= $(this),
$options= $select.find('option')
/**
* Generate markup and return elements of custom select
* @memberOf $.fn.toCustomSelect
* @returns {object} All elements of the new select replacement
*/
varidealSelect= (function () {
var
$wrap= $(''),
$menu= $(
'
' +$options.filter(':selected').text() +
''
),
items= (function () {
varitems= []
$options.each(function () {
var $this= $(this)
items.push('' + $this.text() + '')
})
return items
}())
$menu.append('' + items.join('') + '')
$wrap.append($menu)
return {
select: $wrap,
title: $menu.find('.ideal-select-title'),
sub: $menu.find('.ideal-select-sub'),
items: $menu.find('.ideal-select-item')
}
}())
/**
* @namespace Methods of custom select
* @memberOf $.fn.toCustomSelect
*/
varActions= {
getSelectedIdx: function () {
return idealSelect.items
.filter('.ideal-select-item-selected').index()
},
/**
* @private
*/
init: (function () {
$select.css({
position: 'absolute',
left: '-9999px'
})
idealSelect.sub.hide()
idealSelect.select.insertAfter($select)
idealSelect.select.css(
'min-width',
Utils.getMaxWidth(idealSelect.items)
)
idealSelect.items
.eq($options.filter(':selected').index())
.addClass('ideal-select-item-selected')
}()),
noWindowScroll: function (e) {
if (e.which=== 40 ||e.which=== 38 ||e.which=== 13) {
e.preventDefault()
}
},
// Fix loosing focus when scrolling
// and selecting item with keyboard
focusHack: function () {
setTimeout(function () {
$select.trigger('focus')
}, 1)
},
focus: function () {
idealSelect.select.addClass('ideal-select-focus')
$(document).on('keydown.noscroll', Actions.noWindowScroll)
},
blur: function () {
idealSelect.select
.removeClass('ideal-select-open ideal-select-focus')
$(document).off('.noscroll')
},
scrollIntoView: function (dir) {
var
$selected=idealSelect.items.filter('.ideal-select-item-selected'),
itemHeight=idealSelect.items.outerHeight(),
menuHeight=idealSelect.sub.outerHeight(),
isInView= (function () {
// relative position to the submenu
varelPos= $selected.position().top + itemHeight
returndir=== 'down'
? elPos<= menuHeight
: elPos>0
}())
if (!isInView) {
itemHeight= (dir=== 'down')
? itemHeight // go down
: -itemHeight // go up
idealSelect.sub
.scrollTop(idealSelect.sub.scrollTop() + itemHeight)
}
},
scrollToItem: function () {
varidx=Actions.getSelectedIdx(),
height=idealSelect.items.outerHeight(),
nItems=idealSelect.items.length,
allHeight=height* nItems,
curHeight=height* (nItems - idx)
idealSelect.sub.scrollTop(allHeight - curHeight)
},
showMenu: function () {
idealSelect.sub.fadeIn('fast')
idealSelect.select.addClass('ideal-select-open')
Actions.select(Actions.getSelectedIdx())
Actions.scrollToItem()
},
hideMenu: function () {
idealSelect.sub.hide()
idealSelect.select.removeClass('ideal-select-open')
},
select: function (idx) {
idealSelect.items
.removeClass('ideal-select-item-selected')
idealSelect.items
.eq(idx).addClass('ideal-select-item-selected')
},
change: function (idx) {
vartext=idealSelect.items.eq(idx).text()
Actions.select(idx)
idealSelect.title.text(text)
$options.eq(idx).prop('selected', true)
$select.trigger('change')
},
keydown: function (key) {
var
idx=Actions.getSelectedIdx(),
isMenu=idealSelect.select.is('.ideal-select-menu'),
isOpen=idealSelect.select.is('.ideal-select-open')
/**
* @namespace Key pressed
*/
varkeys= {
9: function () { // TAB
if (isMenu) {
Actions.blur()
Actions.hideMenu()
}
},
13: function () { // ENTER
if (isMenu)
isOpen
? Actions.hideMenu()
: Actions.showMenu()
Actions.change(idx)
},
27: function () { // ESC
if (isMenu) Actions.hideMenu()
},
40: function () { // DOWN
if (idx
isOpen
? Actions.select(idx + 1)
: Actions.change(idx + 1)
}
Actions.scrollIntoView('down')
},
38: function () { // UP
if (idx>0) {
isOpen
? Actions.select(idx - 1)
: Actions.change(idx - 1)
}
Actions.scrollIntoView('up')
},
'default': function () { // Letter
var
letter=String.fromCharCode(key),
$matches=idealSelect.items
.filter(function () {
return /^\w+$/i.test( letter ) && // not allow modifier keys ( ctrl, cmd, meta, super... )
new RegExp('^' + letter, 'i').test( $(this).text() ) // find first match
}),
nMatches= $matches.length,
counter=idealSelect.select.data('counter') + 1 || 0,
curKey=idealSelect.select.data('key') || key,
newIdx= $matches.eq(counter).index()
if (!nMatches) // No matches
return false
// If more matches with same letter
if (curKey=== key) {
if (counter
idealSelect.select.data('counter', counter)
}
else {
idealSelect.select.data('counter', 0)
newIdx= $matches.eq(0).index()
}
}
// If new letter
else {
idealSelect.select.data('counter', 0)
newIdx= $matches.eq(0).index()
}
if (isOpen)
Actions.select(newIdx)
else
Actions.change(newIdx)
idealSelect.select.data('key', key)
Actions.scrollToItem()
Actions.focusHack()
}
}
keys[key]
? keys[key]()
: keys['default']()
}
}
/**
* @namespace Holds all events of custom select for "menu mode" and "list mode"
* @memberOf $.fn.toCustomSelect
*/
varevents= {
focus: Actions.focus,
'blur.menu': function () {
Actions.blur()
Actions.hideMenu()
},
'blur.list': function () {
Actions.blur()
},
keydown: function (e) {
Actions.keydown(e.which)
},
'clickItem.menu': function () {
Actions.change($(this).index())
Actions.hideMenu()
},
'clickItem.list': function () {
Actions.change($(this).index())
},
'clickTitle.menu': function () {
Actions.focus()
Actions.showMenu()
$select.trigger('focus')
},
'hideOutside.menu': function () {
$select.off('blur.menu')
$(document).on('mousedown.ideal', function (evt) {
if (!$(evt.target).closest(idealSelect.select).length) {
$(document).off('mousedown.ideal')
$select.on('blur.menu', events['blur.menu'])
} else {
Actions.focusHack()
}
})
},
'mousedown.list': function () {
Actions.focusHack()
}
}
// Reset events
vardisableEvents=function() {
idealSelect.select.removeClass('ideal-select-menu ideal-select-list')
$select.off('.menu .list')
idealSelect.items.off('.menu .list')
idealSelect.select.off('.menu .list')
idealSelect.title.off('.menu .list')
}
// Menu mode
idealSelect.select.on('menu', function () {
disableEvents()
idealSelect.select.addClass('ideal-select-menu')
Actions.hideMenu()
$select.on({
'blur.menu': events['blur.menu'],
'focus.menu': events.focus,
'keydown.menu': events.keydown
})
idealSelect.select.on('mousedown.menu', events['hideOutside.menu'])
idealSelect.items.on('click.menu', events['clickItem.menu'])
idealSelect.title.on('click.menu', events['clickTitle.menu'])
})
// List mode
idealSelect.select.on('list', function () {
disableEvents()
idealSelect.select.addClass('ideal-select-list')
Actions.showMenu()
$select.on({
'blur.list': events['blur.list'],
'focus.list': events.focus,
'keydown.list': events.keydown
})
idealSelect.select.on('mousedown.list', events['mousedown.list'])
idealSelect.items.on('mousedown.list', events['clickItem.list'])
})
$select.keydown(function (e) {
// Prevent default keydown event
// to avoid bugs with Ideal Select events
if (e.which !== 9) e.preventDefault()
})
// Reset
idealSelect.select.on('reset', function(){
Actions.change(0)
})
idealSelect.select.trigger('menu') // Default to "menu mode"
})
}
/*
* idealRadioCheck: jQuery plguin for checkbox and radio replacement
* Usage: $('input[type=checkbox], input[type=radio]').idealRadioCheck()
*/
$.fn.idealRadioCheck=function() {
return this.each(function() {
var $this= $(this)
var $span= $('')
$span.addClass( 'ideal-'+ ( $this.is(':checkbox') ? 'check' : 'radio' ) )
$this.is(':checked') && $span.addClass('checked') // init
$span.insertAfter( $this )
$this.parent('label').addClass('ideal-radiocheck-label')
.attr('onclick', '') // Fix clicking label in iOS
$this.css({ position: 'absolute', left: '-9999px' }) // hide by shifting left
// Events
$this.on({
change: function() {
var $this= $(this)
if ( $this.is('input[type="radio"]') ) {
$this.parent().siblings('label').find('.ideal-radio').removeClass('checked')
}
$span.toggleClass( 'checked', $this.is(':checked') )
},
focus: function() { $span.addClass('focus') },
blur: function() { $span.removeClass('focus') },
click: function() { $(this).trigger('focus') }
})
})
}
;(function( $ ) {
// Browser supports HTML5 multiple file?
varmultipleSupport=typeof$('')[0].multiple !== 'undefined',
isIE= /msie/i.test( navigator.userAgent )
$.fn.idealFile=function() {
return this.each(function() {
var $file= $(this).addClass('ideal-file'), // the original file input
// label that will be used for IE hack
$wrap= $(''),
$input= $(''),
// Button that will be used in non-IE browsers
$button= $('Open'),
// Hack for IE
$label= $('Open')
// Hide by shifting to the left so we
// can still trigger events
$file.css({
position: 'absolute',
left: '-9999px'
})
$wrap.append( $input, ( isIE ? $label : $button ) ).insertAfter( $file )
// Prevent focus
$file.attr('tabIndex', -1)
$button.attr('tabIndex', -1)
$button.click(function () {
$file.focus().click() // Open dialog
})
$file.change(function() {
varfiles= [], fileArr, filename
// If multiple is supported then extract
// all filenames from the file array
if ( multipleSupport ) {
fileArr= $file[0].files
for ( vari=0,len=fileArr.length; i
files.push( fileArr[i].name )
}
filename=files.join(', ')
// If not supported then just take the value
// and remove the path to just show the filename
} else {
filename= $file.val().split('\\').pop()
}
$input.val( filename ) // Set the value
.attr( 'title', filename ) // Show filename in title tootlip
})
$input.on({
focus: function () { $file.trigger('change') },
blur: function () { $file.trigger('blur') },
keydown: function( e ) {
if (e.which=== 13 ) { // Enter
if ( !isIE ) { $file.trigger('click') }
} else if (e.which=== 8 ||e.which=== 46 ) { // Backspace & Del
// On some browsers the value is read-only
// with this trick we remove the old input and add
// a clean clone with all the original events attached
$file.replaceWith( $file= $file.val('').clone( true ) )
$file.trigger('change')
$input.val('')
} else if (e.which=== 9 ){ // TAB
return
} else { // All other keys
return false
}
}
})
})
}
}( jQuery ))
/**
* @namespace Errors
* @locale en
*/
$.idealforms.errors= {
required: '此处是必填的.',
number: '必须是数字.',
digits: '必须是唯一的数字.',
name: '必须至少有3个字符长,并且只能包含字母.',
username: '用户名最短5位,最长30位,请使用英文字母、数字、中文和下划线.用户名首字符必须为字母、数字、中文,不能为全数字.中文最长21个字.',
pass: '密码的位数必须的在6-15位之间,并且至少包含一个数字,一个大写字母和一个小写字母.',
strongpass: '必须至少为8个字符长,至少包含一个大写字母和一个小写字母和一个数字或特殊字符.',
email: '必须是一个有效的email地址.(例: user@gmail.com)',
phone: '必须是一个有效的手机号码.(例: 18723101212)',
zip: 'Must be a valid US zip code.(e.g. 33245 or 33245-0003)',
url: 'Must be a valid URL.(e.g. www.google.com)',
minChar: 'Must be at least{0}characters long.',
minOption: 'Check at least{0}options.',
maxChar: 'No more than{0}characters long.',
maxOption: 'No more than{0}options allowed.',
range: 'Must be a number between {0} and {1}.',
date: 'Must be a valid date.(e.g. {0})',
dob: 'Must be a valid date of birth.',
exclude: '"{0}" is not available.',
excludeOption: '{0}',
equalto: 'Must be the same value as"{0}"',
extension: 'File(s) must have a valid extension.(e.g. "{0}")',
ajaxSuccess: '{0}is not available.',
ajaxError: 'Server error...'
}
/**
* Get all default filters
* @returns object
*/
vargetFilters=function() {
varfilters= {
required: {
regex: /.+/,
error: $.idealforms.errors.required
},
number: {
regex: function( i, v ) { return !isNaN(v) },
error: $.idealforms.errors.number
},
digits: {
regex: /^\d+$/,
error: $.idealforms.errors.digits
},
name: {
regex: /^[A-Za-z]{3,}$/,
error: $.idealforms.errors.name
},
username: {
regex: /^[a-z](?=[\w.]{4,30}$)\w*\.?\w*$/i,
error: $.idealforms.errors.username
},
pass: {
regex: /(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{6,}/,
error: $.idealforms.errors.pass
},
strongpass: {
regex: /(?=^.{8,}$)((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$/,
error: $.idealforms.errors.strongpass
},
email: {
regex: /^([a-zA-Z0-9]*[-_.]?[a-zA-Z0-9]+)*@([a-zA-Z0-9]*[-_]?[a-zA-Z0-9]+)+[\\.][A-Za-z]{2,3}([\\.][A-Za-z]{2})?$/,
error: $.idealforms.errors.email
},
phone: {
//regex: /^((13[0-9])|(15[0-9])|(17[0-9])|(18[0-9]))\\d{8}$/,
regex: /^(0|86|17951)?(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$/,
error: $.idealforms.errors.phone
},
zip: {
regex: /^\d{5}$|^\d{5}-\d{4}$/,
error: $.idealforms.errors.zip
},
url: {
regex: /^(?:(ftp|http|https):\/\/)?(?:[\w\-]+\.)+[a-z]{2,6}([\:\/?#].*)?$/i,
error: $.idealforms.errors.url
},
min: {
regex: function( input, value ) {
var $inputinput= input.input,
min=input.userOptions.data.min,
isRadioCheck= $input.is('[type="checkbox"], [type="radio"]')
if ( isRadioCheck ) {
this.error= $.idealforms.errors.minOption.replace( '{0}', min )
return $input.filter(':checked').length>= min
}
this.error= $.idealforms.errors.minChar.replace( '{0}', min )
return value.length>= min
}
},
max: {
regex: function( input, value ) {
var $inputinput= input.input,
max=input.userOptions.data.max,
isRadioCheck= $input.is('[type="checkbox"], [type="radio"]')
if ( isRadioCheck ) {
this.error= $.idealforms.errors.maxOption.replace( '{0}', max )
return $input.filter(':checked').length<= max
}
this.error= $.idealforms.errors.maxChar.replace( '{0}', max )
return value.length<= max
}
},
range: {
regex: function( input, value ) {
varrange=input.userOptions.data.range,
val= +value
this.error= $.idealforms.errors.range
.replace( '{0}', range[0] )
.replace( '{1}', range[1] )
return val>= range[0] && val<= range[1]
}
},
date: {
regex: function( input, value ) {
var
userFormat=
input.userOptions.data && input.userOptions.data.date
? input.userOptions.data.date
: 'mm/dd/yyyy', // default format
delimiter= /[^mdy]/.exec( userFormat )[0],
theFormat=userFormat.split(delimiter),
theDate=value.split(delimiter),
isDate=function( date, format ) {
var m, d, y
for ( vari=0,len=format.length; i
if ( /m/.test( format[i]) )m=date[i]
if ( /d/.test( format[i]) )d=date[i]
if ( /y/.test( format[i]) )y=date[i]
}
return (
m>0 && m<13&&
y &&y.length=== 4 &&
d>0 && d<= ( new Date( y, m, 0 ) ).getDate()
)
}
this.error= $.idealforms.errors.date.replace( '{0}', userFormat )
return isDate( theDate, theFormat )
}
},
dob: {
regex: function( input, value ) {
var
userFormat=
input.userOptions.data && input.userOptions.data.dob
? input.userOptions.data.dob
: 'mm/dd/yyyy', // default format
// Simulate a date input
dateInput= {
input: input.input,
userOptions: {
data: { date: userFormat }
}
},
// Use internal date filter to validate the date
isDate=filters.date.regex( dateInput, value ),
// DOB
theYear= /\d{4}/.exec( value ),
maxYear=newDate().getFullYear(), // Current year
minYear=maxYear- 100
this.error= $.idealforms.errors.dob
return isDate && theYear>= minYear && theYear<= maxYear
}
},
exclude: {
regex: function( input, value ) {
var $inputinput= input.input,
exclude=input.userOptions.data.exclude,
isOption= $input.is('[type="checkbox"], [type="radio"], select')
this.error=isOption
? $.idealforms.errors.excludeOption.replace( '{0}', value )
:this.error= $.idealforms.errors.exclude.replace( '{0}', value )
return $.inArray( value, exclude ) === -1
}
},
equalto: {
regex: function( input, value ) {
var $equals= $( input.userOptions.data.equalto ),
$inputinput= input.input,
name= $equals.attr('name') || $equals.attr('id'),
isValid= $equals.parents('.ideal-field')
.filter(function(){ return $(this).data('ideal-isvalid') === true })
.length
if ( !isValid ) { return false }
this.error= $.idealforms.errors.equalto.replace( '{0}', name )
return $input.val() === $equals.val()
}
},
extension: {
regex: function( input, value ) {
varfiles=input.input[0].files || [{ name: value }],
extensions=input.userOptions.data.extension,
re=newRegExp( '\\.'+ extensions.join('|') +'$', 'i' ),
valid=false
for ( vari=0,len=files.length; i
valid=re.test( files[i].name );
}
this.error= $.idealforms.errors.extension.replace( '{0}', extensions.join('", "') )
return valid
}
},
ajax: {
regex: function( input, value, showOrHideError ) {
varself=this
var $inputinput= input.input
varuserOptions=input.userOptions
varname= $input.attr('name')
var $field= $input.parents('.ideal-field')
varvalid=false
varcustomErrors=userOptions.errors && userOptions.errors.ajax
self.error= {}
self.error.success=customErrors&& customErrors.success
? customErrors.success
: $.idealforms.errors.ajaxSuccess.replace( '{0}', value )
self.error.fail=customErrors&& customErrors.error
? customErrors.error
: $.idealforms.errors.ajaxError
// Send input name as $_POST[name]
vardata= {}
data[ name ] = $.trim( value )
// Ajax options defined by the user
varuserAjaxOps=input.userOptions.data.ajax
varajaxOps= {
type: 'post',
dataType: 'json',
data: data,
success: function( resp, text, xhr ) {
console.log(resp)
showOrHideError( self.error.success, true )
$input.data({
'ideal-ajax-resp': resp,
'ideal-ajax-error': self.error.success
})
$input.trigger('change') // to update counter
$field.removeClass('ajax')
// Run custom success callback
if( userAjaxOps._success ) {
userAjaxOps._success( resp, text, xhr )
}
},
error: function( xhr, text, error ) {
if ( text !== 'abort' ) {
showOrHideError( self.error.fail, false )
$input.data( 'ideal-ajax-error', self.error.fail )
$field.removeClass('ajax')
// Run custom error callback
if ( userAjaxOps._error ) {
userAjaxOps._error( xhr, text, error )
}
}
}
}
$.extend( ajaxOps, userAjaxOps )
// Init
$input.removeData('ideal-ajax-error')
$input.removeData('ideal-ajax-resp')
$field.addClass('ajax')
// Run request and save it to be able to abort it
// so requests don't bubble
$.idealforms.ajaxRequests[ name ] = $.ajax( ajaxOps )
}
}
}
return filters
}
$.idealforms.flags= {
noerror: function (i) {
i.parent().siblings('.ideal-error').hide()
},
noicons: function (i) {
i.siblings('.ideal-icon-valid, .ideal-icon-invalid').hide()
},
novalidicon: function (i) {
i.siblings('.ideal-icon-valid').hide()
},
noinvalidicon: function (i) {
i.siblings('.ideal-icon-invalid').hide()
},
noclass: function (i) {
i.parents('.ideal-field').removeClass('valid invalid')
},
novalidclass: function (i) {
i.parents('.ideal-field').removeClass('valid')
},
noinvalidclass: function (i) {
i.parents('.ideal-field').removeClass('invalid')
}
}
/*
* Ideal Forms plugin
*/
var_defaults= {
inputs: {},
customFilters: {},
customFlags: {},
globalFlags: '',
onSuccess: function(e) { alert('Thank you...') },
onFail: function() { alert('Invalid!') },
responsiveAt: 'auto',
disableCustom: ''
}
// Constructor
varIdealForms=function( element, options ) {
varself=this
self.$form= $( element )
self.opts= $.extend( {}, _defaults, options )
self.$tabs=self.$form.find('section')
// Set localized filters
$.extend( $.idealforms.filters, getFilters() )
self._init()
}
// Plugin
$.fn.idealforms=function( options ) {
return this.each(function() {
if ( !$.data( this, 'idealforms' ) ) {
$.data( this, 'idealforms', new IdealForms( this, options ) )
}
})
}
// Get LESS variables
varLessVars= {
fieldWidth: Utils.getLessVar( 'ideal-field-width', 'width' )
}
/*
* Private Methods
*/
$.extend( IdealForms.prototype, {
_init: function() {
varself=this
varo=self.opts
varformElements=self._getFormElements()
self.$form.css( 'visibility', 'visible' )
.addClass('ideal-form')
.attr( 'novalidate', 'novalidate' ) // disable HTML5 validation
// Do markup
formElements.inputs
.add( formElements.headings )
.add( formElements.separators )
.each(function(){ self._doMarkup( $(this) ) })
// Generate tabs
if ( self.$tabs.length ) {
var $tabContainer= $('')
self.$form.prepend( $tabContainer )
self.$tabs.idealTabs( $tabContainer )
}
// Always show datepicker below the input
if ( jQuery.ui ) {
$.datepicker._checkOffset=function( a,b,c ) { return b }
}
// Add inputs specified by data-ideal
// to the list of user inputs
self.$form.find('[data-ideal]').each(function() {
varuserInput=o.inputs[ this.name ]
o.inputs[ this.name ] = userInput || { filters: $(this).data('ideal') }
})
// Responsive
if ( o.responsiveAt ) {
$(window).resize(function(){ self._responsive() })
self._responsive()
}
// Form events
self.$form.on({
keydown: function( e ) {
// Prevent submit when pressing enter
// but exclude textareas
if (e.which=== 13 && e.target.nodeName !== 'TEXTAREA' ) {
e.preventDefault()
}
},
submit: function( e ) {
if ( !self.isValid() ) {
e.preventDefault()
o.onFail()
self.focusFirstInvalid()
} else {
o.onSuccess( e )
}
}
})
self._adjust()
self._attachEvents()
self.fresh() // Start fresh
},
_getFormElements: function() {
return {
inputs: this.$form.find('input, select, textarea, :button'),
labels: this.$form.find('div>label:first-child'),
text: this.$form.find('input:not([type="checkbox"], [type="radio"], [type="submit"]), textarea'),
select: this.$form.find('select'),
radiocheck: this.$form.find('input[type="radio"], input[type="checkbox"]'),
buttons: this.$form.find(':button'),
file: this.$form.find('input[type="file"]'),
headings: this.$form.find('h1, h2, h3, h4, h5, h6'),
separators: this.$form.find('hr'),
hidden: this.$form.find('input:hidden')
}
},
_getUserInputs: function() {
return this.$form.find('[name="'+ Utils.getKeys( this.opts.inputs ).join('"], [name="') +'"]')
},
_getTab: function( nameOrIdx ) {
varself=this
varisNumber= !isNaN( nameOrIdx )
if ( isNumber ) {
return self.$tabs.eq( nameOrIdx )
}
return self.$tabs.filter(function() {
varre=newRegExp( nameOrIdx, 'i' )
return re.test( $(this).data('ideal-tabs-content-name') )
})
},
_getCurrentTabIdx: function() {
return this.$tabs.index( this.$form.find('.ideal-tabs-content:visible') )
},
_updateTabsCounter: function() {
varself=this
self.$tabs.each(function( i ) {
varinvalid=self.getInvalidInTab( i ).length
self.$tabs.updateCounter( i, invalid )
})
},
_adjust: function() {
varself=this
varo=self.opts
varformElements=self._getFormElements()
varcurTab=self._getCurrentTabIdx()
// Autocomplete causes some problems...
formElements.inputs.attr('autocomplete', 'off')
// Show tabs to calculate dimensions
if ( self.$tabs.length ) { self.$tabs.show() }
// Adjust labels
varlabels=formElements.labels
labels.removeAttr('style').width( Utils.getMaxWidth( labels ) )
// Adjust headings and separators
if ( self.$tabs.length ) {
this.$tabs.each(function(){
$( this ).find('.ideal-heading:first').addClass('first-child')
})
} else {
self.$form.find('.ideal-heading:first').addClass('first-child')
}
self._setDatepicker()
// Done calculating hide tabs
if ( self.$tabs.length ) {
self.$tabs.hide()
self.switchTab( curTab )
}
},
_setDatepicker: function() {
varo=this.opts
var $datepicker=this.$form.find('input.datepicker')
if ( jQuery.ui && $datepicker.length ) {
$datepicker.each(function() {
varuserInput=o.inputs[ this.name ]
vardata=userInput&& userInput.data && userInput.data.date
varformat=data? data.replace( 'yyyy', 'yy' ) : 'mm/dd/yy'
$(this).datepicker({
dateFormat: format,
beforeShow: function( input ) {
$( input ).addClass('open')
},
onChangeMonthYear: function() {
// Hack to fix IE9 not resizing
var $this= $(this)
varw= $this.outerWidth() // cache first!
setTimeout(function() {
$this.datepicker('widget').css( 'width', w )
}, 1)
},
onClose: function() { $(this).removeClass('open') }
})
})
// Adjust width
$datepicker.on('focus keyup', function() {
vart= $(this),w=t.outerWidth()
t.datepicker('widget').css( 'width', w )
})
$datepicker.parent().siblings('.ideal-error').addClass('hidden')
}
},
_doMarkup: function( $element ) {
varo=this.opts
varelementType=Utils.getIdealType( $element )
// Validation elements
var $field= $('')
var $error= $('')
var $valid= $('')
var $invalid= $('')
.click(function(){
$(this).parent().find('input:first, textarea, select').focus()
})
// Basic markup
$element.closest('div').addClass('ideal-wrap')
.children('label:first-child').addClass('ideal-label')
varidealElements= {
_defaultInput: function() {
$element.wrapAll( $field ).after( $valid, $invalid )
.parent().after( $error )
},
text: function() { idealElements._defaultInput() },
radiocheck: function() {
// Check if input is already wrapped so we don't
// wrap radios and checks more than once
varisWrapped= $element.parents('.ideal-field').length
if ( !isWrapped ) {
$element.parent().nextAll().andSelf().wrapAll( $field.addClass('ideal-radiocheck') )
$element.parents('.ideal-field').append( $valid, $invalid ).after( $error )
}
if ( !/radiocheck/.test( o.disableCustom ) ) {
$element.idealRadioCheck()
}
},
select: function() {
idealElements._defaultInput()
if ( !/select/.test( o.disableCustom ) ) {
$element.idealSelect()
}
},
file: function() {
idealElements._defaultInput()
if ( !/file/.test( o.disableCustom ) ) {
$element.idealFile()
}
},
button: function() {
if ( !/button/.test( o.disableCustom ) ) {
$element.addClass('ideal-button')
}
},
hidden: function() {
$element.closest('div').addClass('ideal-hidden')
},
heading: function() {
$element.closest('div').addClass('ideal-full-width')
$element.parent().children().wrapAll('')
},
separator: function() {
$element.closest('div').addClass('ideal-full-width')
$element.wrapAll('')
}
}
// Generate markup for current element type
idealElements[ elementType ] ? idealElements[ elementType ]() : $.noop()
$error.add( $valid ).add( $invalid ).hide() // Start fresh
},
/** Validates an input and shows or hides error and icon
* @memberOf Actions
* @param {object} $input jQuery object
* @param {string} e The JavaScript event
*/
_validate: function( $input, e ) {
varself=this
varo=this.opts
varuserOptions= o.inputs[ $input.attr('name') ]
varuserFilters=userOptions.filters && userOptions.filters.split(/\s/)
varname= $input.attr('name')
varvalue= $input.val()
varajaxRequest= $.idealforms.ajaxRequests[ name ]
varisRadioCheck= $input.is('[type="checkbox"], [type="radio"]')
varinputData= {
// If is radio or check validate all inputs related by name
input: isRadioCheck ? self.$form.find('[name="' + name + '"]') : $input,
userOptions: userOptions
}
// Validation elements
var $field= $input.parents('.ideal-field')
var $error= $field.siblings('.ideal-error')
var $invalid=isRadioCheck
? $input.parent().siblings('.ideal-icon-invalid')
: $input.siblings('.ideal-icon-invalid')
var $valid=isRadioCheck
? $input.parent().siblings('.ideal-icon-valid')
: $input.siblings('.ideal-icon-valid')
function resetError() {
$field.removeClass('valid invalid').removeData('ideal-isvalid')
$error.add( $invalid ).add( $valid ).hide()
}
function showOrHideError( error, valid ) {
resetError()
valid ? $valid.show() : $invalid.show()
$field.addClass( valid ? 'valid' : 'invalid' )
$field.data( 'ideal-isvalid', valid )
if ( !valid ) {
$error.html( error ).toggle( $field.is('.ideal-field-focus') )
}
}
// Prevent validation when typing but not introducing any new characters
// This is mainly to prevent multiple AJAX requests
varoldValue= $input.data('ideal-value') || 0
$input.data( 'ideal-value', value )
if (e.type=== 'keyup' &&value=== oldValue ) { return false }
// Validate
if ( userFilters ) {
$.each( userFilters, function( i, filter ) {
vartheFilter= $.idealforms.filters[ filter ]
varcustomError=userOptions.errors && userOptions.errors[ filter ]
varerror=''
// If field is empty and not required
if ( !value && filter !== 'required' ) {
resetError()
return false
}
if ( theFilter ) {
// Abort and reset ajax if there's a request pending
if (e.type=== 'keyup' && ajaxRequest ) {
ajaxRequest.abort()
$field.removeClass('ajax')
}
// AJAX
if (filter=== 'ajax' ) {
showOrHideError( error, false ) // set invalid till response comes back
$error.hide()
if (e.type=== 'keyup' ) {
theFilter.regex( inputData, value, showOrHideError ) // runs the ajax callback
} else {
varajaxError= $input.data('ideal-ajax-error')
if ( ajaxError ) {
showOrHideError( ajaxError, $input.data('ideal-ajax-resp') || false )
}
}
}
// All other filters
else {
varvalid=Utils.isRegex( theFilter.regex ) && theFilter.regex.test( value ) ||
Utils.isFunction( theFilter.regex ) && theFilter.regex( inputData, value )
error=customError|| theFilter.error // assign error after calling regex()
showOrHideError( error, valid )
if ( !valid ) { return false }
}
}
})
}
// Reset if there are no filters
else {
resetError()
}
// Flags
varflags= (function(){
varf=userOptions.flags && userOptions.flags.split(' ') || []
if ( o.globalFlags ) {
$.each( o.globalFlags.split(' '), function( i,v ) { f.push(v) })
}
return f
}())
if ( flags.length ) {
$.each(flags, function( i,f ) {
vartheFlag= $.idealforms.flags[f]
if ( theFlag ) { theFlag( $input, e.type ) }
})
}
// Update counter
if ( self.$tabs.length ) {
self._updateTabsCounter( self._getCurrentTabIdx() )
}
},
_attachEvents: function() {
varself=this
self._getUserInputs().on('keyup change focus blur', function(e) {
var $this= $(this)
var $field= $this.parents('.ideal-field')
varisFile= $this.is('input[type=file]')
// Trigger on change iftype=filecuz custom file
// disables focus on original file input (tabIndex= -1)
if (e.type=== 'focus' || isFile &&e.type=== 'change' ) {
$field.addClass('ideal-field-focus')
}
if (e.type=== 'blur' ) {
$field.removeClass('ideal-field-focus')
}
self._validate( $this, e )
})
},
_responsive: function() {
varformElements=this._getFormElements()
varmaxWidth=LessVars.fieldWidth + formElements.labels.outerWidth()
var $emptyLabel=formElements.labels.filter(function() {
return $(this).html() === ' '
})
var $customSelect=this.$form.find('.ideal-select')
this.opts.responsiveAt=== 'auto'
? this.$form.toggleClass( 'stack', this.$form.width()
: this.$form.toggleClass( 'stack', $(window).width()
varisStack=this.$form.is('.stack')
$emptyLabel.toggle( !isStack )
$customSelect.trigger( isStack ? 'list' : 'menu' )
// Hide datePicker
var $datePicker=this.$form.find('input.hasDatepicker')
if ( $datePicker.length ) { $datePicker.datepicker('hide') }
}
})
/*
* Public Methods
*/
$.extend( IdealForms.prototype, {
getInvalid: function() {
return this.$form.find('.ideal-field').filter(function() {
return $(this).data('ideal-isvalid') === false
})
},
getInvalidInTab: function( nameOrIdx ) {
return this._getTab( nameOrIdx ).find('.ideal-field').filter(function() {
return $(this).data('ideal-isvalid') === false
})
},
isValid: function() {
return !this.getInvalid().length
},
isValidField: function( field ) {
var $input=Utils.getByNameOrId( field )
return $input.parents('.ideal-field').data('ideal-isvalid') === true
},
focusFirst: function() {
if ( this.$tabs.length ) {
this.$tabs.filter(':visible')
.find('.ideal-field:first')
.find('input:first, select, textarea').focus()
} else {
this.$form.find('.ideal-field:first')
.find('input:first, select, textarea').focus()
}
return this
},
focusFirstInvalid: function() {
var $first=this.getInvalid().first().find('input:first, select, textarea')
vartabName= $first.parents('.ideal-tabs-content').data('ideal-tabs-content-name')
if ( this.$tabs.length ) {
this.switchTab( tabName )
}
$first.focus()
return this
},
switchTab: function( nameOrIdx ) {
this.$tabs.switchTab( nameOrIdx )
return this
},
nextTab: function() {
this.$tabs.nextTab()
return this
},
prevTab: function() {
this.$tabs.prevTab()
return this
},
firstTab: function() {
this.$tabs.firstTab()
return this
},
lastTab: function() {
this.$tabs.lastTab()
return this
},
fresh: function() {
this._getUserInputs().change().parents('.ideal-field')
.removeClass('valid invalid')
return this
},
freshFields: function( fields ) {
fields=Utils.convertToArray( fields )
$.each( fields, function( i ) {
var $input=Utils.getByNameOrId( fields[ i ] )
$input.change().parents('.ideal-field').removeClass('valid invalid')
})
return this
},
reload: function() {
this._adjust()
this._attachEvents()
return this
},
reset: function() {
varformElements=this._getFormElements()
formElements.text.val('') // text inputs
formElements.radiocheck.removeAttr('checked') // radio & check
// Select and custom select
formElements.select.find('option').first().prop( 'selected', true )
this.$form.find('.ideal-select').trigger('reset')
if ( this.$tabs.length ) { this.firstTab() }
this.focusFirst().fresh()
return this
},
resetFields: function( fields ) {
fields=Utils.convertToArray( fields )
varformElements=this._getFormElements()
$.each( fields, function( i, v ) {
var $input=Utils.getByNameOrId( v )
vartype=Utils.getIdealType( $input )
if (type=== 'text' ||type=== 'file' ) {
$input.val('')
}
if (type=== 'radiocheck' ) {
$input.removeAttr('checked') // radio & check
}
if (type=== 'select' ) {
$input.find('option').first().prop( 'selected', true )
$input.next('.ideal-select').trigger('reset')
}
$input.change()
})
this.freshFields( fields )
return this
},
toggleFields: function( fields ) {
fields=Utils.convertToArray( fields )
varself=this
var $fields=Utils.getFieldsFromArray( fields )
$fields.each(function() {
var $this= $(this)
varname= $this.attr('name') || $this.attr('id')
varinput=self.opts.inputs[ name ]
varfilters=input&& input.filters
vardataFilters= $this.data('ideal-filters') || ''
$this.data( 'ideal-filters', filters )
$this.closest('.ideal-wrap').toggle()
self.setFieldOptions( name, { filters: dataFilters } )
})
return this
},
setOptions: function( options ) {
$.extend( true, this.opts, options )
this.reload().fresh()
return this
},
setFieldOptions: function( name, options ) {
$.extend( true, this.opts.inputs[ name ], options )
this.reload().freshFields([ name ])
return this
},
addFields: function( fields ) {
fields=Utils.convertToArray( fields )
varself=this
// Save names of all inputs in Array
// to use methods that take names ie. fresh()
varallNames= []
// Add an input to the DOM
function add( ops ) {
varname=ops.name
varuserOptions= {
filters: ops.filters || '',
data: ops.data || {},
errors: ops.errors || {},
flags: ops.flags || ''
}
varlabel=ops.label || ''
vartype=ops.type
varlist=ops.list || []
varplaceholder=ops.placeholder || ''
varvalue=ops.value || ''
var $field= $('
''+ label +':'+
Utils.makeInput( name, value, type, list, placeholder ) +
'
var $input= $field.find('input, select, textarea, :button')
// Add inputs with filters to the list
// of user inputs to validate
if ( userOptions.filters ) { self.opts.inputs[ name ] = userOptions }
self._doMarkup( $input )
// Insert in DOM
if ( ops.addAfter ) {
$field.insertAfter(
$( Utils.getByNameOrId( ops.addAfter ) ).parents('.ideal-wrap')
)
} else if ( ops.addBefore ) {
$field.insertBefore(
$(Utils.getByNameOrId( ops.addBefore ))
.parents('.ideal-wrap')
)
} else if ( ops.appendToTab ) {
$field.insertAfter(
self._getTab( ops.appendToTab ).find('.ideal-wrap:last-child')
)
} else {
$field.insertAfter( self.$form.find('.ideal-wrap').last() )
}
// Add current field name to list of names
allNames.push( name )
}
// Run through each input
$.each( fields, function( i, ops ) { add( ops ) })
self.reload()
self.freshFields( allNames )
self._responsive()
return this
},
removeFields: function( fields ) {
fields=Utils.convertToArray( fields )
var $fields=Utils.getFieldsFromArray( fields )
$fields.parents('.ideal-wrap').remove()
this.reload()
return this
}
})
}( jQuery, window, document ))