formValidation.popular.js 192 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768
  1. /*!
  2. * FormValidation (http://formvalidation.io)
  3. * The best jQuery plugin to validate form fields. Support Bootstrap, Foundation, Pure, SemanticUI, UIKit and custom frameworks
  4. *
  5. * This is a custom build that does NOT consist of all validators. Only popular validators are included:
  6. * - between
  7. * - callback
  8. * - choice
  9. * - color
  10. * - creditCard
  11. * - date
  12. * - different
  13. * - digits
  14. * - emailAddress
  15. * - file
  16. * - greaterThan
  17. * - identical
  18. * - integer
  19. * - lessThan
  20. * - notEmpty
  21. * - numeric
  22. * - promise
  23. * - regexp
  24. * - remote
  25. * - stringLength
  26. * - uri
  27. *
  28. * Use formValidation(.min).js file if you want to have all validators.
  29. *
  30. * @version v0.8.1, built on 2016-07-29 1:10:54 AM
  31. * @author https://twitter.com/formvalidation
  32. * @copyright (c) 2013 - 2016 Nguyen Huu Phuoc
  33. * @license http://formvalidation.io/license/
  34. */
  35. // Register the namespace
  36. window.FormValidation = {
  37. AddOn: {}, // Add-ons
  38. Framework: {}, // Supported frameworks
  39. I18n: {}, // i18n
  40. Validator: {} // Available validators
  41. };
  42. if (typeof jQuery === 'undefined') {
  43. throw new Error('FormValidation requires jQuery');
  44. }
  45. (function($) {
  46. var version = $.fn.jquery.split(' ')[0].split('.');
  47. if ((+version[0] < 2 && +version[1] < 9) || (+version[0] === 1 && +version[1] === 9 && +version[2] < 1)) {
  48. throw new Error('FormValidation requires jQuery version 1.9.1 or higher');
  49. }
  50. }(jQuery));
  51. (function($) {
  52. // TODO: Remove backward compatibility
  53. /**
  54. * Constructor
  55. *
  56. * @param {jQuery|String} form The form element or selector
  57. * @param {Object} options The options
  58. * @param {String} [namespace] The optional namespace which is used for data-{namespace}-xxx attributes and internal data.
  59. * Currently, it's used to support backward version
  60. * @constructor
  61. */
  62. FormValidation.Base = function(form, options, namespace) {
  63. this.$form = $(form);
  64. this.options = $.extend({}, $.fn.formValidation.DEFAULT_OPTIONS, options);
  65. this._namespace = namespace || 'fv';
  66. this.$invalidFields = $([]); // Array of invalid fields
  67. this.$submitButton = null; // The submit button which is clicked to submit form
  68. this.$hiddenButton = null;
  69. // Validating status
  70. this.STATUS_NOT_VALIDATED = 'NOT_VALIDATED';
  71. this.STATUS_VALIDATING = 'VALIDATING';
  72. this.STATUS_INVALID = 'INVALID';
  73. this.STATUS_VALID = 'VALID';
  74. this.STATUS_IGNORED = 'IGNORED';
  75. // Default message
  76. this.DEFAULT_MESSAGE = $.fn.formValidation.DEFAULT_MESSAGE;
  77. // Determine the event that is fired when user change the field value
  78. // Most modern browsers supports input event except IE 7, 8.
  79. // IE 9 supports input event but the event is still not fired if I press the backspace key.
  80. // Get IE version
  81. // https://gist.github.com/padolsey/527683/#comment-7595
  82. this._ieVersion = (function() {
  83. var v = 3, div = document.createElement('div'), a = div.all || [];
  84. while (div.innerHTML = '<!--[if gt IE '+(++v)+']><br><![endif]-->', a[0]) {}
  85. return v > 4 ? v : document.documentMode;
  86. }());
  87. var el = document.createElement('div');
  88. this._changeEvent = (this._ieVersion === 9 || !('oninput' in el)) ? 'keyup' : 'input';
  89. // The flag to indicate that the form is ready to submit when a remote/callback validator returns
  90. this._submitIfValid = null;
  91. // Field elements
  92. this._cacheFields = {};
  93. this._init();
  94. };
  95. FormValidation.Base.prototype = {
  96. constructor: FormValidation.Base,
  97. /**
  98. * Check if the number of characters of field value exceed the threshold or not
  99. *
  100. * @param {jQuery} $field The field element
  101. * @returns {Boolean}
  102. */
  103. _exceedThreshold: function($field) {
  104. var ns = this._namespace,
  105. field = $field.attr('data-' + ns + '-field'),
  106. threshold = this.options.fields[field].threshold || this.options.threshold;
  107. if (!threshold) {
  108. return true;
  109. }
  110. var cannotType = $.inArray($field.attr('type'), ['button', 'checkbox', 'file', 'hidden', 'image', 'radio', 'reset', 'submit']) !== -1;
  111. return (cannotType || $field.val().length >= threshold);
  112. },
  113. /**
  114. * Init form
  115. */
  116. _init: function() {
  117. var that = this,
  118. ns = this._namespace,
  119. options = {
  120. addOns: {},
  121. autoFocus: this.$form.attr('data-' + ns + '-autofocus'),
  122. button: {
  123. selector: this.$form.attr('data-' + ns + '-button-selector') || this.$form.attr('data-' + ns + '-submitbuttons'), // Support backward
  124. disabled: this.$form.attr('data-' + ns + '-button-disabled')
  125. },
  126. control: {
  127. valid: this.$form.attr('data-' + ns + '-control-valid'),
  128. invalid: this.$form.attr('data-' + ns + '-control-invalid')
  129. },
  130. err: {
  131. clazz: this.$form.attr('data-' + ns + '-err-clazz'),
  132. container: this.$form.attr('data-' + ns + '-err-container') || this.$form.attr('data-' + ns + '-container'), // Support backward
  133. parent: this.$form.attr('data-' + ns + '-err-parent')
  134. },
  135. events: {
  136. formInit: this.$form.attr('data-' + ns + '-events-form-init'),
  137. formPreValidate: this.$form.attr('data-' + ns + '-events-form-prevalidate'),
  138. formError: this.$form.attr('data-' + ns + '-events-form-error'),
  139. formReset: this.$form.attr('data-' + ns + '-events-form-reset'),
  140. formSuccess: this.$form.attr('data-' + ns + '-events-form-success'),
  141. fieldAdded: this.$form.attr('data-' + ns + '-events-field-added'),
  142. fieldRemoved: this.$form.attr('data-' + ns + '-events-field-removed'),
  143. fieldInit: this.$form.attr('data-' + ns + '-events-field-init'),
  144. fieldError: this.$form.attr('data-' + ns + '-events-field-error'),
  145. fieldReset: this.$form.attr('data-' + ns + '-events-field-reset'),
  146. fieldSuccess: this.$form.attr('data-' + ns + '-events-field-success'),
  147. fieldStatus: this.$form.attr('data-' + ns + '-events-field-status'),
  148. localeChanged: this.$form.attr('data-' + ns + '-events-locale-changed'),
  149. validatorError: this.$form.attr('data-' + ns + '-events-validator-error'),
  150. validatorSuccess: this.$form.attr('data-' + ns + '-events-validator-success'),
  151. validatorIgnored: this.$form.attr('data-' + ns + '-events-validator-ignored')
  152. },
  153. excluded: this.$form.attr('data-' + ns + '-excluded'),
  154. icon: {
  155. valid: this.$form.attr('data-' + ns + '-icon-valid') || this.$form.attr('data-' + ns + '-feedbackicons-valid'), // Support backward
  156. invalid: this.$form.attr('data-' + ns + '-icon-invalid') || this.$form.attr('data-' + ns + '-feedbackicons-invalid'), // Support backward
  157. validating: this.$form.attr('data-' + ns + '-icon-validating') || this.$form.attr('data-' + ns + '-feedbackicons-validating'), // Support backward
  158. feedback: this.$form.attr('data-' + ns + '-icon-feedback')
  159. },
  160. live: this.$form.attr('data-' + ns + '-live'),
  161. locale: this.$form.attr('data-' + ns + '-locale'),
  162. message: this.$form.attr('data-' + ns + '-message'),
  163. onPreValidate: this.$form.attr('data-' + ns + '-onprevalidate'),
  164. onError: this.$form.attr('data-' + ns + '-onerror'),
  165. onReset: this.$form.attr('data-' + ns + '-onreset'),
  166. onSuccess: this.$form.attr('data-' + ns + '-onsuccess'),
  167. row: {
  168. selector: this.$form.attr('data-' + ns + '-row-selector') || this.$form.attr('data-' + ns + '-group'), // Support backward
  169. valid: this.$form.attr('data-' + ns + '-row-valid'),
  170. invalid: this.$form.attr('data-' + ns + '-row-invalid'),
  171. feedback: this.$form.attr('data-' + ns + '-row-feedback')
  172. },
  173. threshold: this.$form.attr('data-' + ns + '-threshold'),
  174. trigger: this.$form.attr('data-' + ns + '-trigger'),
  175. verbose: this.$form.attr('data-' + ns + '-verbose'),
  176. fields: {}
  177. };
  178. this.$form
  179. // Disable client side validation in HTML 5
  180. .attr('novalidate', 'novalidate')
  181. .addClass(this.options.elementClass)
  182. // Disable the default submission first
  183. .on('submit.' + ns, function(e) {
  184. e.preventDefault();
  185. that.validate();
  186. })
  187. .on('click.' + ns, this.options.button.selector, function() {
  188. that.$submitButton = $(this);
  189. // The user just click the submit button
  190. that._submitIfValid = true;
  191. });
  192. if (this.options.declarative === true || this.options.declarative === 'true') {
  193. // Find all fields which have either "name" or "data-{namespace}-field" attribute
  194. this.$form
  195. .find('[name], [data-' + ns + '-field]')
  196. .each(function() {
  197. var $field = $(this),
  198. field = $field.attr('name') || $field.attr('data-' + ns + '-field'),
  199. opts = that._parseOptions($field);
  200. if (opts) {
  201. $field.attr('data-' + ns + '-field', field);
  202. options.fields[field] = $.extend({}, opts, options.fields[field]);
  203. }
  204. });
  205. }
  206. this.options = $.extend(true, this.options, options);
  207. // Normalize the err.parent option
  208. if ('string' === typeof this.options.err.parent) {
  209. this.options.err.parent = new RegExp(this.options.err.parent);
  210. }
  211. // Support backward
  212. if (this.options.container) {
  213. this.options.err.container = this.options.container;
  214. delete this.options.container;
  215. }
  216. if (this.options.feedbackIcons) {
  217. this.options.icon = $.extend(true, this.options.icon, this.options.feedbackIcons);
  218. delete this.options.feedbackIcons;
  219. }
  220. if (this.options.group) {
  221. this.options.row.selector = this.options.group;
  222. delete this.options.group;
  223. }
  224. if (this.options.submitButtons) {
  225. this.options.button.selector = this.options.submitButtons;
  226. delete this.options.submitButtons;
  227. }
  228. // If the locale is not found, reset it to default one
  229. if (!FormValidation.I18n[this.options.locale]) {
  230. this.options.locale = $.fn.formValidation.DEFAULT_OPTIONS.locale;
  231. }
  232. // Parse the add-on options from HTML attributes
  233. if (this.options.declarative === true || this.options.declarative === 'true') {
  234. this.options = $.extend(true, this.options, { addOns: this._parseAddOnOptions() });
  235. }
  236. // When pressing Enter on any field in the form, the first submit button will do its job.
  237. // The form then will be submitted.
  238. // I create a first hidden submit button
  239. this.$hiddenButton = $('<button/>')
  240. .attr('type', 'submit')
  241. .prependTo(this.$form)
  242. .addClass('fv-hidden-submit')
  243. .css({ display: 'none', width: 0, height: 0 });
  244. this.$form
  245. .on('click.' + this._namespace, '[type="submit"]', function(e) {
  246. // #746: Check if the button click handler returns false
  247. if (!e.isDefaultPrevented()) {
  248. var $target = $(e.target),
  249. // The button might contain HTML tag
  250. $button = $target.is('[type="submit"]') ? $target.eq(0) : $target.parent('[type="submit"]').eq(0);
  251. // Don't perform validation when clicking on the submit button/input which
  252. // aren't defined by the 'button.selector' option
  253. if (that.options.button.selector && !$button.is(that.options.button.selector) && !$button.is(that.$hiddenButton)) {
  254. that.$form.off('submit.' + that._namespace).submit();
  255. // Fix the issue where 'formnovalidate' causes IE to send two postbacks to server
  256. return false;
  257. }
  258. }
  259. });
  260. for (var field in this.options.fields) {
  261. this._initField(field);
  262. }
  263. // Init the add-ons
  264. for (var addOn in this.options.addOns) {
  265. if ('function' === typeof FormValidation.AddOn[addOn].init) {
  266. FormValidation.AddOn[addOn].init(this, this.options.addOns[addOn]);
  267. }
  268. }
  269. this.$form.trigger($.Event(this.options.events.formInit), {
  270. bv: this, // Support backward
  271. fv: this,
  272. options: this.options
  273. });
  274. // Prepare the events
  275. if (this.options.onPreValidate) {
  276. this.$form.on(this.options.events.formPreValidate, function(e) {
  277. FormValidation.Helper.call(that.options.onPreValidate, [e]);
  278. });
  279. }
  280. if (this.options.onSuccess) {
  281. this.$form.on(this.options.events.formSuccess, function(e) {
  282. FormValidation.Helper.call(that.options.onSuccess, [e]);
  283. });
  284. }
  285. if (this.options.onError) {
  286. this.$form.on(this.options.events.formError, function(e) {
  287. FormValidation.Helper.call(that.options.onError, [e]);
  288. });
  289. }
  290. if (this.options.onReset) {
  291. this.$form.on(this.options.events.formReset, function(e) {
  292. FormValidation.Helper.call(that.options.onReset, [e]);
  293. });
  294. }
  295. },
  296. /**
  297. * Init field
  298. *
  299. * @param {String|jQuery} field The field name or field element
  300. */
  301. _initField: function(field) {
  302. var ns = this._namespace,
  303. fields = $([]);
  304. switch (typeof field) {
  305. case 'object':
  306. fields = field;
  307. field = field.attr('data-' + ns + '-field');
  308. break;
  309. case 'string':
  310. fields = this.getFieldElements(field);
  311. fields.attr('data-' + ns + '-field', field);
  312. break;
  313. default:
  314. break;
  315. }
  316. // We don't need to validate non-existing fields
  317. if (fields.length === 0) {
  318. return;
  319. }
  320. if (this.options.fields[field] === null || this.options.fields[field].validators === null) {
  321. return;
  322. }
  323. var validators = this.options.fields[field].validators,
  324. validatorName,
  325. alias;
  326. for (validatorName in validators) {
  327. alias = validators[validatorName].alias || validatorName;
  328. if (!FormValidation.Validator[alias]) {
  329. delete this.options.fields[field].validators[validatorName];
  330. }
  331. }
  332. if (this.options.fields[field].enabled === null) {
  333. this.options.fields[field].enabled = true;
  334. }
  335. var that = this,
  336. total = fields.length,
  337. type = fields.attr('type'),
  338. updateAll = (total === 1) || ('radio' === type) || ('checkbox' === type),
  339. trigger = this._getFieldTrigger(fields.eq(0)),
  340. clazz = this.options.err.clazz.split(' ').join('.'),
  341. events = $.map(trigger, function(item) {
  342. return item + '.update.' + ns;
  343. }).join(' ');
  344. for (var i = 0; i < total; i++) {
  345. var $field = fields.eq(i),
  346. row = this.options.fields[field].row || this.options.row.selector,
  347. $parent = $field.closest(row),
  348. // Allow user to indicate where the error messages are shown
  349. // Support backward
  350. container = ('function' === typeof (this.options.fields[field].container || this.options.fields[field].err || this.options.err.container))
  351. ? (this.options.fields[field].container || this.options.fields[field].err || this.options.err.container).call(this, $field, this)
  352. : (this.options.fields[field].container || this.options.fields[field].err || this.options.err.container),
  353. $message = (container && container !== 'tooltip' && container !== 'popover') ? $(container) : this._getMessageContainer($field, row);
  354. if (container && container !== 'tooltip' && container !== 'popover') {
  355. $message.addClass(this.options.err.clazz);
  356. }
  357. // Remove all error messages and feedback icons
  358. $message.find('.' + clazz + '[data-' + ns + '-validator][data-' + ns + '-for="' + field + '"]').remove();
  359. $parent.find('i[data-' + ns + '-icon-for="' + field + '"]').remove();
  360. // Whenever the user change the field value, mark it as not validated yet
  361. $field.off(events).on(events, function() {
  362. that.updateStatus($(this), that.STATUS_NOT_VALIDATED);
  363. });
  364. // Create help block elements for showing the error messages
  365. $field.data(ns + '.messages', $message);
  366. for (validatorName in validators) {
  367. $field.data(ns + '.result.' + validatorName, this.STATUS_NOT_VALIDATED);
  368. if (!updateAll || i === total - 1) {
  369. $('<small/>')
  370. .css('display', 'none')
  371. .addClass(this.options.err.clazz)
  372. .attr('data-' + ns + '-validator', validatorName)
  373. .attr('data-' + ns + '-for', field)
  374. .attr('data-' + ns + '-result', this.STATUS_NOT_VALIDATED)
  375. .html(this._getMessage(field, validatorName))
  376. .appendTo($message);
  377. }
  378. // Init the validator
  379. alias = validators[validatorName].alias || validatorName;
  380. if ('function' === typeof FormValidation.Validator[alias].init) {
  381. FormValidation.Validator[alias].init(this, $field, this.options.fields[field].validators[validatorName], validatorName);
  382. }
  383. }
  384. // Prepare the feedback icons
  385. if (this.options.fields[field].icon !== false && this.options.fields[field].icon !== 'false'
  386. && this.options.icon
  387. && this.options.icon.valid && this.options.icon.invalid && this.options.icon.validating
  388. && (!updateAll || i === total - 1))
  389. {
  390. // $parent.removeClass(this.options.row.valid).removeClass(this.options.row.invalid).addClass(this.options.row.feedback);
  391. // Keep error messages which are populated from back-end
  392. $parent.addClass(this.options.row.feedback);
  393. var $icon = $('<i/>')
  394. .css('display', 'none')
  395. .addClass(this.options.icon.feedback)
  396. .attr('data-' + ns + '-icon-for', field)
  397. .insertAfter($field);
  398. // Store the icon as a data of field element
  399. (!updateAll ? $field : fields).data(ns + '.icon', $icon);
  400. if ('tooltip' === container || 'popover' === container) {
  401. (!updateAll ? $field : fields)
  402. .on(this.options.events.fieldError, function() {
  403. $parent.addClass('fv-has-tooltip');
  404. })
  405. .on(this.options.events.fieldSuccess, function() {
  406. $parent.removeClass('fv-has-tooltip');
  407. });
  408. $field
  409. // Show tooltip/popover message when field gets focus
  410. .off('focus.container.' + ns)
  411. .on('focus.container.' + ns, function() {
  412. that._showTooltip($(this), container);
  413. })
  414. // and hide them when losing focus
  415. .off('blur.container.' + ns)
  416. .on('blur.container.' + ns, function() {
  417. that._hideTooltip($(this), container);
  418. });
  419. }
  420. if ('string' === typeof this.options.fields[field].icon && this.options.fields[field].icon !== 'true') {
  421. $icon.appendTo($(this.options.fields[field].icon));
  422. } else {
  423. this._fixIcon($field, $icon);
  424. }
  425. }
  426. }
  427. // Sort the validators by priority
  428. var sortedByPriority = [];
  429. for (validatorName in validators) {
  430. alias = validators[validatorName].alias || validatorName;
  431. // Determine the priority
  432. validators[validatorName].priority = parseInt(validators[validatorName].priority
  433. || FormValidation.Validator[alias].priority
  434. || 1, 10);
  435. sortedByPriority.push({
  436. validator: validatorName,
  437. priority: validators[validatorName].priority
  438. });
  439. }
  440. sortedByPriority = sortedByPriority.sort(function(a, b) {
  441. return a.priority - b.priority;
  442. });
  443. // Prepare the events
  444. fields
  445. .data(ns + '.validators', sortedByPriority)
  446. .on(this.options.events.fieldSuccess, function(e, data) {
  447. var onSuccess = that.getOptions(data.field, null, 'onSuccess');
  448. if (onSuccess) {
  449. FormValidation.Helper.call(onSuccess, [e, data]);
  450. }
  451. })
  452. .on(this.options.events.fieldError, function(e, data) {
  453. var onError = that.getOptions(data.field, null, 'onError');
  454. if (onError) {
  455. FormValidation.Helper.call(onError, [e, data]);
  456. }
  457. })
  458. .on(this.options.events.fieldReset, function(e, data) {
  459. var onReset = that.getOptions(data.field, null, 'onReset');
  460. if (onReset) {
  461. FormValidation.Helper.call(onReset, [e, data]);
  462. }
  463. })
  464. .on(this.options.events.fieldStatus, function(e, data) {
  465. var onStatus = that.getOptions(data.field, null, 'onStatus');
  466. if (onStatus) {
  467. FormValidation.Helper.call(onStatus, [e, data]);
  468. }
  469. })
  470. .on(this.options.events.validatorError, function(e, data) {
  471. var onError = that.getOptions(data.field, data.validator, 'onError');
  472. if (onError) {
  473. FormValidation.Helper.call(onError, [e, data]);
  474. }
  475. })
  476. .on(this.options.events.validatorIgnored, function(e, data) {
  477. var onIgnored = that.getOptions(data.field, data.validator, 'onIgnored');
  478. if (onIgnored) {
  479. FormValidation.Helper.call(onIgnored, [e, data]);
  480. }
  481. })
  482. .on(this.options.events.validatorSuccess, function(e, data) {
  483. var onSuccess = that.getOptions(data.field, data.validator, 'onSuccess');
  484. if (onSuccess) {
  485. FormValidation.Helper.call(onSuccess, [e, data]);
  486. }
  487. });
  488. // Set live mode
  489. this.onLiveChange(fields, 'live', function() {
  490. if (that._exceedThreshold($(this))) {
  491. that.validateField($(this));
  492. }
  493. });
  494. fields.trigger($.Event(this.options.events.fieldInit), {
  495. bv: this, // Support backward
  496. fv: this,
  497. field: field,
  498. element: fields
  499. });
  500. },
  501. /**
  502. * Check if the field is excluded.
  503. * Returning true means that the field will not be validated
  504. *
  505. * @param {jQuery} $field The field element
  506. * @returns {Boolean}
  507. */
  508. _isExcluded: function($field) {
  509. var ns = this._namespace,
  510. excludedAttr = $field.attr('data-' + ns + '-excluded'),
  511. // I still need to check the 'name' attribute while initializing the field
  512. field = $field.attr('data-' + ns + '-field') || $field.attr('name');
  513. switch (true) {
  514. case (!!field && this.options.fields && this.options.fields[field] && (this.options.fields[field].excluded === 'true' || this.options.fields[field].excluded === true)):
  515. case (excludedAttr === 'true'):
  516. case (excludedAttr === ''):
  517. return true;
  518. case (!!field && this.options.fields && this.options.fields[field] && (this.options.fields[field].excluded === 'false' || this.options.fields[field].excluded === false)):
  519. case (excludedAttr === 'false'):
  520. return false;
  521. case (!!field && this.options.fields && this.options.fields[field] && 'function' === typeof this.options.fields[field].excluded):
  522. return this.options.fields[field].excluded.call(this, $field, this);
  523. case (!!field && this.options.fields && this.options.fields[field] && 'string' === typeof this.options.fields[field].excluded):
  524. case (excludedAttr):
  525. return FormValidation.Helper.call(this.options.fields[field].excluded, [$field, this]);
  526. default:
  527. if (this.options.excluded) {
  528. // Convert to array first
  529. if ('string' === typeof this.options.excluded) {
  530. this.options.excluded = $.map(this.options.excluded.split(','), function(item) {
  531. // Trim the spaces
  532. return $.trim(item);
  533. });
  534. }
  535. var length = this.options.excluded.length;
  536. for (var i = 0; i < length; i++) {
  537. if (('string' === typeof this.options.excluded[i] && $field.is(this.options.excluded[i]))
  538. || ('function' === typeof this.options.excluded[i] && this.options.excluded[i].call(this, $field, this) === true))
  539. {
  540. return true;
  541. }
  542. }
  543. }
  544. return false;
  545. }
  546. },
  547. /**
  548. * Get a field changed trigger event
  549. *
  550. * @param {jQuery} $field The field element
  551. * @returns {String[]} The event names triggered on field change
  552. */
  553. _getFieldTrigger: function($field) {
  554. var ns = this._namespace,
  555. trigger = $field.data(ns + '.trigger');
  556. if (trigger) {
  557. return trigger;
  558. }
  559. // IE10/11 auto fires input event of elements using the placeholder attribute
  560. // https://connect.microsoft.com/IE/feedback/details/856700/
  561. var type = $field.attr('type'),
  562. name = $field.attr('data-' + ns + '-field'),
  563. event = ('radio' === type || 'checkbox' === type || 'file' === type || 'SELECT' === $field.get(0).tagName)
  564. ? 'change'
  565. : (this._ieVersion >= 10 && $field.attr('placeholder') ? 'keyup' : this._changeEvent);
  566. trigger = ((this.options.fields[name] ? this.options.fields[name].trigger : null) || this.options.trigger || event).split(' ');
  567. // Since the trigger data is used many times, I need to cache it to use later
  568. $field.data(ns + '.trigger', trigger);
  569. return trigger;
  570. },
  571. /**
  572. * Get the error message for given field and validator
  573. *
  574. * @param {String} field The field name
  575. * @param {String} validatorName The validator name
  576. * @returns {String}
  577. */
  578. _getMessage: function(field, validatorName) {
  579. if (!this.options.fields[field] || !this.options.fields[field].validators) {
  580. return '';
  581. }
  582. var validators = this.options.fields[field].validators,
  583. alias = (validators[validatorName] && validators[validatorName].alias) ? validators[validatorName].alias : validatorName;
  584. if (!FormValidation.Validator[alias]) {
  585. return '';
  586. }
  587. switch (true) {
  588. case !!validators[validatorName].message:
  589. return validators[validatorName].message;
  590. case !!this.options.fields[field].message:
  591. return this.options.fields[field].message;
  592. case !!this.options.message:
  593. return this.options.message;
  594. case (!!FormValidation.I18n[this.options.locale] && !!FormValidation.I18n[this.options.locale][alias] && !!FormValidation.I18n[this.options.locale][alias]['default']):
  595. return FormValidation.I18n[this.options.locale][alias]['default'];
  596. default:
  597. return this.DEFAULT_MESSAGE;
  598. }
  599. },
  600. /**
  601. * Get the element to place the error messages
  602. *
  603. * @param {jQuery} $field The field element
  604. * @param {String} row
  605. * @returns {jQuery}
  606. */
  607. _getMessageContainer: function($field, row) {
  608. if (!this.options.err.parent) {
  609. throw new Error('The err.parent option is not defined');
  610. }
  611. var $parent = $field.parent();
  612. if ($parent.is(row)) {
  613. return $parent;
  614. }
  615. var cssClasses = $parent.attr('class');
  616. if (!cssClasses) {
  617. return this._getMessageContainer($parent, row);
  618. }
  619. if (this.options.err.parent.test(cssClasses)) {
  620. return $parent;
  621. }
  622. return this._getMessageContainer($parent, row);
  623. },
  624. /**
  625. * Parse the add-on options from HTML attributes
  626. *
  627. * @returns {Object}
  628. */
  629. _parseAddOnOptions: function() {
  630. var ns = this._namespace,
  631. names = this.$form.attr('data-' + ns + '-addons'),
  632. addOns = this.options.addOns || {};
  633. if (names) {
  634. names = names.replace(/\s/g, '').split(',');
  635. for (var i = 0; i < names.length; i++) {
  636. if (!addOns[names[i]]) {
  637. addOns[names[i]] = {};
  638. }
  639. }
  640. }
  641. // Try to parse each add-on options
  642. var addOn, attrMap, attr, option;
  643. for (addOn in addOns) {
  644. if (!FormValidation.AddOn[addOn]) {
  645. // Add-on is not found
  646. delete addOns[addOn];
  647. continue;
  648. }
  649. attrMap = FormValidation.AddOn[addOn].html5Attributes;
  650. if (attrMap) {
  651. for (attr in attrMap) {
  652. option = this.$form.attr('data-' + ns + '-addons-' + addOn.toLowerCase() + '-' + attr.toLowerCase());
  653. if (option) {
  654. addOns[addOn][attrMap[attr]] = option;
  655. }
  656. }
  657. }
  658. }
  659. return addOns;
  660. },
  661. /**
  662. * Parse the validator options from HTML attributes
  663. *
  664. * @param {jQuery} $field The field element
  665. * @returns {Object}
  666. */
  667. _parseOptions: function($field) {
  668. var ns = this._namespace,
  669. field = $field.attr('name') || $field.attr('data-' + ns + '-field'),
  670. validators = {},
  671. aliasAttr = new RegExp('^data-' + ns + '-([a-z]+)-alias$'),
  672. validatorSet = $.extend({}, FormValidation.Validator),
  673. validator,
  674. v, // Validator name
  675. attrName,
  676. enabled,
  677. optionName,
  678. optionAttrName,
  679. optionValue,
  680. html5AttrName,
  681. html5AttrMap;
  682. // Determine whether the alias validator is used by checking the data-fv-validator-alias attribute
  683. $.each($field.get(0).attributes, function(i, attribute) {
  684. if (attribute.value && aliasAttr.test(attribute.name)) {
  685. v = attribute.name.split('-')[2];
  686. if (validatorSet[attribute.value]) {
  687. validatorSet[v] = validatorSet[attribute.value];
  688. validatorSet[v].alias = attribute.value;
  689. }
  690. }
  691. });
  692. for (v in validatorSet) {
  693. validator = validatorSet[v];
  694. attrName = 'data-' + ns + '-' + v.toLowerCase(),
  695. enabled = $field.attr(attrName) + '';
  696. html5AttrMap = ('function' === typeof validator.enableByHtml5) ? validator.enableByHtml5($field) : null;
  697. if ((html5AttrMap && enabled !== 'false')
  698. || (html5AttrMap !== true && ('' === enabled || 'true' === enabled || attrName === enabled.toLowerCase())))
  699. {
  700. // Try to parse the options via attributes
  701. validator.html5Attributes = $.extend({}, {
  702. message: 'message',
  703. onerror: 'onError',
  704. onreset: 'onReset',
  705. onsuccess: 'onSuccess',
  706. priority: 'priority',
  707. transformer: 'transformer'
  708. }, validator.html5Attributes);
  709. validators[v] = $.extend({}, html5AttrMap === true ? {} : html5AttrMap, validators[v]);
  710. if (validator.alias) {
  711. validators[v].alias = validator.alias;
  712. }
  713. for (html5AttrName in validator.html5Attributes) {
  714. optionName = validator.html5Attributes[html5AttrName];
  715. optionAttrName = 'data-' + ns + '-' + v.toLowerCase() + '-' + html5AttrName;
  716. optionValue = $field.attr(optionAttrName);
  717. if (optionValue) {
  718. if ('true' === optionValue || optionAttrName === optionValue.toLowerCase()) {
  719. optionValue = true;
  720. } else if ('false' === optionValue) {
  721. optionValue = false;
  722. }
  723. validators[v][optionName] = optionValue;
  724. }
  725. }
  726. }
  727. }
  728. var opts = {
  729. autoFocus: $field.attr('data-' + ns + '-autofocus'),
  730. err: $field.attr('data-' + ns + '-err-container') || $field.attr('data-' + ns + '-container'), // Support backward
  731. enabled: $field.attr('data-' + ns + '-enabled'),
  732. excluded: $field.attr('data-' + ns + '-excluded'),
  733. icon: $field.attr('data-' + ns + '-icon') || $field.attr('data-' + ns + '-feedbackicons') || (this.options.fields && this.options.fields[field] ? this.options.fields[field].feedbackIcons : null), // Support backward
  734. message: $field.attr('data-' + ns + '-message'),
  735. onError: $field.attr('data-' + ns + '-onerror'),
  736. onReset: $field.attr('data-' + ns + '-onreset'),
  737. onStatus: $field.attr('data-' + ns + '-onstatus'),
  738. onSuccess: $field.attr('data-' + ns + '-onsuccess'),
  739. row: $field.attr('data-' + ns + '-row') || $field.attr('data-' + ns + '-group') || (this.options.fields && this.options.fields[field] ? this.options.fields[field].group : null), // Support backward
  740. selector: $field.attr('data-' + ns + '-selector'),
  741. threshold: $field.attr('data-' + ns + '-threshold'),
  742. transformer: $field.attr('data-' + ns + '-transformer'),
  743. trigger: $field.attr('data-' + ns + '-trigger'),
  744. verbose: $field.attr('data-' + ns + '-verbose'),
  745. validators: validators
  746. },
  747. emptyOptions = $.isEmptyObject(opts), // Check if the field options are set using HTML attributes
  748. emptyValidators = $.isEmptyObject(validators); // Check if the field validators are set using HTML attributes
  749. if (!emptyValidators || (!emptyOptions && this.options.fields && this.options.fields[field])) {
  750. //opts.validators = validators;
  751. return opts;
  752. } else {
  753. return null;
  754. }
  755. },
  756. /**
  757. * Called when all validations are completed
  758. */
  759. _submit: function() {
  760. var isValid = this.isValid();
  761. if (isValid === null) {
  762. return;
  763. }
  764. var eventType = isValid ? this.options.events.formSuccess : this.options.events.formError,
  765. e = $.Event(eventType);
  766. this.$form.trigger(e);
  767. // Call default handler
  768. // Check if whether the submit button is clicked
  769. if (this.$submitButton) {
  770. isValid ? this._onSuccess(e) : this._onError(e);
  771. }
  772. },
  773. // ~~~~~~
  774. // Events
  775. // ~~~~~~
  776. /**
  777. * The default handler of error.form.fv event.
  778. * It will be called when there is a invalid field
  779. *
  780. * @param {jQuery.Event} e The jQuery event object
  781. */
  782. _onError: function(e) {
  783. if (e.isDefaultPrevented()) {
  784. return;
  785. }
  786. if ('submitted' === this.options.live) {
  787. // Enable live mode
  788. this.options.live = 'enabled';
  789. var that = this;
  790. for (var field in this.options.fields) {
  791. (function(f) {
  792. var fields = that.getFieldElements(f);
  793. if (fields.length) {
  794. that.onLiveChange(fields, 'live', function() {
  795. if (that._exceedThreshold($(this))) {
  796. that.validateField($(this));
  797. }
  798. });
  799. }
  800. })(field);
  801. }
  802. }
  803. // Determined the first invalid field which will be focused on automatically
  804. var ns = this._namespace;
  805. for (var i = 0; i < this.$invalidFields.length; i++) {
  806. var $field = this.$invalidFields.eq(i),
  807. autoFocus = this.isOptionEnabled($field.attr('data-' + ns + '-field'), 'autoFocus');
  808. if (autoFocus) {
  809. // Focus the field
  810. $field.focus();
  811. break;
  812. }
  813. }
  814. },
  815. /**
  816. * Called after validating a field element
  817. *
  818. * @param {jQuery} $field The field element
  819. * @param {String} [validatorName] The validator name
  820. */
  821. _onFieldValidated: function($field, validatorName) {
  822. var ns = this._namespace,
  823. field = $field.attr('data-' + ns + '-field'),
  824. validators = this.options.fields[field].validators,
  825. counter = {},
  826. numValidators = 0,
  827. data = {
  828. bv: this, // Support backward
  829. fv: this,
  830. field: field,
  831. element: $field,
  832. validator: validatorName,
  833. result: $field.data(ns + '.response.' + validatorName)
  834. };
  835. // Trigger an event after given validator completes
  836. if (validatorName) {
  837. switch ($field.data(ns + '.result.' + validatorName)) {
  838. case this.STATUS_INVALID:
  839. $field.trigger($.Event(this.options.events.validatorError), data);
  840. break;
  841. case this.STATUS_VALID:
  842. $field.trigger($.Event(this.options.events.validatorSuccess), data);
  843. break;
  844. case this.STATUS_IGNORED:
  845. $field.trigger($.Event(this.options.events.validatorIgnored), data);
  846. break;
  847. default:
  848. break;
  849. }
  850. }
  851. counter[this.STATUS_NOT_VALIDATED] = 0;
  852. counter[this.STATUS_VALIDATING] = 0;
  853. counter[this.STATUS_INVALID] = 0;
  854. counter[this.STATUS_VALID] = 0;
  855. counter[this.STATUS_IGNORED] = 0;
  856. for (var v in validators) {
  857. if (validators[v].enabled === false) {
  858. continue;
  859. }
  860. numValidators++;
  861. var result = $field.data(ns + '.result.' + v);
  862. if (result) {
  863. counter[result]++;
  864. }
  865. }
  866. // The sum of valid fields now also include ignored fields
  867. if (counter[this.STATUS_VALID] + counter[this.STATUS_IGNORED] === numValidators) {
  868. // Remove from the list of invalid fields
  869. this.$invalidFields = this.$invalidFields.not($field);
  870. $field.trigger($.Event(this.options.events.fieldSuccess), data);
  871. }
  872. // If all validators are completed and there is at least one validator which doesn't pass
  873. else if ((counter[this.STATUS_NOT_VALIDATED] === 0 || !this.isOptionEnabled(field, 'verbose')) && counter[this.STATUS_VALIDATING] === 0 && counter[this.STATUS_INVALID] > 0) {
  874. // Add to the list of invalid fields
  875. this.$invalidFields = this.$invalidFields.add($field);
  876. $field.trigger($.Event(this.options.events.fieldError), data);
  877. }
  878. },
  879. /**
  880. * The default handler of success.form.fv event.
  881. * It will be called when all the fields are valid
  882. *
  883. * @param {jQuery.Event} e The jQuery event object
  884. */
  885. _onSuccess: function(e) {
  886. if (e.isDefaultPrevented()) {
  887. return;
  888. }
  889. // Submit the form
  890. this.disableSubmitButtons(true).defaultSubmit();
  891. },
  892. // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  893. // Abstract methods
  894. // Need to be implemented by sub-class that supports specific framework
  895. // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  896. /**
  897. * Specific framework might need to adjust the icon position
  898. *
  899. * @param {jQuery} $field The field element
  900. * @param {jQuery} $icon The icon element
  901. */
  902. _fixIcon: function($field, $icon) {
  903. },
  904. /**
  905. * Create a tooltip or popover
  906. * It will be shown when focusing on the field
  907. *
  908. * @param {jQuery} $field The field element
  909. * @param {String} message The message
  910. * @param {String} type Can be 'tooltip' or 'popover'
  911. */
  912. _createTooltip: function($field, message, type) {
  913. },
  914. /**
  915. * Destroy the tooltip or popover
  916. *
  917. * @param {jQuery} $field The field element
  918. * @param {String} type Can be 'tooltip' or 'popover'
  919. */
  920. _destroyTooltip: function($field, type) {
  921. },
  922. /**
  923. * Hide a tooltip or popover
  924. *
  925. * @param {jQuery} $field The field element
  926. * @param {String} type Can be 'tooltip' or 'popover'
  927. */
  928. _hideTooltip: function($field, type) {
  929. },
  930. /**
  931. * Show a tooltip or popover
  932. *
  933. * @param {jQuery} $field The field element
  934. * @param {String} type Can be 'tooltip' or 'popover'
  935. */
  936. _showTooltip: function($field, type) {
  937. },
  938. // ~~~~~~~~~~~~~~
  939. // Public methods
  940. // ~~~~~~~~~~~~~~
  941. /**
  942. * Submit the form using default submission.
  943. * It also does not perform any validations when submitting the form
  944. */
  945. defaultSubmit: function() {
  946. var ns = this._namespace;
  947. if (this.$submitButton) {
  948. // Create hidden input to send the submit buttons
  949. $('<input/>')
  950. .attr({
  951. 'type': 'hidden',
  952. name: this.$submitButton.attr('name')
  953. })
  954. .attr('data-' + ns + '-submit-hidden', '')
  955. .val(this.$submitButton.val())
  956. .appendTo(this.$form);
  957. }
  958. // Submit form
  959. this.$form.off('submit.' + ns).submit();
  960. },
  961. /**
  962. * Disable/enable submit buttons
  963. *
  964. * @param {Boolean} disabled Can be true or false
  965. * @returns {FormValidation.Base}
  966. */
  967. disableSubmitButtons: function(disabled) {
  968. if (!disabled) {
  969. this.$form
  970. .find(this.options.button.selector)
  971. .removeAttr('disabled')
  972. .removeClass(this.options.button.disabled);
  973. } else if (this.options.live !== 'disabled') {
  974. // Don't disable if the live validating mode is disabled
  975. this.$form
  976. .find(this.options.button.selector)
  977. .attr('disabled', 'disabled')
  978. .addClass(this.options.button.disabled);
  979. }
  980. return this;
  981. },
  982. /**
  983. * Retrieve the field elements by given name
  984. *
  985. * @param {String} field The field name
  986. * @returns {null|jQuery[]}
  987. */
  988. getFieldElements: function(field) {
  989. if (!this._cacheFields[field]) {
  990. if (this.options.fields[field] && this.options.fields[field].selector) {
  991. // Look for the field inside the form first
  992. var f = this.$form.find(this.options.fields[field].selector);
  993. // If not found, search in entire document
  994. this._cacheFields[field] = f.length ? f : $(this.options.fields[field].selector);
  995. } else {
  996. this._cacheFields[field] = this.$form.find('[name="' + field + '"]');
  997. }
  998. }
  999. return this._cacheFields[field];
  1000. },
  1001. /**
  1002. * Get the field value after applying transformer
  1003. *
  1004. * @param {String|jQuery} field The field name or field element
  1005. * @param {String} validatorName The validator name
  1006. * @returns {String}
  1007. */
  1008. getFieldValue: function(field, validatorName) {
  1009. var $field, ns = this._namespace;
  1010. if ('string' === typeof field) {
  1011. $field = this.getFieldElements(field);
  1012. if ($field.length === 0) {
  1013. return null;
  1014. }
  1015. } else {
  1016. $field = field;
  1017. field = $field.attr('data-' + ns + '-field');
  1018. }
  1019. if (!field || !this.options.fields[field]) {
  1020. return $field.val();
  1021. }
  1022. var transformer = (this.options.fields[field].validators && this.options.fields[field].validators[validatorName]
  1023. ? this.options.fields[field].validators[validatorName].transformer : null)
  1024. || this.options.fields[field].transformer;
  1025. return transformer ? FormValidation.Helper.call(transformer, [$field, validatorName, this]) : $field.val();
  1026. },
  1027. /**
  1028. * Get the namespace
  1029. *
  1030. * @returns {String}
  1031. */
  1032. getNamespace: function() {
  1033. return this._namespace;
  1034. },
  1035. /**
  1036. * Get the field options
  1037. *
  1038. * @param {String|jQuery} [field] The field name or field element. If it is not set, the method returns the form options
  1039. * @param {String} [validator] The name of validator. It null, the method returns form options
  1040. * @param {String} [option] The option name
  1041. * @return {String|Object}
  1042. */
  1043. getOptions: function(field, validator, option) {
  1044. var ns = this._namespace;
  1045. if (!field) {
  1046. return option ? this.options[option] : this.options;
  1047. }
  1048. if ('object' === typeof field) {
  1049. field = field.attr('data-' + ns + '-field');
  1050. }
  1051. if (!this.options.fields[field]) {
  1052. return null;
  1053. }
  1054. var options = this.options.fields[field];
  1055. if (!validator) {
  1056. return option ? options[option] : options;
  1057. }
  1058. if (!options.validators || !options.validators[validator]) {
  1059. return null;
  1060. }
  1061. return option ? options.validators[validator][option] : options.validators[validator];
  1062. },
  1063. /**
  1064. * Get the validating result of field
  1065. *
  1066. * @param {String|jQuery} field The field name or field element
  1067. * @param {String} validatorName The validator name
  1068. * @returns {String} The status. Can be 'NOT_VALIDATED', 'VALIDATING', 'INVALID', 'VALID' or 'IGNORED'
  1069. */
  1070. getStatus: function(field, validatorName) {
  1071. var ns = this._namespace;
  1072. switch (typeof field) {
  1073. case 'object':
  1074. return field.data(ns + '.result.' + validatorName);
  1075. case 'string':
  1076. /* falls through */
  1077. default:
  1078. return this.getFieldElements(field).eq(0).data(ns + '.result.' + validatorName);
  1079. }
  1080. },
  1081. /**
  1082. * Check whether or not a field option is enabled
  1083. *
  1084. * @param {String} field The field name
  1085. * @param {String} option The option name, "verbose", "autoFocus", for example
  1086. * @returns {Boolean}
  1087. */
  1088. isOptionEnabled: function(field, option) {
  1089. if (this.options.fields[field] && (this.options.fields[field][option] === 'true' || this.options.fields[field][option] === true)) {
  1090. return true;
  1091. }
  1092. if (this.options.fields[field] && (this.options.fields[field][option] === 'false' || this.options.fields[field][option] === false)) {
  1093. return false;
  1094. }
  1095. return this.options[option] === 'true' || this.options[option] === true;
  1096. },
  1097. /**
  1098. * Check the form validity
  1099. *
  1100. * @returns {Boolean|null} Returns one of three values
  1101. * - true, if all fields are valid
  1102. * - false, if there is one invalid field
  1103. * - null, if there is at least one field which is not validated yet or being validated
  1104. */
  1105. isValid: function() {
  1106. for (var field in this.options.fields) {
  1107. var isValidField = this.isValidField(field);
  1108. if (isValidField === null) {
  1109. return null;
  1110. }
  1111. if (isValidField === false) {
  1112. return false;
  1113. }
  1114. }
  1115. return true;
  1116. },
  1117. /**
  1118. * Check if all fields inside a given container are valid.
  1119. * It's useful when working with a wizard-like such as tab, collapse
  1120. *
  1121. * @param {String|jQuery} container The container selector or element
  1122. * @returns {Boolean|null} Returns one of three values
  1123. * - true, if all fields inside the container are valid
  1124. * - false, if there is one invalid field inside the container
  1125. * - null, if the container consists of at least one field which is not validated yet or being validated
  1126. */
  1127. isValidContainer: function(container) {
  1128. var that = this,
  1129. ns = this._namespace,
  1130. fields = [],
  1131. $container = ('string' === typeof container) ? $(container) : container;
  1132. if ($container.length === 0) {
  1133. return true;
  1134. }
  1135. $container.find('[data-' + ns + '-field]').each(function() {
  1136. var $field = $(this);
  1137. if (!that._isExcluded($field)) {
  1138. fields.push($field);
  1139. }
  1140. });
  1141. var total = fields.length,
  1142. clazz = this.options.err.clazz.split(' ').join('.');
  1143. for (var i = 0; i < total; i++) {
  1144. var $f = fields[i],
  1145. field = $f.attr('data-' + ns + '-field'),
  1146. $errors = $f.data(ns + '.messages')
  1147. .find('.' + clazz + '[data-' + ns + '-validator][data-' + ns + '-for="' + field + '"]');
  1148. if (this.options.fields && this.options.fields[field]
  1149. && (this.options.fields[field].enabled === 'false' || this.options.fields[field].enabled === false))
  1150. {
  1151. continue;
  1152. }
  1153. if ($errors.filter('[data-' + ns + '-result="' + this.STATUS_INVALID + '"]').length > 0) {
  1154. return false;
  1155. }
  1156. // If the field is not validated
  1157. if ($errors.filter('[data-' + ns + '-result="' + this.STATUS_NOT_VALIDATED + '"]').length > 0
  1158. || $errors.filter('[data-' + ns + '-result="' + this.STATUS_VALIDATING + '"]').length > 0)
  1159. {
  1160. return null;
  1161. }
  1162. }
  1163. return true;
  1164. },
  1165. /**
  1166. * Check if the field is valid or not
  1167. *
  1168. * @param {String|jQuery} field The field name or field element
  1169. * @returns {Boolean|null} Returns one of three values
  1170. * - true, if the field passes all validators
  1171. * - false, if the field doesn't pass any validator
  1172. * - null, if there is at least one validator which isn't validated yet or being validated
  1173. */
  1174. isValidField: function(field) {
  1175. var ns = this._namespace,
  1176. fields = $([]);
  1177. switch (typeof field) {
  1178. case 'object':
  1179. fields = field;
  1180. field = field.attr('data-' + ns + '-field');
  1181. break;
  1182. case 'string':
  1183. fields = this.getFieldElements(field);
  1184. break;
  1185. default:
  1186. break;
  1187. }
  1188. if (fields.length === 0 || !this.options.fields[field]
  1189. || this.options.fields[field].enabled === 'false' || this.options.fields[field].enabled === false)
  1190. {
  1191. return true;
  1192. }
  1193. var type = fields.attr('type'),
  1194. total = ('radio' === type || 'checkbox' === type) ? 1 : fields.length,
  1195. $field, validatorName, status;
  1196. for (var i = 0; i < total; i++) {
  1197. $field = fields.eq(i);
  1198. if (this._isExcluded($field)) {
  1199. continue;
  1200. }
  1201. for (validatorName in this.options.fields[field].validators) {
  1202. if (this.options.fields[field].validators[validatorName].enabled === false) {
  1203. continue;
  1204. }
  1205. status = $field.data(ns + '.result.' + validatorName);
  1206. if (status === this.STATUS_VALIDATING || status === this.STATUS_NOT_VALIDATED) {
  1207. return null;
  1208. } else if (status === this.STATUS_INVALID) {
  1209. return false;
  1210. }
  1211. }
  1212. }
  1213. return true;
  1214. },
  1215. /**
  1216. * Detach a handler function for a field live change event
  1217. *
  1218. * @param {jQuery[]} $fields The field elements
  1219. * @param {String} namespace The event namespace
  1220. * @returns {FormValidation.Base}
  1221. */
  1222. offLiveChange: function($fields, namespace) {
  1223. if ($fields === null || $fields.length === 0) {
  1224. return this;
  1225. }
  1226. var ns = this._namespace,
  1227. trigger = this._getFieldTrigger($fields.eq(0)),
  1228. events = $.map(trigger, function(item) {
  1229. return item + '.' + namespace + '.' + ns;
  1230. }).join(' ');
  1231. $fields.off(events);
  1232. return this;
  1233. },
  1234. /**
  1235. * Attach a handler function for a field live change event
  1236. *
  1237. * @param {jQuery[]} $fields The field elements
  1238. * @param {String} namespace The event namespace
  1239. * @param {Function} handler The handler function
  1240. * @returns {FormValidation.Base}
  1241. */
  1242. onLiveChange: function($fields, namespace, handler) {
  1243. if ($fields === null || $fields.length === 0) {
  1244. return this;
  1245. }
  1246. var ns = this._namespace,
  1247. trigger = this._getFieldTrigger($fields.eq(0)),
  1248. events = $.map(trigger, function(item) {
  1249. return item + '.' + namespace + '.' + ns;
  1250. }).join(' ');
  1251. switch (this.options.live) {
  1252. case 'submitted':
  1253. break;
  1254. case 'disabled':
  1255. $fields.off(events);
  1256. break;
  1257. case 'enabled':
  1258. /* falls through */
  1259. default:
  1260. $fields.off(events).on(events, function(e) {
  1261. handler.apply(this, arguments);
  1262. });
  1263. break;
  1264. }
  1265. return this;
  1266. },
  1267. /**
  1268. * Update the error message
  1269. *
  1270. * @param {String|jQuery} field The field name or field element
  1271. * @param {String} validator The validator name
  1272. * @param {String} message The message
  1273. * @returns {FormValidation.Base}
  1274. */
  1275. updateMessage: function(field, validator, message) {
  1276. var ns = this._namespace,
  1277. $fields = $([]);
  1278. switch (typeof field) {
  1279. case 'object':
  1280. $fields = field;
  1281. field = field.attr('data-' + ns + '-field');
  1282. break;
  1283. case 'string':
  1284. $fields = this.getFieldElements(field);
  1285. break;
  1286. default:
  1287. break;
  1288. }
  1289. var clazz = this.options.err.clazz.split(' ').join('.');
  1290. $fields.each(function() {
  1291. $(this)
  1292. .data(ns + '.messages')
  1293. .find('.' + clazz + '[data-' + ns + '-validator="' + validator + '"][data-' + ns + '-for="' + field + '"]').html(message);
  1294. });
  1295. return this;
  1296. },
  1297. /**
  1298. * Update all validating results of field
  1299. *
  1300. * @param {String|jQuery} field The field name or field element
  1301. * @param {String} status The status. Can be 'NOT_VALIDATED', 'VALIDATING', 'INVALID', 'VALID' or 'IGNORED'
  1302. * @param {String} [validatorName] The validator name. If null, the method updates validity result for all validators
  1303. * @returns {FormValidation.Base}
  1304. */
  1305. updateStatus: function(field, status, validatorName) {
  1306. var ns = this._namespace,
  1307. fields = $([]);
  1308. switch (typeof field) {
  1309. case 'object':
  1310. fields = field;
  1311. field = field.attr('data-' + ns + '-field');
  1312. break;
  1313. case 'string':
  1314. fields = this.getFieldElements(field);
  1315. break;
  1316. default:
  1317. break;
  1318. }
  1319. if (!field || !this.options.fields[field]) {
  1320. return this;
  1321. }
  1322. if (status === this.STATUS_NOT_VALIDATED) {
  1323. // Reset the flag
  1324. // To prevent the form from doing submit when a deferred validator returns true while typing
  1325. this._submitIfValid = false;
  1326. }
  1327. var that = this,
  1328. type = fields.attr('type'),
  1329. row = this.options.fields[field].row || this.options.row.selector,
  1330. total = ('radio' === type || 'checkbox' === type) ? 1 : fields.length,
  1331. clazz = this.options.err.clazz.split(' ').join('.');
  1332. for (var i = 0; i < total; i++) {
  1333. var $field = fields.eq(i);
  1334. if (this._isExcluded($field)) {
  1335. continue;
  1336. }
  1337. var $parent = $field.closest(row),
  1338. $message = $field.data(ns + '.messages'),
  1339. $allErrors = $message.find('.' + clazz + '[data-' + ns + '-validator][data-' + ns + '-for="' + field + '"]'),
  1340. $errors = validatorName ? $allErrors.filter('[data-' + ns + '-validator="' + validatorName + '"]') : $allErrors,
  1341. $icon = $field.data(ns + '.icon'),
  1342. // Support backward
  1343. container = ('function' === typeof (this.options.fields[field].container || this.options.fields[field].err || this.options.err.container))
  1344. ? (this.options.fields[field].container || this.options.fields[field].err || this.options.err.container).call(this, $field, this)
  1345. : (this.options.fields[field].container || this.options.fields[field].err || this.options.err.container),
  1346. isValidField = null,
  1347. isValidating,
  1348. isNotValidated;
  1349. // Update status
  1350. if (validatorName) {
  1351. $field.data(ns + '.result.' + validatorName, status);
  1352. } else {
  1353. for (var v in this.options.fields[field].validators) {
  1354. $field.data(ns + '.result.' + v, status);
  1355. }
  1356. }
  1357. // Show/hide error elements and feedback icons
  1358. $errors.attr('data-' + ns + '-result', status);
  1359. switch (status) {
  1360. case this.STATUS_VALIDATING:
  1361. isValidField = null;
  1362. this.disableSubmitButtons(true);
  1363. $field.removeClass(this.options.control.valid).removeClass(this.options.control.invalid);
  1364. $parent.removeClass(this.options.row.valid).removeClass(this.options.row.invalid);
  1365. if ($icon) {
  1366. $icon.removeClass(this.options.icon.valid).removeClass(this.options.icon.invalid).addClass(this.options.icon.validating).show();
  1367. }
  1368. break;
  1369. case this.STATUS_INVALID:
  1370. isValidField = false;
  1371. this.disableSubmitButtons(true);
  1372. $field.removeClass(this.options.control.valid).addClass(this.options.control.invalid);
  1373. $parent.removeClass(this.options.row.valid).addClass(this.options.row.invalid);
  1374. if ($icon) {
  1375. $icon.removeClass(this.options.icon.valid).removeClass(this.options.icon.validating).addClass(this.options.icon.invalid).show();
  1376. }
  1377. break;
  1378. case this.STATUS_IGNORED: // Treat ignored fields like they are valid with some specialties
  1379. case this.STATUS_VALID:
  1380. isValidating = ($allErrors.filter('[data-' + ns + '-result="' + this.STATUS_VALIDATING +'"]').length > 0);
  1381. isNotValidated = ($allErrors.filter('[data-' + ns + '-result="' + this.STATUS_NOT_VALIDATED +'"]').length > 0);
  1382. var numIgnored = $allErrors.filter('[data-' + ns + '-result="' + this.STATUS_IGNORED +'"]').length;
  1383. // If the field is valid (passes all validators)
  1384. isValidField = (isValidating || isNotValidated) // There are some validators that have not done
  1385. ? null
  1386. // All validators are completed
  1387. : ($allErrors.filter('[data-' + ns + '-result="' + this.STATUS_VALID +'"]').length + numIgnored === $allErrors.length);
  1388. $field.removeClass(this.options.control.valid).removeClass(this.options.control.invalid);
  1389. if (isValidField === true) {
  1390. this.disableSubmitButtons(this.isValid() === false);
  1391. if (status === this.STATUS_VALID) {
  1392. $field.addClass(this.options.control.valid);
  1393. }
  1394. } else if (isValidField === false) {
  1395. this.disableSubmitButtons(true);
  1396. if (status === this.STATUS_VALID) {
  1397. $field.addClass(this.options.control.invalid);
  1398. }
  1399. }
  1400. if ($icon) {
  1401. $icon.removeClass(this.options.icon.invalid).removeClass(this.options.icon.validating).removeClass(this.options.icon.valid);
  1402. if (status === this.STATUS_VALID || numIgnored !== $allErrors.length) {
  1403. $icon.addClass(isValidating
  1404. ? this.options.icon.validating
  1405. : (isValidField === null ? '' : (isValidField ? this.options.icon.valid : this.options.icon.invalid)))
  1406. .show();
  1407. }
  1408. }
  1409. var isValidContainer = this.isValidContainer($parent);
  1410. if (isValidContainer !== null) {
  1411. $parent.removeClass(this.options.row.valid).removeClass(this.options.row.invalid);
  1412. if (status === this.STATUS_VALID || numIgnored !== $allErrors.length) {
  1413. $parent.addClass(isValidContainer ? this.options.row.valid : this.options.row.invalid);
  1414. }
  1415. }
  1416. break;
  1417. case this.STATUS_NOT_VALIDATED:
  1418. /* falls through */
  1419. default:
  1420. isValidField = null;
  1421. this.disableSubmitButtons(false);
  1422. $field.removeClass(this.options.control.valid).removeClass(this.options.control.invalid);
  1423. $parent.removeClass(this.options.row.valid).removeClass(this.options.row.invalid);
  1424. if ($icon) {
  1425. $icon.removeClass(this.options.icon.valid).removeClass(this.options.icon.invalid).removeClass(this.options.icon.validating).hide();
  1426. }
  1427. break;
  1428. }
  1429. if ($icon && ('tooltip' === container || 'popover' === container)) {
  1430. (isValidField === false)
  1431. // Only show the first error message
  1432. ? this._createTooltip($field, $allErrors.filter('[data-' + ns + '-result="' + that.STATUS_INVALID + '"]').eq(0).html(), container)
  1433. : this._destroyTooltip($field, container);
  1434. } else {
  1435. (status === this.STATUS_INVALID) ? $errors.show() : $errors.hide();
  1436. }
  1437. // Trigger an event
  1438. $field.trigger($.Event(this.options.events.fieldStatus), {
  1439. bv: this, // Support backward
  1440. fv: this,
  1441. field: field,
  1442. element: $field,
  1443. status: status,
  1444. validator: validatorName
  1445. });
  1446. this._onFieldValidated($field, validatorName);
  1447. }
  1448. return this;
  1449. },
  1450. /**
  1451. * Validate the form
  1452. *
  1453. * @returns {FormValidation.Base}
  1454. */
  1455. validate: function() {
  1456. if ($.isEmptyObject(this.options.fields)) {
  1457. this._submit();
  1458. return this;
  1459. }
  1460. this.$form.trigger($.Event(this.options.events.formPreValidate));
  1461. this.disableSubmitButtons(true);
  1462. this._submitIfValid = false;
  1463. for (var field in this.options.fields) {
  1464. this.validateField(field);
  1465. }
  1466. this._submit();
  1467. this._submitIfValid = true;
  1468. return this;
  1469. },
  1470. /**
  1471. * Validate given field
  1472. *
  1473. * @param {String|jQuery} field The field name or field element
  1474. * @returns {FormValidation.Base}
  1475. */
  1476. validateField: function(field) {
  1477. var ns = this._namespace,
  1478. fields = $([]);
  1479. switch (typeof field) {
  1480. case 'object':
  1481. fields = field;
  1482. field = field.attr('data-' + ns + '-field');
  1483. break;
  1484. case 'string':
  1485. fields = this.getFieldElements(field);
  1486. break;
  1487. default:
  1488. break;
  1489. }
  1490. if (fields.length === 0 || !this.options.fields[field]
  1491. || this.options.fields[field].enabled === 'false' || this.options.fields[field].enabled === false)
  1492. {
  1493. return this;
  1494. }
  1495. var that = this,
  1496. type = fields.attr('type'),
  1497. total = (('radio' === type || 'checkbox' === type) && this.options.live !== 'disabled') ? 1 : fields.length,
  1498. updateAll = ('radio' === type || 'checkbox' === type),
  1499. validators = this.options.fields[field].validators,
  1500. verbose = this.isOptionEnabled(field, 'verbose'),
  1501. validatorName,
  1502. alias,
  1503. validateResult;
  1504. for (var i = 0; i < total; i++) {
  1505. var $field = fields.eq(i);
  1506. if (this._isExcluded($field)) {
  1507. continue;
  1508. }
  1509. var stop = false,
  1510. sortedByPriority = $field.data(ns + '.validators'),
  1511. numValidators = sortedByPriority.length;
  1512. for (var j = 0; j < numValidators; j++) {
  1513. validatorName = sortedByPriority[j].validator;
  1514. if ($field.data(ns + '.dfs.' + validatorName)) {
  1515. $field.data(ns + '.dfs.' + validatorName).reject();
  1516. }
  1517. if (stop) {
  1518. break;
  1519. }
  1520. // Don't validate field if it is already done
  1521. var result = $field.data(ns + '.result.' + validatorName);
  1522. if (result === this.STATUS_VALID || result === this.STATUS_INVALID) {
  1523. this._onFieldValidated($field, validatorName);
  1524. continue;
  1525. } else if (validators[validatorName].enabled === false) {
  1526. // Changed in v0.6.2:
  1527. // When the field validator is disabled, it should be treated as STATUS_IGNORED instead of STATUS_VALID
  1528. // By doing that, the field with only disabled and ignored validators will not have success/error class
  1529. this.updateStatus(updateAll ? field : $field, this.STATUS_IGNORED, validatorName);
  1530. continue;
  1531. }
  1532. $field.data(ns + '.result.' + validatorName, this.STATUS_VALIDATING);
  1533. // Check whether or not the validator is just an alias of another
  1534. alias = validators[validatorName].alias || validatorName;
  1535. validateResult = FormValidation.Validator[alias].validate(this, $field, validators[validatorName], validatorName);
  1536. // validateResult can be a $.Deferred object ...
  1537. if ('object' === typeof validateResult && validateResult.resolve) {
  1538. this.updateStatus(updateAll ? field : $field, this.STATUS_VALIDATING, validatorName);
  1539. $field.data(ns + '.dfs.' + validatorName, validateResult);
  1540. validateResult.done(function($f, v, response) {
  1541. // v is validator name
  1542. $f.removeData(ns + '.dfs.' + v).data(ns + '.response.' + v, response);
  1543. if (response.message) {
  1544. that.updateMessage($f, v, response.message);
  1545. }
  1546. that.updateStatus(updateAll ? $f.attr('data-' + ns + '-field') : $f,
  1547. response.valid === true ? that.STATUS_VALID : (response.valid === false ? that.STATUS_INVALID : that.STATUS_IGNORED),
  1548. v);
  1549. if (response.valid && that._submitIfValid === true) {
  1550. // If a remote validator returns true and the form is ready to submit, then do it
  1551. that._submit();
  1552. } else if (response.valid === false && !verbose) {
  1553. stop = true;
  1554. }
  1555. });
  1556. }
  1557. // ... or object { valid: true/false/null, message: 'dynamic message', otherKey: value, ... }
  1558. else if ('object' === typeof validateResult && validateResult.valid !== undefined) {
  1559. $field.data(ns + '.response.' + validatorName, validateResult);
  1560. if (validateResult.message) {
  1561. this.updateMessage(updateAll ? field : $field, validatorName, validateResult.message);
  1562. }
  1563. this.updateStatus(updateAll ? field : $field,
  1564. validateResult.valid === true ? this.STATUS_VALID : (validateResult.valid === false ? this.STATUS_INVALID : this.STATUS_IGNORED),
  1565. validatorName);
  1566. if (validateResult.valid === false && !verbose) {
  1567. break;
  1568. }
  1569. }
  1570. // ... or a boolean value
  1571. else if ('boolean' === typeof validateResult) {
  1572. $field.data(ns + '.response.' + validatorName, validateResult);
  1573. this.updateStatus(updateAll ? field : $field, validateResult ? this.STATUS_VALID : this.STATUS_INVALID, validatorName);
  1574. if (!validateResult && !verbose) {
  1575. break;
  1576. }
  1577. }
  1578. // ... or null
  1579. // to indicate that the field should be ignored for current validator
  1580. else if (null === validateResult) {
  1581. $field.data(ns + '.response.' + validatorName, validateResult);
  1582. this.updateStatus(updateAll ? field : $field, this.STATUS_IGNORED, validatorName);
  1583. }
  1584. }
  1585. }
  1586. return this;
  1587. },
  1588. // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1589. // Useful APIs which aren't used internally
  1590. // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1591. /**
  1592. * Add a new field
  1593. *
  1594. * @param {String|jQuery} field The field name or field element
  1595. * @param {Object} [options] The validator rules
  1596. * @returns {FormValidation.Base}
  1597. */
  1598. addField: function(field, options) {
  1599. var ns = this._namespace,
  1600. fields = $([]);
  1601. switch (typeof field) {
  1602. case 'object':
  1603. fields = field;
  1604. field = field.attr('data-' + ns + '-field') || field.attr('name');
  1605. break;
  1606. case 'string':
  1607. delete this._cacheFields[field];
  1608. fields = this.getFieldElements(field);
  1609. break;
  1610. default:
  1611. break;
  1612. }
  1613. fields.attr('data-' + ns + '-field', field);
  1614. var type = fields.attr('type'),
  1615. total = ('radio' === type || 'checkbox' === type) ? 1 : fields.length;
  1616. for (var i = 0; i < total; i++) {
  1617. var $field = fields.eq(i);
  1618. // Try to parse the options from HTML attributes
  1619. var opts = this._parseOptions($field);
  1620. opts = (opts === null) ? options : $.extend(true, opts, options);
  1621. this.options.fields[field] = $.extend(true, this.options.fields[field], opts);
  1622. // Update the cache
  1623. this._cacheFields[field] = this._cacheFields[field] ? this._cacheFields[field].add($field) : $field;
  1624. // Init the element
  1625. this._initField(('checkbox' === type || 'radio' === type) ? field : $field);
  1626. }
  1627. this.disableSubmitButtons(false);
  1628. // Trigger an event
  1629. this.$form.trigger($.Event(this.options.events.fieldAdded), {
  1630. field: field,
  1631. element: fields,
  1632. options: this.options.fields[field]
  1633. });
  1634. return this;
  1635. },
  1636. /**
  1637. * Destroy the plugin
  1638. * It will remove all error messages, feedback icons and turn off the events
  1639. */
  1640. destroy: function() {
  1641. var ns = this._namespace, i, field, fields, $field, validator, $icon, row, alias;
  1642. // Destroy the validators first
  1643. for (field in this.options.fields) {
  1644. fields = this.getFieldElements(field);
  1645. for (i = 0; i < fields.length; i++) {
  1646. $field = fields.eq(i);
  1647. for (validator in this.options.fields[field].validators) {
  1648. if ($field.data(ns + '.dfs.' + validator)) {
  1649. $field.data(ns + '.dfs.' + validator).reject();
  1650. }
  1651. $field.removeData(ns + '.result.' + validator)
  1652. .removeData(ns + '.response.' + validator)
  1653. .removeData(ns + '.dfs.' + validator);
  1654. // Destroy the validator
  1655. alias = this.options.fields[field].validators[validator].alias || validator;
  1656. if ('function' === typeof FormValidation.Validator[alias].destroy) {
  1657. FormValidation.Validator[alias].destroy(this, $field, this.options.fields[field].validators[validator], validator);
  1658. }
  1659. }
  1660. }
  1661. }
  1662. // Remove messages and icons
  1663. var clazz = this.options.err.clazz.split(' ').join('.');
  1664. for (field in this.options.fields) {
  1665. fields = this.getFieldElements(field);
  1666. row = this.options.fields[field].row || this.options.row.selector;
  1667. for (i = 0; i < fields.length; i++) {
  1668. $field = fields.eq(i);
  1669. // Remove all error messages
  1670. var $messages = $field.data(ns + '.messages');
  1671. if ($messages) {
  1672. $messages.find('.' + clazz + '[data-' + ns + '-validator][data-' + ns + '-for="' + field + '"]').remove();
  1673. }
  1674. $field
  1675. .removeData(ns + '.messages')
  1676. .removeData(ns + '.validators')
  1677. // Remove feedback classes
  1678. .closest(row)
  1679. .removeClass(this.options.row.valid)
  1680. .removeClass(this.options.row.invalid)
  1681. .removeClass(this.options.row.feedback)
  1682. .end()
  1683. // Turn off events
  1684. .off('.' + ns)
  1685. .removeAttr('data-' + ns + '-field');
  1686. // Remove feedback icons, tooltip/popover container
  1687. // Support backward
  1688. var container = ('function' === typeof (this.options.fields[field].container || this.options.fields[field].err || this.options.err.container))
  1689. ? (this.options.fields[field].container || this.options.fields[field].err || this.options.err.container).call(this, $field, this)
  1690. : (this.options.fields[field].container || this.options.fields[field].err || this.options.err.container);
  1691. if ('tooltip' === container || 'popover' === container) {
  1692. this._destroyTooltip($field, container);
  1693. }
  1694. $icon = $field.data(ns + '.icon');
  1695. if ($icon) {
  1696. $icon.remove();
  1697. }
  1698. $field.removeData(ns + '.icon')
  1699. // It's safe to remove trigger data here, because it might be used when destroying the validator
  1700. .removeData(ns + '.trigger');
  1701. }
  1702. }
  1703. // Destroy the add-ons
  1704. for (var addOn in this.options.addOns) {
  1705. if ('function' === typeof FormValidation.AddOn[addOn].destroy) {
  1706. FormValidation.AddOn[addOn].destroy(this, this.options.addOns[addOn]);
  1707. }
  1708. }
  1709. this.disableSubmitButtons(false); // Enable submit buttons
  1710. this.$hiddenButton.remove(); // Remove the hidden button
  1711. this.$form
  1712. .removeClass(this.options.elementClass)
  1713. .off('.' + ns)
  1714. .removeData('bootstrapValidator') // Support backward
  1715. .removeData('formValidation')
  1716. // Remove generated hidden elements
  1717. .find('[data-' + ns + '-submit-hidden]').remove().end()
  1718. .find('[type="submit"]')
  1719. .off('click.' + ns);
  1720. },
  1721. /**
  1722. * Enable/Disable all validators to given field
  1723. *
  1724. * @param {String} field The field name
  1725. * @param {Boolean} enabled Enable/Disable field validators
  1726. * @param {String} [validatorName] The validator name. If null, all validators will be enabled/disabled
  1727. * @returns {FormValidation.Base}
  1728. */
  1729. enableFieldValidators: function(field, enabled, validatorName) {
  1730. var validators = this.options.fields[field].validators;
  1731. // Enable/disable particular validator
  1732. if (validatorName
  1733. && validators
  1734. && validators[validatorName] && validators[validatorName].enabled !== enabled)
  1735. {
  1736. this.options.fields[field].validators[validatorName].enabled = enabled;
  1737. this.updateStatus(field, this.STATUS_NOT_VALIDATED, validatorName);
  1738. }
  1739. // Enable/disable all validators
  1740. else if (!validatorName && this.options.fields[field].enabled !== enabled) {
  1741. this.options.fields[field].enabled = enabled;
  1742. for (var v in validators) {
  1743. this.enableFieldValidators(field, enabled, v);
  1744. }
  1745. }
  1746. return this;
  1747. },
  1748. /**
  1749. * Some validators have option which its value is dynamic.
  1750. * For example, the zipCode validator has the country option which might be changed dynamically by a select element.
  1751. *
  1752. * @param {jQuery|String} field The field name or element
  1753. * @param {String|Function} option The option which can be determined by:
  1754. * - a string
  1755. * - name of field which defines the value
  1756. * - name of function which returns the value
  1757. * - a function returns the value
  1758. *
  1759. * The callback function has the format of
  1760. * callback: function(value, validator, $field) {
  1761. * // value is the value of field
  1762. * // validator is the BootstrapValidator instance
  1763. * // $field is the field element
  1764. * }
  1765. *
  1766. * @returns {String}
  1767. */
  1768. getDynamicOption: function(field, option) {
  1769. var $field = ('string' === typeof field) ? this.getFieldElements(field) : field,
  1770. value = $field.val();
  1771. // Option can be determined by
  1772. // ... a function
  1773. if ('function' === typeof option) {
  1774. return FormValidation.Helper.call(option, [value, this, $field]);
  1775. }
  1776. // ... value of other field
  1777. else if ('string' === typeof option) {
  1778. var $f = this.getFieldElements(option);
  1779. if ($f.length) {
  1780. return $f.val();
  1781. }
  1782. // ... return value of callback
  1783. else {
  1784. return FormValidation.Helper.call(option, [value, this, $field]) || option;
  1785. }
  1786. }
  1787. return null;
  1788. },
  1789. /**
  1790. * Get the form element
  1791. *
  1792. * @returns {jQuery}
  1793. */
  1794. getForm: function() {
  1795. return this.$form;
  1796. },
  1797. /**
  1798. * Get the list of invalid fields
  1799. *
  1800. * @returns {jQuery[]}
  1801. */
  1802. getInvalidFields: function() {
  1803. return this.$invalidFields;
  1804. },
  1805. /**
  1806. * Get the current locale
  1807. *
  1808. * @return {String}
  1809. */
  1810. getLocale: function() {
  1811. return this.options.locale;
  1812. },
  1813. /**
  1814. * Get the error messages
  1815. *
  1816. * @param {String|jQuery} [field] The field name or field element
  1817. * If the field is not defined, the method returns all error messages of all fields
  1818. * @param {String} [validator] The name of validator
  1819. * If the validator is not defined, the method returns error messages of all validators
  1820. * @returns {String[]}
  1821. */
  1822. getMessages: function(field, validator) {
  1823. var that = this,
  1824. ns = this._namespace,
  1825. messages = [],
  1826. $fields = $([]);
  1827. switch (true) {
  1828. case (field && 'object' === typeof field):
  1829. $fields = field;
  1830. break;
  1831. case (field && 'string' === typeof field):
  1832. var f = this.getFieldElements(field);
  1833. if (f.length > 0) {
  1834. var type = f.attr('type');
  1835. $fields = ('radio' === type || 'checkbox' === type) ? f.eq(0) : f;
  1836. }
  1837. break;
  1838. default:
  1839. $fields = this.$invalidFields;
  1840. break;
  1841. }
  1842. var filter = validator ? '[data-' + ns + '-validator="' + validator + '"]' : '',
  1843. clazz = this.options.err.clazz.split(' ').join('.');
  1844. $fields.each(function() {
  1845. messages = messages.concat(
  1846. $(this)
  1847. .data(ns + '.messages')
  1848. .find('.' + clazz + '[data-' + ns + '-for="' + $(this).attr('data-' + ns + '-field') + '"][data-' + ns + '-result="' + that.STATUS_INVALID + '"]' + filter)
  1849. .map(function() {
  1850. var v = $(this).attr('data-' + ns + '-validator'),
  1851. f = $(this).attr('data-' + ns + '-for');
  1852. return (that.options.fields[f].validators[v].enabled === false) ? '' : $(this).html();
  1853. })
  1854. .get()
  1855. );
  1856. });
  1857. return messages;
  1858. },
  1859. /**
  1860. * Returns the clicked submit button
  1861. *
  1862. * @returns {jQuery}
  1863. */
  1864. getSubmitButton: function() {
  1865. return this.$submitButton;
  1866. },
  1867. /**
  1868. * Remove a given field
  1869. *
  1870. * @param {String|jQuery} field The field name or field element
  1871. * @returns {FormValidation.Base}
  1872. */
  1873. removeField: function(field) {
  1874. var ns = this._namespace,
  1875. fields = $([]);
  1876. switch (typeof field) {
  1877. case 'object':
  1878. fields = field;
  1879. field = field.attr('data-' + ns + '-field') || field.attr('name');
  1880. fields.attr('data-' + ns + '-field', field);
  1881. break;
  1882. case 'string':
  1883. fields = this.getFieldElements(field);
  1884. break;
  1885. default:
  1886. break;
  1887. }
  1888. if (fields.length === 0) {
  1889. return this;
  1890. }
  1891. var type = fields.attr('type'),
  1892. total = ('radio' === type || 'checkbox' === type) ? 1 : fields.length;
  1893. for (var i = 0; i < total; i++) {
  1894. var $field = fields.eq(i);
  1895. // Remove from the list of invalid fields
  1896. this.$invalidFields = this.$invalidFields.not($field);
  1897. // Update the cache
  1898. this._cacheFields[field] = this._cacheFields[field].not($field);
  1899. }
  1900. if (!this._cacheFields[field] || this._cacheFields[field].length === 0) {
  1901. delete this.options.fields[field];
  1902. }
  1903. if ('checkbox' === type || 'radio' === type) {
  1904. this._initField(field);
  1905. }
  1906. this.disableSubmitButtons(false);
  1907. // Trigger an event
  1908. this.$form.trigger($.Event(this.options.events.fieldRemoved), {
  1909. field: field,
  1910. element: fields
  1911. });
  1912. return this;
  1913. },
  1914. /**
  1915. * Reset given field
  1916. *
  1917. * @param {String|jQuery} field The field name or field element
  1918. * @param {Boolean} [resetValue] If true, the method resets field value to empty or remove checked/selected attribute (for radio/checkbox)
  1919. * @returns {FormValidation.Base}
  1920. */
  1921. resetField: function(field, resetValue) {
  1922. var ns = this._namespace,
  1923. $fields = $([]);
  1924. switch (typeof field) {
  1925. case 'object':
  1926. $fields = field;
  1927. field = field.attr('data-' + ns + '-field');
  1928. break;
  1929. case 'string':
  1930. $fields = this.getFieldElements(field);
  1931. break;
  1932. default:
  1933. break;
  1934. }
  1935. var i = 0,
  1936. total = $fields.length;
  1937. if (this.options.fields[field]) {
  1938. for (i = 0; i < total; i++) {
  1939. for (var validator in this.options.fields[field].validators) {
  1940. $fields.eq(i).removeData(ns + '.dfs.' + validator);
  1941. }
  1942. }
  1943. }
  1944. if (resetValue) {
  1945. var type = $fields.attr('type');
  1946. ('radio' === type || 'checkbox' === type) ? $fields.prop('checked', false).removeAttr('selected') : $fields.val('');
  1947. }
  1948. // Mark field as not validated yet
  1949. this.updateStatus(field, this.STATUS_NOT_VALIDATED);
  1950. for (i = 0; i < total; i++) {
  1951. $fields.eq(i).trigger($.Event(this.options.events.fieldReset), {
  1952. fv: this,
  1953. field: field,
  1954. element: $fields.eq(i),
  1955. resetValue: resetValue
  1956. });
  1957. }
  1958. return this;
  1959. },
  1960. /**
  1961. * Reset the form
  1962. *
  1963. * @param {Boolean} [resetValue] If true, the method resets field value to empty or remove checked/selected attribute (for radio/checkbox)
  1964. * @returns {FormValidation.Base}
  1965. */
  1966. resetForm: function(resetValue) {
  1967. for (var field in this.options.fields) {
  1968. this.resetField(field, resetValue);
  1969. }
  1970. this.$invalidFields = $([]);
  1971. this.$submitButton = null;
  1972. // Enable submit buttons
  1973. this.disableSubmitButtons(false);
  1974. this.$form.trigger($.Event(this.options.events.formReset), {
  1975. fv: this,
  1976. resetValue: resetValue
  1977. });
  1978. return this;
  1979. },
  1980. /**
  1981. * Revalidate given field
  1982. * It's used when you need to revalidate the field which its value is updated by other plugin
  1983. *
  1984. * @param {String|jQuery} field The field name of field element
  1985. * @returns {FormValidation.Base}
  1986. */
  1987. revalidateField: function(field) {
  1988. this.updateStatus(field, this.STATUS_NOT_VALIDATED)
  1989. .validateField(field);
  1990. return this;
  1991. },
  1992. /**
  1993. * Set the locale
  1994. *
  1995. * @param {String} locale The locale in format of countrycode_LANGUAGECODE
  1996. * @returns {FormValidation.Base}
  1997. */
  1998. setLocale: function(locale) {
  1999. this.options.locale = locale;
  2000. this.$form.trigger($.Event(this.options.events.localeChanged), {
  2001. locale: locale,
  2002. bv: this, // Support backward
  2003. fv: this
  2004. });
  2005. return this;
  2006. },
  2007. /**
  2008. * Update the option of a specific validator
  2009. *
  2010. * @param {String|jQuery} field The field name or field element
  2011. * @param {String} validator The validator name
  2012. * @param {String} option The option name
  2013. * @param {String} value The value to set
  2014. * @returns {FormValidation.Base}
  2015. */
  2016. updateOption: function(field, validator, option, value) {
  2017. var ns = this._namespace;
  2018. if ('object' === typeof field) {
  2019. field = field.attr('data-' + ns + '-field');
  2020. }
  2021. if (this.options.fields[field] && this.options.fields[field].validators[validator]) {
  2022. this.options.fields[field].validators[validator][option] = value;
  2023. this.updateStatus(field, this.STATUS_NOT_VALIDATED, validator);
  2024. }
  2025. return this;
  2026. },
  2027. /**
  2028. * Validate given container
  2029. * It can be used with isValidContainer() when you want to work with wizard form
  2030. *
  2031. * @param {String|jQuery} container The container selector or element
  2032. * @returns {FormValidation.Base}
  2033. */
  2034. validateContainer: function(container) {
  2035. var that = this,
  2036. ns = this._namespace,
  2037. fields = [],
  2038. $container = ('string' === typeof container) ? $(container) : container;
  2039. if ($container.length === 0) {
  2040. return this;
  2041. }
  2042. $container.find('[data-' + ns + '-field]').each(function() {
  2043. var $field = $(this);
  2044. if (!that._isExcluded($field)) {
  2045. fields.push($field);
  2046. }
  2047. });
  2048. var total = fields.length;
  2049. for (var i = 0; i < total; i++) {
  2050. this.validateField(fields[i]);
  2051. }
  2052. return this;
  2053. }
  2054. };
  2055. // Plugin definition
  2056. $.fn.formValidation = function(option) {
  2057. var params = arguments;
  2058. return this.each(function() {
  2059. var $this = $(this),
  2060. data = $this.data('formValidation'),
  2061. options = 'object' === typeof option && option;
  2062. if (!data) {
  2063. var framework = (options.framework || $this.attr('data-fv-framework') || 'bootstrap').toLowerCase(),
  2064. clazz = framework.substr(0, 1).toUpperCase() + framework.substr(1);
  2065. if (typeof FormValidation.Framework[clazz] === 'undefined') {
  2066. throw new Error('The class FormValidation.Framework.' + clazz + ' is not implemented');
  2067. }
  2068. data = new FormValidation.Framework[clazz](this, options);
  2069. $this.addClass('fv-form-' + framework)
  2070. .data('formValidation', data);
  2071. }
  2072. // Allow to call plugin method
  2073. if ('string' === typeof option) {
  2074. data[option].apply(data, Array.prototype.slice.call(params, 1));
  2075. }
  2076. });
  2077. };
  2078. $.fn.formValidation.Constructor = FormValidation.Base;
  2079. // Default message
  2080. $.fn.formValidation.DEFAULT_MESSAGE = 'This value is not valid';
  2081. // The default options sorted in alphabetical order
  2082. $.fn.formValidation.DEFAULT_OPTIONS = {
  2083. // The first invalid field will be focused automatically
  2084. autoFocus: true,
  2085. // Support declarative usage (setting options via HTML 5 attributes)
  2086. // Setting to false can improve the performance
  2087. declarative: true,
  2088. // The form CSS class
  2089. elementClass: 'fv-form',
  2090. // Use custom event name to avoid window.onerror being invoked by jQuery
  2091. // See #630
  2092. events: {
  2093. // Support backward
  2094. formInit: 'init.form.fv',
  2095. formPreValidate: 'prevalidate.form.fv',
  2096. formError: 'err.form.fv',
  2097. formReset: 'rst.form.fv',
  2098. formSuccess: 'success.form.fv',
  2099. fieldAdded: 'added.field.fv',
  2100. fieldRemoved: 'removed.field.fv',
  2101. fieldInit: 'init.field.fv',
  2102. fieldError: 'err.field.fv',
  2103. fieldReset: 'rst.field.fv',
  2104. fieldSuccess: 'success.field.fv',
  2105. fieldStatus: 'status.field.fv',
  2106. localeChanged: 'changed.locale.fv',
  2107. validatorError: 'err.validator.fv',
  2108. validatorSuccess: 'success.validator.fv',
  2109. validatorIgnored: 'ignored.validator.fv'
  2110. },
  2111. // Indicate fields which won't be validated
  2112. // By default, the plugin will not validate the following kind of fields:
  2113. // - disabled
  2114. // - hidden
  2115. // - invisible
  2116. //
  2117. // The setting consists of jQuery filters. Accept 3 formats:
  2118. // - A string. Use a comma to separate filter
  2119. // - An array. Each element is a filter
  2120. // - An array. Each element can be a callback function
  2121. // function($field, validator) {
  2122. // $field is jQuery object representing the field element
  2123. // validator is the BootstrapValidator instance
  2124. // return true or false;
  2125. // }
  2126. //
  2127. // The 3 following settings are equivalent:
  2128. //
  2129. // 1) ':disabled, :hidden, :not(:visible)'
  2130. // 2) [':disabled', ':hidden', ':not(:visible)']
  2131. // 3) [':disabled', ':hidden', function($field) {
  2132. // return !$field.is(':visible');
  2133. // }]
  2134. excluded: [':disabled', ':hidden', ':not(:visible)'],
  2135. // Map the field name with validator rules
  2136. fields: null,
  2137. // Live validating option
  2138. // Can be one of 3 values:
  2139. // - enabled: The plugin validates fields as soon as they are changed
  2140. // - disabled: Disable the live validating. The error messages are only shown after the form is submitted
  2141. // - submitted: The live validating is enabled after the form is submitted
  2142. live: 'enabled',
  2143. // Locale in the format of languagecode_COUNTRYCODE
  2144. locale: 'en_US',
  2145. // Default invalid message
  2146. message: null,
  2147. // The field will not be live validated if its length is less than this number of characters
  2148. threshold: null,
  2149. // Whether to be verbose when validating a field or not.
  2150. // Possible values:
  2151. // - true: when a field has multiple validators, all of them will be checked, and respectively - if errors occur in
  2152. // multiple validators, all of them will be displayed to the user
  2153. // - false: when a field has multiple validators, validation for this field will be terminated upon the first encountered error.
  2154. // Thus, only the very first error message related to this field will be displayed to the user
  2155. verbose: true,
  2156. // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  2157. // These options mostly are overridden by specific framework
  2158. // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  2159. button: {
  2160. // The submit buttons selector
  2161. // These buttons will be disabled to prevent the valid form from multiple submissions
  2162. // Don't perform validation when clicking on the submit button/input which have formnovalidate attribute
  2163. selector: '[type="submit"]:not([formnovalidate])',
  2164. // The disabled class
  2165. disabled: ''
  2166. },
  2167. control: {
  2168. // The CSS class for valid control
  2169. valid: '',
  2170. // The CSS class for invalid control
  2171. invalid: ''
  2172. },
  2173. err: {
  2174. // The CSS class of each message element
  2175. clazz: '',
  2176. // The error messages container. It can be:
  2177. // - 'tooltip' if you want to use Bootstrap tooltip to show error messages
  2178. // - 'popover' if you want to use Bootstrap popover to show error messages
  2179. // - a CSS selector indicating the container
  2180. // In the first two cases, since the tooltip/popover should be small enough, the plugin only shows only one error message
  2181. // You also can define the message container for particular field
  2182. container: null,
  2183. // Used to determine where the messages are placed
  2184. parent: null
  2185. },
  2186. // Shows ok/error/loading icons based on the field validity.
  2187. icon: {
  2188. valid: null,
  2189. invalid: null,
  2190. validating: null,
  2191. feedback: ''
  2192. },
  2193. row: {
  2194. // The CSS selector for indicating the element consists of the field
  2195. // You should adjust this option if your form group consists of many fields which not all of them need to be validated
  2196. selector: null,
  2197. valid: '',
  2198. invalid: '',
  2199. feedback: ''
  2200. }
  2201. };
  2202. }(jQuery));
  2203. ;(function($) {
  2204. // Helper methods, which can be used in validator class
  2205. FormValidation.Helper = {
  2206. /**
  2207. * Execute a callback function
  2208. *
  2209. * @param {String|Function} functionName Can be
  2210. * - name of global function
  2211. * - name of namespace function (such as A.B.C)
  2212. * - a function
  2213. * @param {Array} args The callback arguments
  2214. */
  2215. call: function(functionName, args) {
  2216. if ('function' === typeof functionName) {
  2217. return functionName.apply(this, args);
  2218. } else if ('string' === typeof functionName) {
  2219. if ('()' === functionName.substring(functionName.length - 2)) {
  2220. functionName = functionName.substring(0, functionName.length - 2);
  2221. }
  2222. var ns = functionName.split('.'),
  2223. func = ns.pop(),
  2224. context = window;
  2225. for (var i = 0; i < ns.length; i++) {
  2226. context = context[ns[i]];
  2227. }
  2228. return (typeof context[func] === 'undefined') ? null : context[func].apply(this, args);
  2229. }
  2230. },
  2231. /**
  2232. * Validate a date
  2233. *
  2234. * @param {Number} year The full year in 4 digits
  2235. * @param {Number} month The month number
  2236. * @param {Number} day The day number
  2237. * @param {Boolean} [notInFuture] If true, the date must not be in the future
  2238. * @returns {Boolean}
  2239. */
  2240. date: function(year, month, day, notInFuture) {
  2241. if (isNaN(year) || isNaN(month) || isNaN(day)) {
  2242. return false;
  2243. }
  2244. if (day.length > 2 || month.length > 2 || year.length > 4) {
  2245. return false;
  2246. }
  2247. day = parseInt(day, 10);
  2248. month = parseInt(month, 10);
  2249. year = parseInt(year, 10);
  2250. if (year < 1000 || year > 9999 || month <= 0 || month > 12) {
  2251. return false;
  2252. }
  2253. var numDays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
  2254. // Update the number of days in Feb of leap year
  2255. if (year % 400 === 0 || (year % 100 !== 0 && year % 4 === 0)) {
  2256. numDays[1] = 29;
  2257. }
  2258. // Check the day
  2259. if (day <= 0 || day > numDays[month - 1]) {
  2260. return false;
  2261. }
  2262. if (notInFuture === true) {
  2263. var currentDate = new Date(),
  2264. currentYear = currentDate.getFullYear(),
  2265. currentMonth = currentDate.getMonth(),
  2266. currentDay = currentDate.getDate();
  2267. return (year < currentYear
  2268. || (year === currentYear && month - 1 < currentMonth)
  2269. || (year === currentYear && month - 1 === currentMonth && day < currentDay));
  2270. }
  2271. return true;
  2272. },
  2273. /**
  2274. * Format a string
  2275. * It's used to format the error message
  2276. * format('The field must between %s and %s', [10, 20]) = 'The field must between 10 and 20'
  2277. *
  2278. * @param {String} message
  2279. * @param {Array} parameters
  2280. * @returns {String}
  2281. */
  2282. format: function(message, parameters) {
  2283. if (!$.isArray(parameters)) {
  2284. parameters = [parameters];
  2285. }
  2286. for (var i in parameters) {
  2287. message = message.replace('%s', parameters[i]);
  2288. }
  2289. return message;
  2290. },
  2291. /**
  2292. * Implement Luhn validation algorithm
  2293. * Credit to https://gist.github.com/ShirtlessKirk/2134376
  2294. *
  2295. * @see http://en.wikipedia.org/wiki/Luhn
  2296. * @param {String} value
  2297. * @returns {Boolean}
  2298. */
  2299. luhn: function(value) {
  2300. var length = value.length,
  2301. mul = 0,
  2302. prodArr = [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], [0, 2, 4, 6, 8, 1, 3, 5, 7, 9]],
  2303. sum = 0;
  2304. while (length--) {
  2305. sum += prodArr[mul][parseInt(value.charAt(length), 10)];
  2306. mul ^= 1;
  2307. }
  2308. return (sum % 10 === 0 && sum > 0);
  2309. },
  2310. /**
  2311. * Implement modulus 11, 10 (ISO 7064) algorithm
  2312. *
  2313. * @param {String} value
  2314. * @returns {Boolean}
  2315. */
  2316. mod11And10: function(value) {
  2317. var check = 5,
  2318. length = value.length;
  2319. for (var i = 0; i < length; i++) {
  2320. check = (((check || 10) * 2) % 11 + parseInt(value.charAt(i), 10)) % 10;
  2321. }
  2322. return (check === 1);
  2323. },
  2324. /**
  2325. * Implements Mod 37, 36 (ISO 7064) algorithm
  2326. * Usages:
  2327. * mod37And36('A12425GABC1234002M')
  2328. * mod37And36('002006673085', '0123456789')
  2329. *
  2330. * @param {String} value
  2331. * @param {String} [alphabet]
  2332. * @returns {Boolean}
  2333. */
  2334. mod37And36: function(value, alphabet) {
  2335. alphabet = alphabet || '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
  2336. var modulus = alphabet.length,
  2337. length = value.length,
  2338. check = Math.floor(modulus / 2);
  2339. for (var i = 0; i < length; i++) {
  2340. check = (((check || modulus) * 2) % (modulus + 1) + alphabet.indexOf(value.charAt(i))) % modulus;
  2341. }
  2342. return (check === 1);
  2343. }
  2344. };
  2345. }(jQuery));
  2346. ;(function($) {
  2347. FormValidation.I18n = $.extend(true, FormValidation.I18n || {}, {
  2348. 'en_US': {
  2349. between: {
  2350. 'default': 'Please enter a value between %s and %s',
  2351. notInclusive: 'Please enter a value between %s and %s strictly'
  2352. }
  2353. }
  2354. });
  2355. FormValidation.Validator.between = {
  2356. html5Attributes: {
  2357. message: 'message',
  2358. min: 'min',
  2359. max: 'max',
  2360. inclusive: 'inclusive'
  2361. },
  2362. enableByHtml5: function($field) {
  2363. if ('range' === $field.attr('type')) {
  2364. return {
  2365. min: $field.attr('min'),
  2366. max: $field.attr('max')
  2367. };
  2368. }
  2369. return false;
  2370. },
  2371. /**
  2372. * Return true if the input value is between (strictly or not) two given numbers
  2373. *
  2374. * @param {FormValidation.Base} validator The validator plugin instance
  2375. * @param {jQuery} $field Field element
  2376. * @param {Object} options Can consist of the following keys:
  2377. * - min
  2378. * - max
  2379. *
  2380. * The min, max keys define the number which the field value compares to. min, max can be
  2381. * - A number
  2382. * - Name of field which its value defines the number
  2383. * - Name of callback function that returns the number
  2384. * - A callback function that returns the number
  2385. *
  2386. * - inclusive [optional]: Can be true or false. Default is true
  2387. * - message: The invalid message
  2388. * @returns {Boolean|Object}
  2389. */
  2390. validate: function(validator, $field, options, validatorName) {
  2391. var value = validator.getFieldValue($field, validatorName);
  2392. if (value === '') {
  2393. return true;
  2394. }
  2395. value = this._format(value);
  2396. var locale = validator.getLocale(),
  2397. min = $.isNumeric(options.min) ? options.min : validator.getDynamicOption($field, options.min),
  2398. max = $.isNumeric(options.max) ? options.max : validator.getDynamicOption($field, options.max),
  2399. minValue = this._format(min),
  2400. maxValue = this._format(max);
  2401. return (options.inclusive === true || options.inclusive === undefined)
  2402. ? {
  2403. valid: $.isNumeric(value) && parseFloat(value) >= minValue && parseFloat(value) <= maxValue,
  2404. message: FormValidation.Helper.format(options.message || FormValidation.I18n[locale].between['default'], [min, max])
  2405. }
  2406. : {
  2407. valid: $.isNumeric(value) && parseFloat(value) > minValue && parseFloat(value) < maxValue,
  2408. message: FormValidation.Helper.format(options.message || FormValidation.I18n[locale].between.notInclusive, [min, max])
  2409. };
  2410. },
  2411. _format: function(value) {
  2412. return (value + '').replace(',', '.');
  2413. }
  2414. };
  2415. }(jQuery));
  2416. ;(function($) {
  2417. FormValidation.I18n = $.extend(true, FormValidation.I18n || {}, {
  2418. 'en_US': {
  2419. callback: {
  2420. 'default': 'Please enter a valid value'
  2421. }
  2422. }
  2423. });
  2424. FormValidation.Validator.callback = {
  2425. priority: 999,
  2426. html5Attributes: {
  2427. message: 'message',
  2428. callback: 'callback'
  2429. },
  2430. /**
  2431. * Return result from the callback method
  2432. *
  2433. * @param {FormValidation.Base} validator The validator plugin instance
  2434. * @param {jQuery} $field Field element
  2435. * @param {Object} options Can consist of the following keys:
  2436. * - callback: The callback method that passes parameters:
  2437. * callback: function(fieldValue, validator, $field) {
  2438. * // fieldValue is the value of field
  2439. * // validator is instance of FormValidation.Base
  2440. * // $field is the field element
  2441. * }
  2442. * - message: The invalid message
  2443. * @returns {Deferred}
  2444. */
  2445. validate: function(validator, $field, options, validatorName) {
  2446. var value = validator.getFieldValue($field, validatorName),
  2447. dfd = new $.Deferred(),
  2448. result = { valid: true };
  2449. if (options.callback) {
  2450. var response = FormValidation.Helper.call(options.callback, [value, validator, $field]);
  2451. result = ('boolean' === typeof response || null === response) ? { valid: response } : response;
  2452. }
  2453. dfd.resolve($field, validatorName, result);
  2454. return dfd;
  2455. }
  2456. };
  2457. }(jQuery));
  2458. ;(function($) {
  2459. FormValidation.I18n = $.extend(true, FormValidation.I18n || {}, {
  2460. 'en_US': {
  2461. choice: {
  2462. 'default': 'Please enter a valid value',
  2463. less: 'Please choose %s options at minimum',
  2464. more: 'Please choose %s options at maximum',
  2465. between: 'Please choose %s - %s options'
  2466. }
  2467. }
  2468. });
  2469. FormValidation.Validator.choice = {
  2470. html5Attributes: {
  2471. message: 'message',
  2472. min: 'min',
  2473. max: 'max'
  2474. },
  2475. /**
  2476. * Check if the number of checked boxes are less or more than a given number
  2477. *
  2478. * @param {FormValidation.Base} validator The validator plugin instance
  2479. * @param {jQuery} $field Field element
  2480. * @param {Object} options Consists of following keys:
  2481. * - min
  2482. * - max
  2483. *
  2484. * At least one of two keys is required
  2485. * The min, max keys define the number which the field value compares to. min, max can be
  2486. * - A number
  2487. * - Name of field which its value defines the number
  2488. * - Name of callback function that returns the number
  2489. * - A callback function that returns the number
  2490. *
  2491. * - message: The invalid message
  2492. * @returns {Object}
  2493. */
  2494. validate: function(validator, $field, options, validatorName) {
  2495. var locale = validator.getLocale(),
  2496. ns = validator.getNamespace(),
  2497. numChoices = $field.is('select')
  2498. ? validator.getFieldElements($field.attr('data-' + ns + '-field')).find('option').filter(':selected').length
  2499. : validator.getFieldElements($field.attr('data-' + ns + '-field')).filter(':checked').length,
  2500. min = options.min ? ($.isNumeric(options.min) ? options.min : validator.getDynamicOption($field, options.min)) : null,
  2501. max = options.max ? ($.isNumeric(options.max) ? options.max : validator.getDynamicOption($field, options.max)) : null,
  2502. isValid = true,
  2503. message = options.message || FormValidation.I18n[locale].choice['default'];
  2504. if ((min && numChoices < parseInt(min, 10)) || (max && numChoices > parseInt(max, 10))) {
  2505. isValid = false;
  2506. }
  2507. switch (true) {
  2508. case (!!min && !!max):
  2509. message = FormValidation.Helper.format(options.message || FormValidation.I18n[locale].choice.between, [parseInt(min, 10), parseInt(max, 10)]);
  2510. break;
  2511. case (!!min):
  2512. message = FormValidation.Helper.format(options.message || FormValidation.I18n[locale].choice.less, parseInt(min, 10));
  2513. break;
  2514. case (!!max):
  2515. message = FormValidation.Helper.format(options.message || FormValidation.I18n[locale].choice.more, parseInt(max, 10));
  2516. break;
  2517. default:
  2518. break;
  2519. }
  2520. return { valid: isValid, message: message };
  2521. }
  2522. };
  2523. }(jQuery));
  2524. ;(function($) {
  2525. FormValidation.I18n = $.extend(true, FormValidation.I18n || {}, {
  2526. 'en_US': {
  2527. color: {
  2528. 'default': 'Please enter a valid color'
  2529. }
  2530. }
  2531. });
  2532. FormValidation.Validator.color = {
  2533. html5Attributes: {
  2534. message: 'message',
  2535. type: 'type'
  2536. },
  2537. enableByHtml5: function($field) {
  2538. return ('color' === $field.attr('type'));
  2539. },
  2540. SUPPORTED_TYPES: [
  2541. 'hex', 'rgb', 'rgba', 'hsl', 'hsla', 'keyword'
  2542. ],
  2543. KEYWORD_COLORS: [
  2544. // Colors start with A
  2545. 'aliceblue', 'antiquewhite', 'aqua', 'aquamarine', 'azure',
  2546. // B
  2547. 'beige', 'bisque', 'black', 'blanchedalmond', 'blue', 'blueviolet', 'brown', 'burlywood',
  2548. // C
  2549. 'cadetblue', 'chartreuse', 'chocolate', 'coral', 'cornflowerblue', 'cornsilk', 'crimson', 'cyan',
  2550. // D
  2551. 'darkblue', 'darkcyan', 'darkgoldenrod', 'darkgray', 'darkgreen', 'darkgrey', 'darkkhaki', 'darkmagenta',
  2552. 'darkolivegreen', 'darkorange', 'darkorchid', 'darkred', 'darksalmon', 'darkseagreen', 'darkslateblue',
  2553. 'darkslategray', 'darkslategrey', 'darkturquoise', 'darkviolet', 'deeppink', 'deepskyblue', 'dimgray',
  2554. 'dimgrey', 'dodgerblue',
  2555. // F
  2556. 'firebrick', 'floralwhite', 'forestgreen', 'fuchsia',
  2557. // G
  2558. 'gainsboro', 'ghostwhite', 'gold', 'goldenrod', 'gray', 'green', 'greenyellow', 'grey',
  2559. // H
  2560. 'honeydew', 'hotpink',
  2561. // I
  2562. 'indianred', 'indigo', 'ivory',
  2563. // K
  2564. 'khaki',
  2565. // L
  2566. 'lavender', 'lavenderblush', 'lawngreen', 'lemonchiffon', 'lightblue', 'lightcoral', 'lightcyan',
  2567. 'lightgoldenrodyellow', 'lightgray', 'lightgreen', 'lightgrey', 'lightpink', 'lightsalmon', 'lightseagreen',
  2568. 'lightskyblue', 'lightslategray', 'lightslategrey', 'lightsteelblue', 'lightyellow', 'lime', 'limegreen',
  2569. 'linen',
  2570. // M
  2571. 'magenta', 'maroon', 'mediumaquamarine', 'mediumblue', 'mediumorchid', 'mediumpurple', 'mediumseagreen',
  2572. 'mediumslateblue', 'mediumspringgreen', 'mediumturquoise', 'mediumvioletred', 'midnightblue', 'mintcream',
  2573. 'mistyrose', 'moccasin',
  2574. // N
  2575. 'navajowhite', 'navy',
  2576. // O
  2577. 'oldlace', 'olive', 'olivedrab', 'orange', 'orangered', 'orchid',
  2578. // P
  2579. 'palegoldenrod', 'palegreen', 'paleturquoise', 'palevioletred', 'papayawhip', 'peachpuff', 'peru', 'pink',
  2580. 'plum', 'powderblue', 'purple',
  2581. // R
  2582. 'red', 'rosybrown', 'royalblue',
  2583. // S
  2584. 'saddlebrown', 'salmon', 'sandybrown', 'seagreen', 'seashell', 'sienna', 'silver', 'skyblue', 'slateblue',
  2585. 'slategray', 'slategrey', 'snow', 'springgreen', 'steelblue',
  2586. // T
  2587. 'tan', 'teal', 'thistle', 'tomato', 'transparent', 'turquoise',
  2588. // V
  2589. 'violet',
  2590. // W
  2591. 'wheat', 'white', 'whitesmoke',
  2592. // Y
  2593. 'yellow', 'yellowgreen'
  2594. ],
  2595. /**
  2596. * Return true if the input value is a valid color
  2597. *
  2598. * @param {FormValidation.Base} validator The validator plugin instance
  2599. * @param {jQuery} $field Field element
  2600. * @param {Object} options Can consist of the following keys:
  2601. * - message: The invalid message
  2602. * - type: The array of valid color types
  2603. * @returns {Boolean}
  2604. */
  2605. validate: function(validator, $field, options, validatorName) {
  2606. var value = validator.getFieldValue($field, validatorName);
  2607. if (value === '') {
  2608. return true;
  2609. }
  2610. // Only accept 6 hex character values due to the HTML 5 spec
  2611. // See http://www.w3.org/TR/html-markup/input.color.html#input.color.attrs.value
  2612. if (this.enableByHtml5($field)) {
  2613. return /^#[0-9A-F]{6}$/i.test(value);
  2614. }
  2615. var types = options.type || this.SUPPORTED_TYPES;
  2616. if (!$.isArray(types)) {
  2617. types = types.replace(/s/g, '').split(',');
  2618. }
  2619. var method,
  2620. type,
  2621. isValid = false;
  2622. for (var i = 0; i < types.length; i++) {
  2623. type = types[i];
  2624. method = '_' + type.toLowerCase();
  2625. isValid = isValid || this[method](value);
  2626. if (isValid) {
  2627. return true;
  2628. }
  2629. }
  2630. return false;
  2631. },
  2632. _hex: function(value) {
  2633. return /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(value);
  2634. },
  2635. _hsl: function(value) {
  2636. return /^hsl\((\s*(-?\d+)\s*,)(\s*(\b(0?\d{1,2}|100)\b%)\s*,)(\s*(\b(0?\d{1,2}|100)\b%)\s*)\)$/.test(value);
  2637. },
  2638. _hsla: function(value) {
  2639. return /^hsla\((\s*(-?\d+)\s*,)(\s*(\b(0?\d{1,2}|100)\b%)\s*,){2}(\s*(0?(\.\d+)?|1(\.0+)?)\s*)\)$/.test(value);
  2640. },
  2641. _keyword: function(value) {
  2642. return $.inArray(value, this.KEYWORD_COLORS) >= 0;
  2643. },
  2644. _rgb: function(value) {
  2645. var regexInteger = /^rgb\((\s*(\b([01]?\d{1,2}|2[0-4]\d|25[0-5])\b)\s*,){2}(\s*(\b([01]?\d{1,2}|2[0-4]\d|25[0-5])\b)\s*)\)$/,
  2646. regexPercent = /^rgb\((\s*(\b(0?\d{1,2}|100)\b%)\s*,){2}(\s*(\b(0?\d{1,2}|100)\b%)\s*)\)$/;
  2647. return regexInteger.test(value) || regexPercent.test(value);
  2648. },
  2649. _rgba: function(value) {
  2650. var regexInteger = /^rgba\((\s*(\b([01]?\d{1,2}|2[0-4]\d|25[0-5])\b)\s*,){3}(\s*(0?(\.\d+)?|1(\.0+)?)\s*)\)$/,
  2651. regexPercent = /^rgba\((\s*(\b(0?\d{1,2}|100)\b%)\s*,){3}(\s*(0?(\.\d+)?|1(\.0+)?)\s*)\)$/;
  2652. return regexInteger.test(value) || regexPercent.test(value);
  2653. }
  2654. };
  2655. }(jQuery));
  2656. ;(function($) {
  2657. FormValidation.I18n = $.extend(true, FormValidation.I18n || {}, {
  2658. 'en_US': {
  2659. creditCard: {
  2660. 'default': 'Please enter a valid credit card number'
  2661. }
  2662. }
  2663. });
  2664. FormValidation.Validator.creditCard = {
  2665. /**
  2666. * Return true if the input value is valid credit card number
  2667. * Based on https://gist.github.com/DiegoSalazar/4075533
  2668. *
  2669. * @param {FormValidation.Base} validator The validator plugin instance
  2670. * @param {jQuery} $field Field element
  2671. * @param {Object} [options] Can consist of the following key:
  2672. * - message: The invalid message
  2673. * @returns {Boolean|Object}
  2674. */
  2675. validate: function(validator, $field, options, validatorName) {
  2676. var value = validator.getFieldValue($field, validatorName);
  2677. if (value === '') {
  2678. return true;
  2679. }
  2680. // Accept only digits, dashes or spaces
  2681. if (/[^0-9-\s]+/.test(value)) {
  2682. return false;
  2683. }
  2684. value = value.replace(/\D/g, '');
  2685. if (!FormValidation.Helper.luhn(value)) {
  2686. return false;
  2687. }
  2688. // Validate the card number based on prefix (IIN ranges) and length
  2689. var cards = {
  2690. AMERICAN_EXPRESS: {
  2691. length: [15],
  2692. prefix: ['34', '37']
  2693. },
  2694. DANKORT: {
  2695. length: [16],
  2696. prefix: ['5019']
  2697. },
  2698. DINERS_CLUB: {
  2699. length: [14],
  2700. prefix: ['300', '301', '302', '303', '304', '305', '36']
  2701. },
  2702. DINERS_CLUB_US: {
  2703. length: [16],
  2704. prefix: ['54', '55']
  2705. },
  2706. DISCOVER: {
  2707. length: [16],
  2708. prefix: ['6011', '622126', '622127', '622128', '622129', '62213',
  2709. '62214', '62215', '62216', '62217', '62218', '62219',
  2710. '6222', '6223', '6224', '6225', '6226', '6227', '6228',
  2711. '62290', '62291', '622920', '622921', '622922', '622923',
  2712. '622924', '622925', '644', '645', '646', '647', '648',
  2713. '649', '65']
  2714. },
  2715. ELO: {
  2716. length: [16],
  2717. prefix: ['4011', '4312', '4389', '4514', '4573', '4576',
  2718. '5041', '5066', '5067', '509',
  2719. '6277', '6362', '6363', '650', '6516', '6550']
  2720. },
  2721. FORBRUGSFORENINGEN: {
  2722. length: [16],
  2723. prefix: ['600722']
  2724. },
  2725. JCB: {
  2726. length: [16],
  2727. prefix: ['3528', '3529', '353', '354', '355', '356', '357', '358']
  2728. },
  2729. LASER: {
  2730. length: [16, 17, 18, 19],
  2731. prefix: ['6304', '6706', '6771', '6709']
  2732. },
  2733. MAESTRO: {
  2734. length: [12, 13, 14, 15, 16, 17, 18, 19],
  2735. prefix: ['5018', '5020', '5038', '5868', '6304', '6759', '6761', '6762', '6763', '6764', '6765', '6766']
  2736. },
  2737. MASTERCARD: {
  2738. length: [16],
  2739. prefix: ['51', '52', '53', '54', '55']
  2740. },
  2741. SOLO: {
  2742. length: [16, 18, 19],
  2743. prefix: ['6334', '6767']
  2744. },
  2745. UNIONPAY: {
  2746. length: [16, 17, 18, 19],
  2747. prefix: ['622126', '622127', '622128', '622129', '62213', '62214',
  2748. '62215', '62216', '62217', '62218', '62219', '6222', '6223',
  2749. '6224', '6225', '6226', '6227', '6228', '62290', '62291',
  2750. '622920', '622921', '622922', '622923', '622924', '622925']
  2751. },
  2752. VISA_ELECTRON: {
  2753. length: [16],
  2754. prefix: ['4026', '417500', '4405', '4508', '4844', '4913', '4917']
  2755. },
  2756. VISA: {
  2757. length: [16],
  2758. prefix: ['4']
  2759. }
  2760. };
  2761. var type, i;
  2762. for (type in cards) {
  2763. for (i in cards[type].prefix) {
  2764. if (value.substr(0, cards[type].prefix[i].length) === cards[type].prefix[i] // Check the prefix
  2765. && $.inArray(value.length, cards[type].length) !== -1) // and length
  2766. {
  2767. return {
  2768. valid: true,
  2769. type: type
  2770. };
  2771. }
  2772. }
  2773. }
  2774. return false;
  2775. }
  2776. };
  2777. }(jQuery));
  2778. ;(function($) {
  2779. FormValidation.I18n = $.extend(true, FormValidation.I18n || {}, {
  2780. 'en_US': {
  2781. date: {
  2782. 'default': 'Please enter a valid date',
  2783. min: 'Please enter a date after %s',
  2784. max: 'Please enter a date before %s',
  2785. range: 'Please enter a date in the range %s - %s'
  2786. }
  2787. }
  2788. });
  2789. FormValidation.Validator.date = {
  2790. html5Attributes: {
  2791. message: 'message',
  2792. format: 'format',
  2793. min: 'min',
  2794. max: 'max',
  2795. separator: 'separator'
  2796. },
  2797. /**
  2798. * Return true if the input value is valid date
  2799. *
  2800. * @param {FormValidation.Base} validator The validator plugin instance
  2801. * @param {jQuery} $field Field element
  2802. * @param {Object} options Can consist of the following keys:
  2803. * - message: The invalid message
  2804. * - min: the minimum date
  2805. * - max: the maximum date
  2806. * - separator: Use to separate the date, month, and year.
  2807. * By default, it is /
  2808. * - format: The date format. Default is MM/DD/YYYY
  2809. * The format can be:
  2810. *
  2811. * i) date: Consist of DD, MM, YYYY parts which are separated by the separator option
  2812. * ii) date and time:
  2813. * The time can consist of h, m, s parts which are separated by :
  2814. * ii) date, time and A (indicating AM or PM)
  2815. * @returns {Boolean|Object}
  2816. */
  2817. validate: function(validator, $field, options, validatorName) {
  2818. var value = validator.getFieldValue($field, validatorName);
  2819. if (value === '') {
  2820. return true;
  2821. }
  2822. options.format = options.format || 'MM/DD/YYYY';
  2823. // #683: Force the format to YYYY-MM-DD as the default browser behaviour when using type="date" attribute
  2824. if ($field.attr('type') === 'date') {
  2825. options.format = 'YYYY-MM-DD';
  2826. }
  2827. var locale = validator.getLocale(),
  2828. message = options.message || FormValidation.I18n[locale].date['default'],
  2829. formats = options.format.split(' '),
  2830. dateFormat = formats[0],
  2831. timeFormat = (formats.length > 1) ? formats[1] : null,
  2832. amOrPm = (formats.length > 2) ? formats[2] : null,
  2833. sections = value.split(' '),
  2834. date = sections[0],
  2835. time = (sections.length > 1) ? sections[1] : null;
  2836. if (formats.length !== sections.length) {
  2837. return {
  2838. valid: false,
  2839. message: message
  2840. };
  2841. }
  2842. // Determine the separator
  2843. var separator = options.separator;
  2844. if (!separator) {
  2845. separator = (date.indexOf('/') !== -1)
  2846. ? '/'
  2847. : ((date.indexOf('-') !== -1) ? '-' : ((date.indexOf('.') !== -1) ? '.' : null));
  2848. }
  2849. if (separator === null || date.indexOf(separator) === -1) {
  2850. return {
  2851. valid: false,
  2852. message: message
  2853. };
  2854. }
  2855. // Determine the date
  2856. date = date.split(separator);
  2857. dateFormat = dateFormat.split(separator);
  2858. if (date.length !== dateFormat.length) {
  2859. return {
  2860. valid: false,
  2861. message: message
  2862. };
  2863. }
  2864. var year = date[$.inArray('YYYY', dateFormat)],
  2865. month = date[$.inArray('MM', dateFormat)],
  2866. day = date[$.inArray('DD', dateFormat)];
  2867. if (!year || !month || !day || year.length !== 4) {
  2868. return {
  2869. valid: false,
  2870. message: message
  2871. };
  2872. }
  2873. // Determine the time
  2874. var minutes = null, hours = null, seconds = null;
  2875. if (timeFormat) {
  2876. timeFormat = timeFormat.split(':');
  2877. time = time.split(':');
  2878. if (timeFormat.length !== time.length) {
  2879. return {
  2880. valid: false,
  2881. message: message
  2882. };
  2883. }
  2884. hours = time.length > 0 ? time[0] : null;
  2885. minutes = time.length > 1 ? time[1] : null;
  2886. seconds = time.length > 2 ? time[2] : null;
  2887. if (hours === '' || minutes === '' || seconds === '') {
  2888. return {
  2889. valid: false,
  2890. message: message
  2891. };
  2892. }
  2893. // Validate seconds
  2894. if (seconds) {
  2895. if (isNaN(seconds) || seconds.length > 2) {
  2896. return {
  2897. valid: false,
  2898. message: message
  2899. };
  2900. }
  2901. seconds = parseInt(seconds, 10);
  2902. if (seconds < 0 || seconds > 60) {
  2903. return {
  2904. valid: false,
  2905. message: message
  2906. };
  2907. }
  2908. }
  2909. // Validate hours
  2910. if (hours) {
  2911. if (isNaN(hours) || hours.length > 2) {
  2912. return {
  2913. valid: false,
  2914. message: message
  2915. };
  2916. }
  2917. hours = parseInt(hours, 10);
  2918. if (hours < 0 || hours >= 24 || (amOrPm && hours > 12)) {
  2919. return {
  2920. valid: false,
  2921. message: message
  2922. };
  2923. }
  2924. }
  2925. // Validate minutes
  2926. if (minutes) {
  2927. if (isNaN(minutes) || minutes.length > 2) {
  2928. return {
  2929. valid: false,
  2930. message: message
  2931. };
  2932. }
  2933. minutes = parseInt(minutes, 10);
  2934. if (minutes < 0 || minutes > 59) {
  2935. return {
  2936. valid: false,
  2937. message: message
  2938. };
  2939. }
  2940. }
  2941. }
  2942. // Validate day, month, and year
  2943. var valid = FormValidation.Helper.date(year, month, day),
  2944. min = null,
  2945. max = null,
  2946. minOption = options.min,
  2947. maxOption = options.max;
  2948. if (minOption) {
  2949. min = (minOption instanceof Date)
  2950. ? minOption
  2951. : (this._parseDate(minOption, dateFormat, separator) ||
  2952. this._parseDate(validator.getDynamicOption($field, minOption), dateFormat, separator));
  2953. // In order to avoid displaying a date string like "Mon Dec 08 2014 19:14:12 GMT+0000 (WET)"
  2954. minOption = this._formatDate(min, options.format);
  2955. }
  2956. if (maxOption) {
  2957. max = (maxOption instanceof Date)
  2958. ? maxOption
  2959. : (this._parseDate(maxOption, dateFormat, separator) ||
  2960. this._parseDate(validator.getDynamicOption($field, maxOption), dateFormat, separator));
  2961. // In order to avoid displaying a date string like "Mon Dec 08 2014 19:14:12 GMT+0000 (WET)"
  2962. maxOption = this._formatDate(max, options.format);
  2963. }
  2964. date = new Date(year, month -1, day, hours, minutes, seconds);
  2965. switch (true) {
  2966. case (minOption && !maxOption && valid):
  2967. valid = date.getTime() >= min.getTime();
  2968. message = options.message || FormValidation.Helper.format(FormValidation.I18n[locale].date.min, minOption);
  2969. break;
  2970. case (maxOption && !minOption && valid):
  2971. valid = date.getTime() <= max.getTime();
  2972. message = options.message || FormValidation.Helper.format(FormValidation.I18n[locale].date.max, maxOption);
  2973. break;
  2974. case (maxOption && minOption && valid):
  2975. valid = date.getTime() <= max.getTime() && date.getTime() >= min.getTime();
  2976. message = options.message || FormValidation.Helper.format(FormValidation.I18n[locale].date.range, [minOption, maxOption]);
  2977. break;
  2978. default:
  2979. break;
  2980. }
  2981. return {
  2982. valid: valid,
  2983. date: date,
  2984. message: message
  2985. };
  2986. },
  2987. /**
  2988. * Return a date object after parsing the date string
  2989. *
  2990. * @param {Date|String} date The date string to parse
  2991. * @param {String} format The date format
  2992. * The format can be:
  2993. * - date: Consist of DD, MM, YYYY parts which are separated by the separator option
  2994. * - date and time:
  2995. * The time can consist of h, m, s parts which are separated by :
  2996. * @param {String} separator The separator used to separate the date, month, and year
  2997. * @returns {Date}
  2998. */
  2999. _parseDate: function(date, format, separator) {
  3000. if (date instanceof Date) {
  3001. return date;
  3002. }
  3003. if (typeof date !== 'string') {
  3004. return null;
  3005. }
  3006. // Ensure that the format must consist of year, month and day patterns
  3007. var yearIndex = $.inArray('YYYY', format),
  3008. monthIndex = $.inArray('MM', format),
  3009. dayIndex = $.inArray('DD', format);
  3010. if (yearIndex === -1 || monthIndex === -1 || dayIndex === -1) {
  3011. return null;
  3012. }
  3013. var minutes = 0, hours = 0, seconds = 0,
  3014. sections = date.split(' '),
  3015. dateSection = sections[0].split(separator);
  3016. if (dateSection.length < 3) {
  3017. return null;
  3018. }
  3019. if (sections.length > 1) {
  3020. var timeSection = sections[1].split(':');
  3021. hours = timeSection.length > 0 ? timeSection[0] : null;
  3022. minutes = timeSection.length > 1 ? timeSection[1] : null;
  3023. seconds = timeSection.length > 2 ? timeSection[2] : null;
  3024. }
  3025. return new Date(dateSection[yearIndex], dateSection[monthIndex] - 1, dateSection[dayIndex],
  3026. hours, minutes, seconds);
  3027. },
  3028. /**
  3029. * Format date
  3030. *
  3031. * @param {Date} date The date object to format
  3032. * @param {String} format The date format
  3033. * The format can consist of the following tokens:
  3034. * d Day of the month without leading zeros (1 through 31)
  3035. * dd Day of the month with leading zeros (01 through 31)
  3036. * m Month without leading zeros (1 through 12)
  3037. * mm Month with leading zeros (01 through 12)
  3038. * yy Last two digits of year (for example: 14)
  3039. * yyyy Full four digits of year (for example: 2014)
  3040. * h Hours without leading zeros (1 through 12)
  3041. * hh Hours with leading zeros (01 through 12)
  3042. * H Hours without leading zeros (0 through 23)
  3043. * HH Hours with leading zeros (00 through 23)
  3044. * M Minutes without leading zeros (0 through 59)
  3045. * MM Minutes with leading zeros (00 through 59)
  3046. * s Seconds without leading zeros (0 through 59)
  3047. * ss Seconds with leading zeros (00 through 59)
  3048. * @returns {String}
  3049. */
  3050. _formatDate: function(date, format) {
  3051. format = format
  3052. .replace(/Y/g, 'y')
  3053. .replace(/M/g, 'm')
  3054. .replace(/D/g, 'd')
  3055. .replace(/:m/g, ':M')
  3056. .replace(/:mm/g, ':MM')
  3057. .replace(/:S/, ':s')
  3058. .replace(/:SS/, ':ss');
  3059. var replacer = {
  3060. d: function(date) {
  3061. return date.getDate();
  3062. },
  3063. dd: function(date) {
  3064. var d = date.getDate();
  3065. return (d < 10) ? '0' + d : d;
  3066. },
  3067. m: function(date) {
  3068. return date.getMonth() + 1;
  3069. },
  3070. mm: function(date) {
  3071. var m = date.getMonth() + 1;
  3072. return m < 10 ? '0' + m : m;
  3073. },
  3074. yy: function(date) {
  3075. return ('' + date.getFullYear()).substr(2);
  3076. },
  3077. yyyy: function(date) {
  3078. return date.getFullYear();
  3079. },
  3080. h: function(date) {
  3081. return date.getHours() % 12 || 12;
  3082. },
  3083. hh: function(date) {
  3084. var h = date.getHours() % 12 || 12;
  3085. return h < 10 ? '0' + h : h;
  3086. },
  3087. H: function(date) {
  3088. return date.getHours();
  3089. },
  3090. HH: function(date) {
  3091. var H = date.getHours();
  3092. return H < 10 ? '0' + H : H;
  3093. },
  3094. M: function(date) {
  3095. return date.getMinutes();
  3096. },
  3097. MM: function(date) {
  3098. var M = date.getMinutes();
  3099. return M < 10 ? '0' + M : M;
  3100. },
  3101. s: function(date) {
  3102. return date.getSeconds();
  3103. },
  3104. ss: function(date) {
  3105. var s = date.getSeconds();
  3106. return s < 10 ? '0' + s : s;
  3107. }
  3108. };
  3109. return format.replace(/d{1,4}|m{1,4}|yy(?:yy)?|([HhMs])\1?|"[^"]*"|'[^']*'/g, function(match) {
  3110. return replacer[match] ? replacer[match](date) : match.slice(1, match.length - 1);
  3111. });
  3112. }
  3113. };
  3114. }(jQuery));
  3115. ;(function($) {
  3116. FormValidation.I18n = $.extend(true, FormValidation.I18n || {}, {
  3117. 'en_US': {
  3118. different: {
  3119. 'default': 'Please enter a different value'
  3120. }
  3121. }
  3122. });
  3123. FormValidation.Validator.different = {
  3124. html5Attributes: {
  3125. message: 'message',
  3126. field: 'field'
  3127. },
  3128. /**
  3129. * Bind the validator on the live change of the field to compare with current one
  3130. *
  3131. * @param {FormValidation.Base} validator The validator plugin instance
  3132. * @param {jQuery} $field Field element
  3133. * @param {Object} options Consists of the following key:
  3134. * - field: The name of field that will be used to compare with current one
  3135. */
  3136. init: function(validator, $field, options, validatorName) {
  3137. var fields = options.field.split(',');
  3138. for (var i = 0; i < fields.length; i++) {
  3139. var compareWith = validator.getFieldElements($.trim(fields[i]));
  3140. validator.onLiveChange(compareWith, 'live_' + validatorName, function() {
  3141. var status = validator.getStatus($field, validatorName);
  3142. if (status !== validator.STATUS_NOT_VALIDATED) {
  3143. validator.revalidateField($field);
  3144. }
  3145. });
  3146. }
  3147. },
  3148. /**
  3149. * Unbind the validator on the live change of the field to compare with current one
  3150. *
  3151. * @param {FormValidation.Base} validator The validator plugin instance
  3152. * @param {jQuery} $field Field element
  3153. * @param {Object} options Consists of the following key:
  3154. * - field: The name of field that will be used to compare with current one
  3155. */
  3156. destroy: function(validator, $field, options, validatorName) {
  3157. var fields = options.field.split(',');
  3158. for (var i = 0; i < fields.length; i++) {
  3159. var compareWith = validator.getFieldElements($.trim(fields[i]));
  3160. validator.offLiveChange(compareWith, 'live_' + validatorName);
  3161. }
  3162. },
  3163. /**
  3164. * Return true if the input value is different with given field's value
  3165. *
  3166. * @param {FormValidation.Base} validator The validator plugin instance
  3167. * @param {jQuery} $field Field element
  3168. * @param {Object} options Consists of the following key:
  3169. * - field: The name of field that will be used to compare with current one
  3170. * - message: The invalid message
  3171. * @returns {Boolean}
  3172. */
  3173. validate: function(validator, $field, options, validatorName) {
  3174. var value = validator.getFieldValue($field, validatorName);
  3175. if (value === '') {
  3176. return true;
  3177. }
  3178. var fields = options.field.split(','),
  3179. isValid = true;
  3180. for (var i = 0; i < fields.length; i++) {
  3181. var compareWith = validator.getFieldElements($.trim(fields[i]));
  3182. if (compareWith == null || compareWith.length === 0) {
  3183. continue;
  3184. }
  3185. var compareValue = validator.getFieldValue(compareWith, validatorName);
  3186. if (value === compareValue) {
  3187. isValid = false;
  3188. } else if (compareValue !== '') {
  3189. validator.updateStatus(compareWith, validator.STATUS_VALID, validatorName);
  3190. }
  3191. }
  3192. return isValid;
  3193. }
  3194. };
  3195. }(jQuery));
  3196. ;(function($) {
  3197. FormValidation.I18n = $.extend(true, FormValidation.I18n || {}, {
  3198. 'en_US': {
  3199. digits: {
  3200. 'default': 'Please enter only digits'
  3201. }
  3202. }
  3203. });
  3204. FormValidation.Validator.digits = {
  3205. /**
  3206. * Return true if the input value contains digits only
  3207. *
  3208. * @param {FormValidation.Base} validator Validate plugin instance
  3209. * @param {jQuery} $field Field element
  3210. * @param {Object} [options]
  3211. * @returns {Boolean}
  3212. */
  3213. validate: function(validator, $field, options, validatorName) {
  3214. var value = validator.getFieldValue($field, validatorName);
  3215. if (value === '') {
  3216. return true;
  3217. }
  3218. return /^\d+$/.test(value);
  3219. }
  3220. };
  3221. }(jQuery));
  3222. ;(function($) {
  3223. FormValidation.I18n = $.extend(true, FormValidation.I18n || {}, {
  3224. 'en_US': {
  3225. emailAddress: {
  3226. 'default': 'Please enter a valid email address'
  3227. }
  3228. }
  3229. });
  3230. FormValidation.Validator.emailAddress = {
  3231. html5Attributes: {
  3232. message: 'message',
  3233. multiple: 'multiple',
  3234. separator: 'separator'
  3235. },
  3236. enableByHtml5: function($field) {
  3237. return ('email' === $field.attr('type'));
  3238. },
  3239. /**
  3240. * Return true if and only if the input value is a valid email address
  3241. *
  3242. * @param {FormValidation.Base} validator Validate plugin instance
  3243. * @param {jQuery} $field Field element
  3244. * @param {Object} [options]
  3245. * - multiple: Allow multiple email addresses, separated by a comma or semicolon; default is false.
  3246. * - separator: Regex for character or characters expected as separator between addresses; default is comma /[,;]/, i.e. comma or semicolon.
  3247. * @returns {Boolean}
  3248. */
  3249. validate: function(validator, $field, options, validatorName) {
  3250. var value = validator.getFieldValue($field, validatorName);
  3251. if (value === '') {
  3252. return true;
  3253. }
  3254. // Email address regular expression
  3255. // http://stackoverflow.com/questions/46155/validate-email-address-in-javascript
  3256. var emailRegExp = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/,
  3257. allowMultiple = options.multiple === true || options.multiple === 'true';
  3258. if (allowMultiple) {
  3259. var separator = options.separator || /[,;]/,
  3260. addresses = this._splitEmailAddresses(value, separator);
  3261. for (var i = 0; i < addresses.length; i++) {
  3262. if (!emailRegExp.test(addresses[i])) {
  3263. return false;
  3264. }
  3265. }
  3266. return true;
  3267. } else {
  3268. return emailRegExp.test(value);
  3269. }
  3270. },
  3271. _splitEmailAddresses: function(emailAddresses, separator) {
  3272. var quotedFragments = emailAddresses.split(/"/),
  3273. quotedFragmentCount = quotedFragments.length,
  3274. emailAddressArray = [],
  3275. nextEmailAddress = '';
  3276. for (var i = 0; i < quotedFragmentCount; i++) {
  3277. if (i % 2 === 0) {
  3278. var splitEmailAddressFragments = quotedFragments[i].split(separator),
  3279. splitEmailAddressFragmentCount = splitEmailAddressFragments.length;
  3280. if (splitEmailAddressFragmentCount === 1) {
  3281. nextEmailAddress += splitEmailAddressFragments[0];
  3282. } else {
  3283. emailAddressArray.push(nextEmailAddress + splitEmailAddressFragments[0]);
  3284. for (var j = 1; j < splitEmailAddressFragmentCount - 1; j++) {
  3285. emailAddressArray.push(splitEmailAddressFragments[j]);
  3286. }
  3287. nextEmailAddress = splitEmailAddressFragments[splitEmailAddressFragmentCount - 1];
  3288. }
  3289. } else {
  3290. nextEmailAddress += '"' + quotedFragments[i];
  3291. if (i < quotedFragmentCount - 1) {
  3292. nextEmailAddress += '"';
  3293. }
  3294. }
  3295. }
  3296. emailAddressArray.push(nextEmailAddress);
  3297. return emailAddressArray;
  3298. }
  3299. };
  3300. }(jQuery));
  3301. ;(function($) {
  3302. FormValidation.I18n = $.extend(true, FormValidation.I18n || {}, {
  3303. 'en_US': {
  3304. file: {
  3305. 'default': 'Please choose a valid file'
  3306. }
  3307. }
  3308. });
  3309. FormValidation.Validator.file = {
  3310. Error: {
  3311. EXTENSION: 'EXTENSION',
  3312. MAX_FILES: 'MAX_FILES',
  3313. MAX_SIZE: 'MAX_SIZE',
  3314. MAX_TOTAL_SIZE: 'MAX_TOTAL_SIZE',
  3315. MIN_FILES: 'MIN_FILES',
  3316. MIN_SIZE: 'MIN_SIZE',
  3317. MIN_TOTAL_SIZE: 'MIN_TOTAL_SIZE',
  3318. TYPE: 'TYPE'
  3319. },
  3320. html5Attributes: {
  3321. extension: 'extension',
  3322. maxfiles: 'maxFiles',
  3323. minfiles: 'minFiles',
  3324. maxsize: 'maxSize',
  3325. minsize: 'minSize',
  3326. maxtotalsize: 'maxTotalSize',
  3327. mintotalsize: 'minTotalSize',
  3328. message: 'message',
  3329. type: 'type'
  3330. },
  3331. /**
  3332. * Validate upload file. Use HTML 5 API if the browser supports
  3333. *
  3334. * @param {FormValidation.Base} validator The validator plugin instance
  3335. * @param {jQuery} $field Field element
  3336. * @param {Object} options Can consist of the following keys:
  3337. * - extension: The allowed extensions, separated by a comma
  3338. * - maxFiles: The maximum number of files
  3339. * - minFiles: The minimum number of files
  3340. * - maxSize: The maximum size in bytes
  3341. * - minSize: The minimum size in bytes
  3342. * - maxTotalSize: The maximum size in bytes for all files
  3343. * - minTotalSize: The minimum size in bytes for all files
  3344. * - message: The invalid message
  3345. * - type: The allowed MIME type, separated by a comma
  3346. * @returns {Boolean|Object}
  3347. */
  3348. validate: function(validator, $field, options, validatorName) {
  3349. var value = validator.getFieldValue($field, validatorName);
  3350. if (value === '') {
  3351. return true;
  3352. }
  3353. var ext,
  3354. extensions = options.extension ? options.extension.toLowerCase().split(',') : null,
  3355. types = options.type ? options.type.toLowerCase().split(',') : null,
  3356. html5 = (window.File && window.FileList && window.FileReader);
  3357. if (html5) {
  3358. // Get FileList instance
  3359. var files = $field.get(0).files,
  3360. total = files.length,
  3361. totalSize = 0;
  3362. // Check the maxFiles
  3363. if (options.maxFiles && total > parseInt(options.maxFiles, 10)) {
  3364. return {
  3365. valid: false,
  3366. error: this.Error.MAX_FILES
  3367. };
  3368. }
  3369. // Check the minFiles
  3370. if (options.minFiles && total < parseInt(options.minFiles, 10)) {
  3371. return {
  3372. valid: false,
  3373. error: this.Error.MIN_FILES
  3374. };
  3375. }
  3376. var metaData = {};
  3377. for (var i = 0; i < total; i++) {
  3378. totalSize += files[i].size;
  3379. ext = files[i].name.substr(files[i].name.lastIndexOf('.') + 1);
  3380. metaData = {
  3381. file: files[i],
  3382. size: files[i].size,
  3383. ext: ext,
  3384. type: files[i].type
  3385. };
  3386. // Check the minSize
  3387. if (options.minSize && files[i].size < parseInt(options.minSize, 10)) {
  3388. return {
  3389. valid: false,
  3390. error: this.Error.MIN_SIZE,
  3391. metaData: metaData
  3392. };
  3393. }
  3394. // Check the maxSize
  3395. if (options.maxSize && files[i].size > parseInt(options.maxSize, 10)) {
  3396. return {
  3397. valid: false,
  3398. error: this.Error.MAX_SIZE,
  3399. metaData: metaData
  3400. };
  3401. }
  3402. // Check file extension
  3403. if (extensions && $.inArray(ext.toLowerCase(), extensions) === -1) {
  3404. return {
  3405. valid: false,
  3406. error: this.Error.EXTENSION,
  3407. metaData: metaData
  3408. };
  3409. }
  3410. // Check file type
  3411. if (files[i].type && types && $.inArray(files[i].type.toLowerCase(), types) === -1) {
  3412. return {
  3413. valid: false,
  3414. error: this.Error.TYPE,
  3415. metaData: metaData
  3416. };
  3417. }
  3418. }
  3419. // Check the maxTotalSize
  3420. if (options.maxTotalSize && totalSize > parseInt(options.maxTotalSize, 10)) {
  3421. return {
  3422. valid: false,
  3423. error: this.Error.MAX_TOTAL_SIZE,
  3424. metaData: {
  3425. totalSize: totalSize
  3426. }
  3427. };
  3428. }
  3429. // Check the minTotalSize
  3430. if (options.minTotalSize && totalSize < parseInt(options.minTotalSize, 10)) {
  3431. return {
  3432. valid: false,
  3433. error: this.Error.MIN_TOTAL_SIZE,
  3434. metaData: {
  3435. totalSize: totalSize
  3436. }
  3437. };
  3438. }
  3439. } else {
  3440. // Check file extension
  3441. ext = value.substr(value.lastIndexOf('.') + 1);
  3442. if (extensions && $.inArray(ext.toLowerCase(), extensions) === -1) {
  3443. return {
  3444. valid: false,
  3445. error: this.Error.EXTENSION,
  3446. metaData: {
  3447. ext: ext
  3448. }
  3449. };
  3450. }
  3451. }
  3452. return true;
  3453. }
  3454. };
  3455. }(jQuery));
  3456. ;(function($) {
  3457. FormValidation.I18n = $.extend(true, FormValidation.I18n || {}, {
  3458. 'en_US': {
  3459. greaterThan: {
  3460. 'default': 'Please enter a value greater than or equal to %s',
  3461. notInclusive: 'Please enter a value greater than %s'
  3462. }
  3463. }
  3464. });
  3465. FormValidation.Validator.greaterThan = {
  3466. html5Attributes: {
  3467. message: 'message',
  3468. value: 'value',
  3469. inclusive: 'inclusive'
  3470. },
  3471. enableByHtml5: function($field) {
  3472. var type = $field.attr('type'),
  3473. min = $field.attr('min');
  3474. if (min && type !== 'date') {
  3475. return {
  3476. value: min
  3477. };
  3478. }
  3479. return false;
  3480. },
  3481. /**
  3482. * Return true if the input value is greater than or equals to given number
  3483. *
  3484. * @param {FormValidation.Base} validator Validate plugin instance
  3485. * @param {jQuery} $field Field element
  3486. * @param {Object} options Can consist of the following keys:
  3487. * - value: Define the number to compare with. It can be
  3488. * - A number
  3489. * - Name of field which its value defines the number
  3490. * - Name of callback function that returns the number
  3491. * - A callback function that returns the number
  3492. *
  3493. * - inclusive [optional]: Can be true or false. Default is true
  3494. * - message: The invalid message
  3495. * @returns {Boolean|Object}
  3496. */
  3497. validate: function(validator, $field, options, validatorName) {
  3498. var value = validator.getFieldValue($field, validatorName);
  3499. if (value === '') {
  3500. return true;
  3501. }
  3502. value = this._format(value);
  3503. var locale = validator.getLocale(),
  3504. compareTo = $.isNumeric(options.value) ? options.value : validator.getDynamicOption($field, options.value),
  3505. compareToValue = this._format(compareTo);
  3506. return (options.inclusive === true || options.inclusive === undefined)
  3507. ? {
  3508. valid: $.isNumeric(value) && parseFloat(value) >= compareToValue,
  3509. message: FormValidation.Helper.format(options.message || FormValidation.I18n[locale].greaterThan['default'], compareTo)
  3510. }
  3511. : {
  3512. valid: $.isNumeric(value) && parseFloat(value) > compareToValue,
  3513. message: FormValidation.Helper.format(options.message || FormValidation.I18n[locale].greaterThan.notInclusive, compareTo)
  3514. };
  3515. },
  3516. _format: function(value) {
  3517. return (value + '').replace(',', '.');
  3518. }
  3519. };
  3520. }(jQuery));
  3521. ;(function($) {
  3522. FormValidation.I18n = $.extend(true, FormValidation.I18n || {}, {
  3523. 'en_US': {
  3524. identical: {
  3525. 'default': 'Please enter the same value'
  3526. }
  3527. }
  3528. });
  3529. FormValidation.Validator.identical = {
  3530. html5Attributes: {
  3531. message: 'message',
  3532. field: 'field'
  3533. },
  3534. /**
  3535. * Bind the validator on the live change of the field to compare with current one
  3536. *
  3537. * @param {FormValidation.Base} validator The validator plugin instance
  3538. * @param {jQuery} $field Field element
  3539. * @param {Object} options Consists of the following key:
  3540. * - field: The name of field that will be used to compare with current one
  3541. */
  3542. init: function(validator, $field, options, validatorName) {
  3543. var compareWith = validator.getFieldElements(options.field);
  3544. validator.onLiveChange(compareWith, 'live_' + validatorName, function() {
  3545. var status = validator.getStatus($field, validatorName);
  3546. if (status !== validator.STATUS_NOT_VALIDATED) {
  3547. validator.revalidateField($field);
  3548. }
  3549. });
  3550. },
  3551. /**
  3552. * Unbind the validator on the live change of the field to compare with current one
  3553. *
  3554. * @param {FormValidation.Base} validator The validator plugin instance
  3555. * @param {jQuery} $field Field element
  3556. * @param {Object} options Consists of the following key:
  3557. * - field: The name of field that will be used to compare with current one
  3558. */
  3559. destroy: function(validator, $field, options, validatorName) {
  3560. var compareWith = validator.getFieldElements(options.field);
  3561. validator.offLiveChange(compareWith, 'live_' + validatorName);
  3562. },
  3563. /**
  3564. * Check if input value equals to value of particular one
  3565. *
  3566. * @param {FormValidation.Base} validator The validator plugin instance
  3567. * @param {jQuery} $field Field element
  3568. * @param {Object} options Consists of the following key:
  3569. * - field: The name of field that will be used to compare with current one
  3570. * @returns {Boolean}
  3571. */
  3572. validate: function(validator, $field, options, validatorName) {
  3573. var value = validator.getFieldValue($field, validatorName),
  3574. compareWith = validator.getFieldElements(options.field);
  3575. if (compareWith === null || compareWith.length === 0) {
  3576. return true;
  3577. }
  3578. var compareValue = validator.getFieldValue(compareWith, validatorName);
  3579. if (value === compareValue) {
  3580. validator.updateStatus(compareWith, validator.STATUS_VALID, validatorName);
  3581. return true;
  3582. }
  3583. return false;
  3584. }
  3585. };
  3586. }(jQuery));
  3587. ;(function($) {
  3588. FormValidation.I18n = $.extend(true, FormValidation.I18n || {}, {
  3589. 'en_US': {
  3590. integer: {
  3591. 'default': 'Please enter a valid number'
  3592. }
  3593. }
  3594. });
  3595. FormValidation.Validator.integer = {
  3596. html5Attributes: {
  3597. message: 'message',
  3598. thousandsseparator: 'thousandsSeparator',
  3599. decimalseparator: 'decimalSeparator'
  3600. },
  3601. enableByHtml5: function($field) {
  3602. return ('number' === $field.attr('type')) && ($field.attr('step') === undefined || $field.attr('step') % 1 === 0);
  3603. },
  3604. /**
  3605. * Return true if the input value is an integer
  3606. *
  3607. * @param {FormValidation.Base} validator The validator plugin instance
  3608. * @param {jQuery} $field Field element
  3609. * @param {Object} options Can consist of the following key:
  3610. * - message: The invalid message
  3611. * - thousandsSeparator: The thousands separator. It's empty by default
  3612. * - decimalSeparator: The decimal separator. It's '.' by default
  3613. * @returns {Boolean}
  3614. */
  3615. validate: function(validator, $field, options, validatorName) {
  3616. if (this.enableByHtml5($field) && $field.get(0).validity && $field.get(0).validity.badInput === true) {
  3617. return false;
  3618. }
  3619. var value = validator.getFieldValue($field, validatorName);
  3620. if (value === '') {
  3621. return true;
  3622. }
  3623. var decimalSeparator = options.decimalSeparator || '.',
  3624. thousandsSeparator = options.thousandsSeparator || '';
  3625. decimalSeparator = (decimalSeparator === '.') ? '\\.' : decimalSeparator;
  3626. thousandsSeparator = (thousandsSeparator === '.') ? '\\.' : thousandsSeparator;
  3627. var testRegexp = new RegExp('^-?[0-9]{1,3}(' + thousandsSeparator + '[0-9]{3})*(' + decimalSeparator + '[0-9]+)?$'),
  3628. thousandsReplacer = new RegExp(thousandsSeparator, 'g');
  3629. if (!testRegexp.test(value)) {
  3630. return false;
  3631. }
  3632. // Replace thousands separator with blank
  3633. if (thousandsSeparator) {
  3634. value = value.replace(thousandsReplacer, '');
  3635. }
  3636. // Replace decimal separator with a dot
  3637. if (decimalSeparator) {
  3638. value = value.replace(decimalSeparator, '.');
  3639. }
  3640. if (isNaN(value) || !isFinite(value)) {
  3641. return false;
  3642. }
  3643. // TODO: Use Number.isInteger() if available
  3644. value = parseFloat(value);
  3645. return Math.floor(value) === value;
  3646. }
  3647. };
  3648. }(jQuery));
  3649. ;(function($) {
  3650. FormValidation.I18n = $.extend(true, FormValidation.I18n || {}, {
  3651. 'en_US': {
  3652. lessThan: {
  3653. 'default': 'Please enter a value less than or equal to %s',
  3654. notInclusive: 'Please enter a value less than %s'
  3655. }
  3656. }
  3657. });
  3658. FormValidation.Validator.lessThan = {
  3659. html5Attributes: {
  3660. message: 'message',
  3661. value: 'value',
  3662. inclusive: 'inclusive'
  3663. },
  3664. enableByHtml5: function($field) {
  3665. var type = $field.attr('type'),
  3666. max = $field.attr('max');
  3667. if (max && type !== 'date') {
  3668. return {
  3669. value: max
  3670. };
  3671. }
  3672. return false;
  3673. },
  3674. /**
  3675. * Return true if the input value is less than or equal to given number
  3676. *
  3677. * @param {FormValidation.Base} validator The validator plugin instance
  3678. * @param {jQuery} $field Field element
  3679. * @param {Object} options Can consist of the following keys:
  3680. * - value: The number used to compare to. It can be
  3681. * - A number
  3682. * - Name of field which its value defines the number
  3683. * - Name of callback function that returns the number
  3684. * - A callback function that returns the number
  3685. *
  3686. * - inclusive [optional]: Can be true or false. Default is true
  3687. * - message: The invalid message
  3688. * @returns {Boolean|Object}
  3689. */
  3690. validate: function(validator, $field, options, validatorName) {
  3691. var value = validator.getFieldValue($field, validatorName);
  3692. if (value === '') {
  3693. return true;
  3694. }
  3695. value = this._format(value);
  3696. var locale = validator.getLocale(),
  3697. compareTo = $.isNumeric(options.value) ? options.value : validator.getDynamicOption($field, options.value),
  3698. compareToValue = this._format(compareTo);
  3699. return (options.inclusive === true || options.inclusive === undefined)
  3700. ? {
  3701. valid: $.isNumeric(value) && parseFloat(value) <= compareToValue,
  3702. message: FormValidation.Helper.format(options.message || FormValidation.I18n[locale].lessThan['default'], compareTo)
  3703. }
  3704. : {
  3705. valid: $.isNumeric(value) && parseFloat(value) < compareToValue,
  3706. message: FormValidation.Helper.format(options.message || FormValidation.I18n[locale].lessThan.notInclusive, compareTo)
  3707. };
  3708. },
  3709. _format: function(value) {
  3710. return (value + '').replace(',', '.');
  3711. }
  3712. };
  3713. }(jQuery));
  3714. ;(function($) {
  3715. FormValidation.I18n = $.extend(true, FormValidation.I18n || {}, {
  3716. 'en_US': {
  3717. notEmpty: {
  3718. 'default': 'Please enter a value'
  3719. }
  3720. }
  3721. });
  3722. FormValidation.Validator.notEmpty = {
  3723. enableByHtml5: function($field) {
  3724. var required = $field.attr('required') + '';
  3725. return ('required' === required || 'true' === required);
  3726. },
  3727. /**
  3728. * Check if input value is empty or not
  3729. *
  3730. * @param {FormValidation.Base} validator The validator plugin instance
  3731. * @param {jQuery} $field Field element
  3732. * @param {Object} options
  3733. * @returns {Boolean}
  3734. */
  3735. validate: function(validator, $field, options, validatorName) {
  3736. var type = $field.attr('type');
  3737. if ('radio' === type || 'checkbox' === type) {
  3738. var ns = validator.getNamespace();
  3739. return validator
  3740. .getFieldElements($field.attr('data-' + ns + '-field'))
  3741. .filter(':checked')
  3742. .length > 0;
  3743. }
  3744. if ('number' === type && $field.get(0).validity && $field.get(0).validity.badInput === true) {
  3745. return true;
  3746. }
  3747. var value = validator.getFieldValue($field, validatorName);
  3748. return $.trim(value) !== '';
  3749. }
  3750. };
  3751. }(jQuery));
  3752. ;(function($) {
  3753. FormValidation.I18n = $.extend(true, FormValidation.I18n || {}, {
  3754. 'en_US': {
  3755. numeric: {
  3756. 'default': 'Please enter a valid float number'
  3757. }
  3758. }
  3759. });
  3760. FormValidation.Validator.numeric = {
  3761. html5Attributes: {
  3762. message: 'message',
  3763. separator: 'separator', // deprecated
  3764. thousandsseparator: 'thousandsSeparator',
  3765. decimalseparator: 'decimalSeparator'
  3766. },
  3767. enableByHtml5: function($field) {
  3768. return ('number' === $field.attr('type')) && ($field.attr('step') !== undefined) && ($field.attr('step') % 1 !== 0);
  3769. },
  3770. /**
  3771. * Validate decimal number
  3772. *
  3773. * @param {FormValidation.Base} validator The validator plugin instance
  3774. * @param {jQuery} $field Field element
  3775. * @param {Object} options Consist of key:
  3776. * - message: The invalid message
  3777. * - thousandsSeparator: The thousands separator. It's empty by default
  3778. * - separator, decimalSeparator: The decimal separator. It's '.' by default
  3779. * The separator option is deprecated and should be replaced with decimalSeparator
  3780. * @returns {Boolean}
  3781. */
  3782. validate: function(validator, $field, options, validatorName) {
  3783. if (this.enableByHtml5($field) && $field.get(0).validity && $field.get(0).validity.badInput === true) {
  3784. return false;
  3785. }
  3786. var value = validator.getFieldValue($field, validatorName);
  3787. if (value === '') {
  3788. return true;
  3789. }
  3790. var decimalSeparator = options.separator || options.decimalSeparator || '.',
  3791. thousandsSeparator = options.thousandsSeparator || '';
  3792. // Support preceding zero numbers such as .5, -.5
  3793. if (value.substr(0, 1) === decimalSeparator) {
  3794. value = '0' + decimalSeparator + value.substr(1);
  3795. } else if (value.substr(0, 2) === '-' + decimalSeparator) {
  3796. value = '-0' + decimalSeparator + value.substr(2);
  3797. }
  3798. decimalSeparator = (decimalSeparator === '.') ? '\\.' : decimalSeparator;
  3799. thousandsSeparator = (thousandsSeparator === '.') ? '\\.' : thousandsSeparator;
  3800. var testRegexp = new RegExp('^-?[0-9]{1,3}(' + thousandsSeparator + '[0-9]{3})*(' + decimalSeparator + '[0-9]+)?$'),
  3801. thousandsReplacer = new RegExp(thousandsSeparator, 'g');
  3802. if (!testRegexp.test(value)) {
  3803. return false;
  3804. }
  3805. // Replace thousands separator with blank
  3806. if (thousandsSeparator) {
  3807. value = value.replace(thousandsReplacer, '');
  3808. }
  3809. // Replace decimal separator with a dot
  3810. if (decimalSeparator) {
  3811. value = value.replace(decimalSeparator, '.');
  3812. }
  3813. return !isNaN(parseFloat(value)) && isFinite(value);
  3814. }
  3815. };
  3816. }(jQuery));
  3817. ;(function($) {
  3818. FormValidation.I18n = $.extend(true, FormValidation.I18n || {}, {
  3819. 'en_US': {
  3820. promise: {
  3821. 'default': 'Please enter a valid value'
  3822. }
  3823. }
  3824. });
  3825. FormValidation.Validator.promise = {
  3826. priority: 999,
  3827. html5Attributes: {
  3828. message: 'message',
  3829. promise: 'promise'
  3830. },
  3831. /**
  3832. * Return result from a jQuery's Deferred object
  3833. *
  3834. * @param {FormValidation.Base} validator The validator plugin instance
  3835. * @param {jQuery} $field Field element
  3836. * @param {Object} options Can consist of the following keys:
  3837. * - promise: The method that passes parameters:
  3838. * promise: function(fieldValue, validator, $field) {
  3839. * // fieldValue is the value of field
  3840. * // validator is instance of FormValidation.Base
  3841. * // $field is the field element
  3842. *
  3843. * var dfd = new $.Deferred();
  3844. *
  3845. * // Do something ...
  3846. *
  3847. * // Resolve when particular task is done
  3848. * dfd.resolve({
  3849. * valid: true or false, // Required
  3850. * message: 'Other message', // Optional
  3851. * key: value // You can attach more data to reuse later
  3852. * });
  3853. *
  3854. * // You can reject if there's error
  3855. * dfd.reject({
  3856. * message: 'Other message', // Optional
  3857. * key: value // You can attach more data to reuse later
  3858. * });
  3859. *
  3860. * return dfd.promise();
  3861. * }
  3862. * - message: The invalid message
  3863. * @returns {Deferred}
  3864. */
  3865. validate: function(validator, $field, options, validatorName) {
  3866. var value = validator.getFieldValue($field, validatorName),
  3867. dfd = new $.Deferred(),
  3868. promise = FormValidation.Helper.call(options.promise, [value, validator, $field]);
  3869. promise
  3870. .done(function(result) {
  3871. dfd.resolve($field, validatorName, result);
  3872. })
  3873. .fail(function(result) {
  3874. result = result || {};
  3875. result.valid = false;
  3876. dfd.resolve($field, validatorName, result);
  3877. });
  3878. return dfd;
  3879. }
  3880. };
  3881. }(jQuery));
  3882. ;(function($) {
  3883. FormValidation.I18n = $.extend(true, FormValidation.I18n || {}, {
  3884. 'en_US': {
  3885. regexp: {
  3886. 'default': 'Please enter a value matching the pattern'
  3887. }
  3888. }
  3889. });
  3890. FormValidation.Validator.regexp = {
  3891. html5Attributes: {
  3892. message: 'message',
  3893. flags: 'flags',
  3894. regexp: 'regexp'
  3895. },
  3896. enableByHtml5: function($field) {
  3897. var pattern = $field.attr('pattern');
  3898. if (pattern) {
  3899. return {
  3900. regexp: pattern
  3901. };
  3902. }
  3903. return false;
  3904. },
  3905. /**
  3906. * Check if the element value matches given regular expression
  3907. *
  3908. * @param {FormValidation.Base} validator The validator plugin instance
  3909. * @param {jQuery} $field Field element
  3910. * @param {Object} options Consists of the following key:
  3911. * - regexp: The regular expression you need to check
  3912. * - flags: If specified, flags can have any combination of Javascript regular expression flags such as:
  3913. * g: global match
  3914. * i: ignore case
  3915. * m: multiple line
  3916. * @returns {Boolean}
  3917. */
  3918. validate: function(validator, $field, options, validatorName) {
  3919. var value = validator.getFieldValue($field, validatorName);
  3920. if (value === '') {
  3921. return true;
  3922. }
  3923. var regexp = ('string' === typeof options.regexp)
  3924. ? (options.flags ? new RegExp(options.regexp, options.flags) : new RegExp(options.regexp))
  3925. : options.regexp;
  3926. return regexp.test(value);
  3927. }
  3928. };
  3929. }(jQuery));
  3930. ;(function($) {
  3931. FormValidation.I18n = $.extend(true, FormValidation.I18n || {}, {
  3932. 'en_US': {
  3933. remote: {
  3934. 'default': 'Please enter a valid value'
  3935. }
  3936. }
  3937. });
  3938. FormValidation.Validator.remote = {
  3939. priority: 1000,
  3940. html5Attributes: {
  3941. async: 'async',
  3942. crossdomain: 'crossDomain',
  3943. data: 'data',
  3944. datatype: 'dataType',
  3945. delay: 'delay',
  3946. message: 'message',
  3947. name: 'name',
  3948. type: 'type',
  3949. url: 'url',
  3950. validkey: 'validKey'
  3951. },
  3952. /**
  3953. * Destroy the timer when destroying the FormValidation (using validator.destroy() method)
  3954. */
  3955. destroy: function(validator, $field, options, validatorName) {
  3956. var ns = validator.getNamespace(),
  3957. timer = $field.data(ns + '.' + validatorName + '.timer');
  3958. if (timer) {
  3959. clearTimeout(timer);
  3960. $field.removeData(ns + '.' + validatorName + '.timer');
  3961. }
  3962. },
  3963. /**
  3964. * Request a remote server to check the input value
  3965. *
  3966. * @param {FormValidation.Base} validator Plugin instance
  3967. * @param {jQuery} $field Field element
  3968. * @param {Object} options Can consist of the following keys:
  3969. * - async {Boolean} [optional] Indicate the Ajax request will be asynchronous or not. It's true by default
  3970. * - crossDomain {Boolean} [optional]
  3971. * - data {Object|Function} [optional]: By default, it will take the value
  3972. * {
  3973. * <fieldName>: <fieldValue>
  3974. * }
  3975. * - dataType {String} [optional]: The type of data which is returned by remote server.
  3976. * It can be json (default), text, script
  3977. * - delay {Number} [optional]
  3978. * - headers {String[]} [optional]: Additional headers
  3979. * - message {String} [optional]: The invalid message
  3980. * - name {String} [optional]: Override the field name for the request.
  3981. * - type {String} [optional] Can be GET or POST (default)
  3982. * - url {String|Function}
  3983. * - validKey {String} [optional]: The valid key. It's "valid" by default
  3984. * This is useful when connecting to external remote server or APIs provided by 3rd parties
  3985. * @returns {Deferred}
  3986. */
  3987. validate: function(validator, $field, options, validatorName) {
  3988. var ns = validator.getNamespace(),
  3989. value = validator.getFieldValue($field, validatorName),
  3990. dfd = new $.Deferred();
  3991. if (value === '') {
  3992. dfd.resolve($field, validatorName, { valid: true });
  3993. return dfd;
  3994. }
  3995. var name = $field.attr('data-' + ns + '-field'),
  3996. data = options.data || {},
  3997. url = options.url,
  3998. validKey = options.validKey || 'valid';
  3999. // Support dynamic data
  4000. if ('function' === typeof data) {
  4001. data = data.call(this, validator, $field, value);
  4002. }
  4003. // Parse string data from HTML5 attribute
  4004. if ('string' === typeof data) {
  4005. data = JSON.parse(data);
  4006. }
  4007. // Support dynamic url
  4008. if ('function' === typeof url) {
  4009. url = url.call(this, validator, $field, value);
  4010. }
  4011. data[options.name || name] = value;
  4012. var ajaxOptions = {
  4013. async: options.async === null || options.async === true || options.async === 'true',
  4014. data: data,
  4015. dataType: options.dataType || 'json',
  4016. headers: options.headers || {},
  4017. type: options.type || 'GET',
  4018. url: url
  4019. };
  4020. if (options.crossDomain !== null) {
  4021. ajaxOptions.crossDomain = (options.crossDomain === true || options.crossDomain === 'true');
  4022. }
  4023. function runCallback() {
  4024. var xhr = $.ajax(ajaxOptions);
  4025. xhr
  4026. .success(function(response) {
  4027. response.valid = (response[validKey] === true || response[validKey] === 'true')
  4028. ? true
  4029. : (response[validKey] === false || response[validKey] === 'false' ? false : null);
  4030. dfd.resolve($field, validatorName, response);
  4031. })
  4032. .error(function(response) {
  4033. dfd.resolve($field, validatorName, {
  4034. valid: false
  4035. });
  4036. });
  4037. dfd.fail(function() {
  4038. xhr.abort();
  4039. });
  4040. return dfd;
  4041. }
  4042. if (options.delay) {
  4043. // Since the form might have multiple fields with the same name
  4044. // I have to attach the timer to the field element
  4045. if ($field.data(ns + '.' + validatorName + '.timer')) {
  4046. clearTimeout($field.data(ns + '.' + validatorName + '.timer'));
  4047. }
  4048. $field.data(ns + '.' + validatorName + '.timer', setTimeout(runCallback, options.delay));
  4049. return dfd;
  4050. } else {
  4051. return runCallback();
  4052. }
  4053. }
  4054. };
  4055. }(jQuery));
  4056. ;(function($) {
  4057. FormValidation.I18n = $.extend(true, FormValidation.I18n || {}, {
  4058. 'en_US': {
  4059. stringLength: {
  4060. 'default': 'Please enter a value with valid length',
  4061. less: 'Please enter less than %s characters',
  4062. more: 'Please enter more than %s characters',
  4063. between: 'Please enter value between %s and %s characters long'
  4064. }
  4065. }
  4066. });
  4067. FormValidation.Validator.stringLength = {
  4068. html5Attributes: {
  4069. message: 'message',
  4070. min: 'min',
  4071. max: 'max',
  4072. trim: 'trim',
  4073. utf8bytes: 'utf8Bytes'
  4074. },
  4075. enableByHtml5: function($field) {
  4076. var options = {},
  4077. maxLength = $field.attr('maxlength'),
  4078. minLength = $field.attr('minlength');
  4079. if (maxLength) {
  4080. options.max = parseInt(maxLength, 10);
  4081. }
  4082. if (minLength) {
  4083. options.min = parseInt(minLength, 10);
  4084. }
  4085. return $.isEmptyObject(options) ? false : options;
  4086. },
  4087. /**
  4088. * Check if the length of element value is less or more than given number
  4089. *
  4090. * @param {FormValidation.Base} validator The validator plugin instance
  4091. * @param {jQuery} $field Field element
  4092. * @param {Object} options Consists of following keys:
  4093. * - min
  4094. * - max
  4095. * At least one of two keys is required
  4096. * The min, max keys define the number which the field value compares to. min, max can be
  4097. * - A number
  4098. * - Name of field which its value defines the number
  4099. * - Name of callback function that returns the number
  4100. * - A callback function that returns the number
  4101. *
  4102. * - message: The invalid message
  4103. * - trim: Indicate the length will be calculated after trimming the value or not. It is false, by default
  4104. * - utf8bytes: Evaluate string length in UTF-8 bytes, default to false
  4105. * @returns {Object}
  4106. */
  4107. validate: function(validator, $field, options, validatorName) {
  4108. var value = validator.getFieldValue($field, validatorName);
  4109. if (options.trim === true || options.trim === 'true') {
  4110. value = $.trim(value);
  4111. }
  4112. if (value === '') {
  4113. return true;
  4114. }
  4115. var locale = validator.getLocale(),
  4116. min = $.isNumeric(options.min) ? options.min : validator.getDynamicOption($field, options.min),
  4117. max = $.isNumeric(options.max) ? options.max : validator.getDynamicOption($field, options.max),
  4118. // Credit to http://stackoverflow.com/a/23329386 (@lovasoa) for UTF-8 byte length code
  4119. utf8Length = function(str) {
  4120. var s = str.length;
  4121. for (var i = str.length - 1; i >= 0; i--) {
  4122. var code = str.charCodeAt(i);
  4123. if (code > 0x7f && code <= 0x7ff) {
  4124. s++;
  4125. } else if (code > 0x7ff && code <= 0xffff) {
  4126. s += 2;
  4127. }
  4128. if (code >= 0xDC00 && code <= 0xDFFF) {
  4129. i--;
  4130. }
  4131. }
  4132. return s;
  4133. },
  4134. length = options.utf8Bytes ? utf8Length(value) : value.length,
  4135. isValid = true,
  4136. message = options.message || FormValidation.I18n[locale].stringLength['default'];
  4137. if ((min && length < parseInt(min, 10)) || (max && length > parseInt(max, 10))) {
  4138. isValid = false;
  4139. }
  4140. switch (true) {
  4141. case (!!min && !!max):
  4142. message = FormValidation.Helper.format(options.message || FormValidation.I18n[locale].stringLength.between, [parseInt(min, 10), parseInt(max, 10)]);
  4143. break;
  4144. case (!!min):
  4145. message = FormValidation.Helper.format(options.message || FormValidation.I18n[locale].stringLength.more, parseInt(min, 10) - 1);
  4146. break;
  4147. case (!!max):
  4148. message = FormValidation.Helper.format(options.message || FormValidation.I18n[locale].stringLength.less, parseInt(max, 10) + 1);
  4149. break;
  4150. default:
  4151. break;
  4152. }
  4153. return {
  4154. valid: isValid,
  4155. message: message
  4156. };
  4157. }
  4158. };
  4159. }(jQuery));
  4160. ;(function($) {
  4161. FormValidation.I18n = $.extend(true, FormValidation.I18n || {}, {
  4162. 'en_US': {
  4163. uri: {
  4164. 'default': 'Please enter a valid URI'
  4165. }
  4166. }
  4167. });
  4168. FormValidation.Validator.uri = {
  4169. html5Attributes: {
  4170. message: 'message',
  4171. allowlocal: 'allowLocal',
  4172. allowemptyprotocol: 'allowEmptyProtocol',
  4173. protocol: 'protocol'
  4174. },
  4175. enableByHtml5: function($field) {
  4176. return ('url' === $field.attr('type'));
  4177. },
  4178. /**
  4179. * Return true if the input value is a valid URL
  4180. *
  4181. * @param {FormValidation.Base} validator The validator plugin instance
  4182. * @param {jQuery} $field Field element
  4183. * @param {Object} options
  4184. * - message: The error message
  4185. * - allowLocal: Allow the private and local network IP. Default to false
  4186. * - allowEmptyProtocol: Allow the URI without protocol. Default to false
  4187. * - protocol: The protocols, separated by a comma. Default to "http, https, ftp"
  4188. * @returns {Boolean}
  4189. */
  4190. validate: function(validator, $field, options, validatorName) {
  4191. var value = validator.getFieldValue($field, validatorName);
  4192. if (value === '') {
  4193. return true;
  4194. }
  4195. // Credit to https://gist.github.com/dperini/729294
  4196. //
  4197. // Regular Expression for URL validation
  4198. //
  4199. // Author: Diego Perini
  4200. // Updated: 2010/12/05
  4201. //
  4202. // the regular expression composed & commented
  4203. // could be easily tweaked for RFC compliance,
  4204. // it was expressly modified to fit & satisfy
  4205. // these test for an URL shortener:
  4206. //
  4207. // http://mathiasbynens.be/demo/url-regex
  4208. //
  4209. // Notes on possible differences from a standard/generic validation:
  4210. //
  4211. // - utf-8 char class take in consideration the full Unicode range
  4212. // - TLDs are mandatory unless `allowLocal` is true
  4213. // - protocols have been restricted to ftp, http and https only as requested
  4214. //
  4215. // Changes:
  4216. //
  4217. // - IP address dotted notation validation, range: 1.0.0.0 - 223.255.255.255
  4218. // first and last IP address of each class is considered invalid
  4219. // (since they are broadcast/network addresses)
  4220. //
  4221. // - Added exclusion of private, reserved and/or local networks ranges
  4222. // unless `allowLocal` is true
  4223. //
  4224. // - Added possibility of choosing a custom protocol
  4225. //
  4226. // - Add option to validate without protocol
  4227. //
  4228. var allowLocal = options.allowLocal === true || options.allowLocal === 'true',
  4229. allowEmptyProtocol = options.allowEmptyProtocol === true || options.allowEmptyProtocol === 'true',
  4230. protocol = (options.protocol || 'http, https, ftp').split(',').join('|').replace(/\s/g, ''),
  4231. urlExp = new RegExp(
  4232. "^" +
  4233. // protocol identifier
  4234. "(?:(?:" + protocol + ")://)" +
  4235. // allow empty protocol
  4236. (allowEmptyProtocol ? '?' : '') +
  4237. // user:pass authentication
  4238. "(?:\\S+(?::\\S*)?@)?" +
  4239. "(?:" +
  4240. // IP address exclusion
  4241. // private & local networks
  4242. (allowLocal
  4243. ? ''
  4244. : ("(?!(?:10|127)(?:\\.\\d{1,3}){3})" +
  4245. "(?!(?:169\\.254|192\\.168)(?:\\.\\d{1,3}){2})" +
  4246. "(?!172\\.(?:1[6-9]|2\\d|3[0-1])(?:\\.\\d{1,3}){2})")) +
  4247. // IP address dotted notation octets
  4248. // excludes loopback network 0.0.0.0
  4249. // excludes reserved space >= 224.0.0.0
  4250. // excludes network & broadcast addresses
  4251. // (first & last IP address of each class)
  4252. "(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])" +
  4253. "(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}" +
  4254. "(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))" +
  4255. "|" +
  4256. // host name
  4257. "(?:(?:[a-z\\u00a1-\\uffff0-9]-?)*[a-z\\u00a1-\\uffff0-9]+)" +
  4258. // domain name
  4259. "(?:\\.(?:[a-z\\u00a1-\\uffff0-9]-?)*[a-z\\u00a1-\\uffff0-9])*" +
  4260. // TLD identifier
  4261. "(?:\\.(?:[a-z\\u00a1-\\uffff]{2,}))" +
  4262. // Allow intranet sites (no TLD) if `allowLocal` is true
  4263. (allowLocal ? '?' : '') +
  4264. ")" +
  4265. // port number
  4266. "(?::\\d{2,5})?" +
  4267. // resource path
  4268. "(?:/[^\\s]*)?" +
  4269. "$", "i"
  4270. );
  4271. return urlExp.test(value);
  4272. }
  4273. };
  4274. }(jQuery));