Açıklama Yok

admin-builder.js 193KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106610761086109611061116112611361146115611661176118611961206121612261236124612561266127612861296130613161326133613461356136613761386139614061416142614361446145614661476148614961506151615261536154615561566157615861596160616161626163616461656166616761686169617061716172617361746175617661776178617961806181618261836184618561866187618861896190619161926193619461956196619761986199620062016202620362046205620662076208620962106211621262136214621562166217621862196220622162226223622462256226622762286229623062316232623362346235623662376238623962406241624262436244624562466247624862496250625162526253625462556256625762586259626062616262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301630263036304630563066307630863096310631163126313631463156316631763186319632063216322632363246325632663276328632963306331633263336334633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436643764386439644064416442644364446445644664476448644964506451645264536454645564566457645864596460646164626463646464656466646764686469647064716472647364746475647664776478647964806481648264836484648564866487648864896490649164926493649464956496649764986499650065016502650365046505650665076508650965106511651265136514651565166517651865196520652165226523652465256526652765286529653065316532653365346535653665376538653965406541654265436544654565466547654865496550655165526553655465556556655765586559656065616562656365646565656665676568656965706571657265736574657565766577657865796580658165826583658465856586658765886589659065916592659365946595659665976598659966006601660266036604660566066607660866096610661166126613661466156616661766186619662066216622662366246625662666276628662966306631663266336634663566366637663866396640664166426643664466456646664766486649665066516652665366546655665666576658665966606661666266636664666566666667666866696670667166726673667466756676667766786679668066816682668366846685668666876688668966906691669266936694669566966697
  1. /* global wpforms_builder, wpf, jconfirm, wpforms_panel_switch, Choices, WPForms, WPFormsFormEmbedWizard, wpCookies, tinyMCE */
  2. var WPFormsBuilder = window.WPFormsBuilder || ( function( document, window, $ ) {
  3. var s,
  4. $builder,
  5. elements = {};
  6. /**
  7. * Whether to show the close confirmation dialog or not.
  8. *
  9. * @since 1.6.0
  10. *
  11. * @type {boolean}
  12. */
  13. var closeConfirmation = true;
  14. /**
  15. * A field is adding.
  16. *
  17. * @since 1.7.1
  18. *
  19. * @type {boolean}
  20. */
  21. var adding = false;
  22. var app = {
  23. settings: {
  24. spinner: '<i class="wpforms-loading-spinner"></i>',
  25. spinnerInline: '<i class="wpforms-loading-spinner wpforms-loading-inline"></i>',
  26. tinymceDefaults: { tinymce: { toolbar1: 'bold,italic,underline,blockquote,strikethrough,bullist,numlist,alignleft,aligncenter,alignright,undo,redo,link' }, quicktags: true },
  27. pagebreakTop: false,
  28. pagebreakBottom: false,
  29. upload_img_modal: false,
  30. },
  31. /**
  32. * Start the engine.
  33. *
  34. * @since 1.0.0
  35. */
  36. init: function() {
  37. var that = this;
  38. wpforms_panel_switch = true;
  39. s = this.settings;
  40. // Document ready.
  41. $( app.ready );
  42. // Page load.
  43. $( window ).on( 'load', function() {
  44. // In the case of jQuery 3.+, we need to wait for a ready event first.
  45. if ( typeof $.ready.then === 'function' ) {
  46. $.ready.then( app.load );
  47. } else {
  48. app.load();
  49. }
  50. } );
  51. $( window ).on( 'beforeunload', function() {
  52. if ( ! that.formIsSaved() && closeConfirmation ) {
  53. return wpforms_builder.are_you_sure_to_close;
  54. }
  55. } );
  56. },
  57. /**
  58. * Page load.
  59. *
  60. * @since 1.0.0
  61. */
  62. load: function() {
  63. app.hideLoadingOverlay();
  64. // Maybe display informational modal.
  65. if ( wpforms_builder.template_modal_display == '1' && 'fields' === wpf.getQueryString('view') ) {
  66. $.alert( {
  67. title: wpforms_builder.template_modal_title,
  68. content: wpforms_builder.template_modal_msg,
  69. icon: 'fa fa-info-circle',
  70. type: 'blue',
  71. buttons: {
  72. confirm: {
  73. text: wpforms_builder.close,
  74. btnClass: 'btn-confirm',
  75. keys: [ 'enter' ],
  76. },
  77. },
  78. } );
  79. }
  80. },
  81. /**
  82. * Document ready.
  83. *
  84. * @since 1.0.0
  85. */
  86. ready: function() {
  87. if ( app.isVisitedViaBackButton() ) {
  88. location.reload();
  89. return;
  90. }
  91. // Cache builder element.
  92. $builder = $( '#wpforms-builder' );
  93. // Cache other elements.
  94. elements.$fieldOptions = $( '#wpforms-field-options' );
  95. elements.$sortableFieldsWrap = $( '.wpforms-field-wrap' );
  96. elements.$noFieldsOptions = $( '.wpforms-no-fields-holder .no-fields' );
  97. elements.$noFieldsPreview = $( '.wpforms-no-fields-holder .no-fields-preview' );
  98. elements.$addFieldsButtons = $( '.wpforms-add-fields-button' ).not( '.not-draggable' ).not( '.warning-modal' ).not( '.education-modal' );
  99. // Remove Embed button if builder opened in popup.
  100. if ( app.isBuilderInPopup() ) {
  101. $( '#wpforms-embed' ).remove();
  102. $( '#wpforms-preview-btn' ).addClass( 'wpforms-alone' );
  103. }
  104. app.loadMsWinCSS();
  105. // Bind all actions.
  106. app.bindUIActions();
  107. // Trigger initial save for new forms.
  108. var newForm = wpf.getQueryString( 'newform' );
  109. if ( newForm ) {
  110. app.formSave( false );
  111. }
  112. // Setup/cache some vars not available before
  113. s.formID = $( '#wpforms-builder-form' ).data( 'id' );
  114. s.pagebreakTop = $( '.wpforms-pagebreak-top' ).length;
  115. s.pagebreakBottom = $( '.wpforms-pagebreak-bottom' ).length;
  116. // Disable implicit submission for every form inside the builder.
  117. // All form values are managed by JS and should not be submitted by pressing Enter.
  118. $builder.on( 'keypress', '#wpforms-builder-form :input:not(textarea)', function( e ) {
  119. if ( e.keyCode === 13 ) {
  120. e.preventDefault();
  121. }
  122. } );
  123. // If there is a section configured, display it.
  124. // Otherwise we show the first panel by default.
  125. $( '.wpforms-panel' ).each( function( index, el ) {
  126. var $this = $( this ),
  127. $configured = $this.find( '.wpforms-panel-sidebar-section.configured' ).first();
  128. if ( $configured.length ) {
  129. var section = $configured.data( 'section' );
  130. $configured.addClass( 'active' );
  131. $this.find( '.wpforms-panel-content-section-' + section ).show().addClass( 'active' );
  132. $this.find( '.wpforms-panel-content-section-default' ).hide();
  133. } else {
  134. $this.find( '.wpforms-panel-content-section:first-of-type' ).show().addClass( 'active' );
  135. $this.find( '.wpforms-panel-sidebar-section:first-of-type' ).addClass( 'active' );
  136. }
  137. } );
  138. app.loadEntryPreviewFields();
  139. // Drag and drop sortable elements.
  140. app.fieldSortable();
  141. app.fieldChoiceSortable( 'select' );
  142. app.fieldChoiceSortable( 'radio' );
  143. app.fieldChoiceSortable( 'checkbox' );
  144. app.fieldChoiceSortable( 'payment-multiple' );
  145. app.fieldChoiceSortable( 'payment-checkbox' );
  146. app.fieldChoiceSortable( 'payment-select' );
  147. // Load match heights.
  148. $( '.wpforms-setup-templates.core .wpforms-template-inner' ).matchHeight( {
  149. byRow: false,
  150. } );
  151. $( '.wpforms-setup-templates.additional .wpforms-template-inner' ).matchHeight( {
  152. byRow: false,
  153. } );
  154. // Set field group visibility.
  155. $( '.wpforms-add-fields-group' ).each( function( index, el ) {
  156. app.fieldGroupToggle( $( this ), 'load' );
  157. } );
  158. app.registerTemplates();
  159. // Trim long form titles.
  160. app.trimFormTitle();
  161. // Load Tooltips.
  162. wpf.initTooltips();
  163. // Load Color Pickers.
  164. app.loadColorPickers();
  165. // Hide/Show CAPTCHA in form.
  166. app.captchaToggle();
  167. // Confirmations initial setup
  168. app.confirmationsSetup();
  169. // Notification settings.
  170. app.notificationToggle();
  171. app.notificationsByStatusAlerts();
  172. // Secret builder hotkeys.
  173. app.builderHotkeys();
  174. // Clone form title to setup page.
  175. $( '#wpforms-setup-name' ).val( $( '#wpforms-panel-field-settings-form_title' ).val() );
  176. // jquery-confirm defaults.
  177. jconfirm.defaults = {
  178. closeIcon: false,
  179. backgroundDismiss: false,
  180. escapeKey: true,
  181. animationBounce: 1,
  182. useBootstrap: false,
  183. theme: 'modern',
  184. boxWidth: '400px',
  185. animateFromElement: false,
  186. content: wpforms_builder.something_went_wrong,
  187. };
  188. app.dropdownField.init();
  189. app.initSomeFieldOptions();
  190. app.dismissNotice();
  191. },
  192. /**
  193. * Load Microsoft Windows specific stylesheet.
  194. *
  195. * @since 1.6.8
  196. */
  197. loadMsWinCSS: function() {
  198. var ua = navigator.userAgent;
  199. // Detect OS & browsers.
  200. if (
  201. ua.indexOf( 'Windows' ) < 0 || (
  202. ua.indexOf( 'Chrome' ) < 0 &&
  203. ua.indexOf( 'Firefox' ) < 0
  204. )
  205. ) {
  206. return;
  207. }
  208. $( '<link>' )
  209. .appendTo( 'head' )
  210. .attr( {
  211. type: 'text/css',
  212. rel: 'stylesheet',
  213. href: wpforms_builder.ms_win_css_url,
  214. } );
  215. },
  216. /**
  217. * Builder was visited via back button in browser.
  218. *
  219. * @since 1.6.5
  220. *
  221. * @returns {boolean} True if the builder was visited via back button in browser.
  222. */
  223. isVisitedViaBackButton: function() {
  224. if ( ! performance ) {
  225. return false;
  226. }
  227. var isVisitedViaBackButton = false;
  228. performance.getEntriesByType( 'navigation' ).forEach( function( nav ) {
  229. if ( nav.type === 'back_forward' ) {
  230. isVisitedViaBackButton = true;
  231. }
  232. } );
  233. return isVisitedViaBackButton;
  234. },
  235. /**
  236. * Remove loading overlay.
  237. *
  238. * @since 1.6.8
  239. */
  240. hideLoadingOverlay: function() {
  241. var $overlay = $( '#wpforms-builder-overlay' );
  242. $overlay.addClass( 'fade-out' );
  243. setTimeout( function() {
  244. $overlay.hide();
  245. }, 250 );
  246. },
  247. /**
  248. * Show loading overlay.
  249. *
  250. * @since 1.6.8
  251. */
  252. showLoadingOverlay: function() {
  253. var $overlay = $( '#wpforms-builder-overlay' );
  254. $overlay.removeClass( 'fade-out' );
  255. $overlay.show();
  256. },
  257. /**
  258. * Initialize some fields options controls.
  259. *
  260. * @since 1.6.3
  261. */
  262. initSomeFieldOptions: function() {
  263. // Show a toggled options groups.
  264. app.toggleAllOptionGroups( $builder );
  265. // Date/Time field Date type option.
  266. $builder.find( '.wpforms-field-option-row-date .type select' ).trigger( 'change' );
  267. },
  268. /**
  269. * Dropdown field component.
  270. *
  271. * @since 1.6.1
  272. */
  273. dropdownField: {
  274. /**
  275. * Field configuration.
  276. *
  277. * @since 1.6.1
  278. */
  279. config: {
  280. modernClass: 'choicesjs-select',
  281. args: {
  282. searchEnabled: false,
  283. searchChoices: false,
  284. renderChoiceLimit : 1,
  285. shouldSort: false,
  286. callbackOnInit: function() {
  287. var $element = $( this.containerOuter.element ),
  288. $previewSelect = $element.closest( '.wpforms-field' ).find( 'select' );
  289. // Turn off disabled styles.
  290. if ( $element.hasClass( 'is-disabled' ) ) {
  291. $element.removeClass( 'is-disabled' );
  292. }
  293. // Disable instances on the preview panel.
  294. if ( $previewSelect.is( '[readonly]' ) ) {
  295. this.disable();
  296. $previewSelect.prop( 'disabled', false );
  297. }
  298. if ( this.passedElement.element.multiple ) {
  299. // Hide a placeholder if field has selected choices.
  300. if ( this.getValue( true ).length ) {
  301. $( this.input.element ).addClass( 'choices__input--hidden' );
  302. }
  303. }
  304. },
  305. },
  306. },
  307. /**
  308. * Initialization for field component.
  309. *
  310. * @since 1.6.1
  311. */
  312. init: function() {
  313. // Choices.js init.
  314. $builder.find( '.' + app.dropdownField.config.modernClass ).each( function() {
  315. app.dropdownField.events.choicesInit( $( this ) );
  316. } );
  317. // Multiple option.
  318. $builder.on(
  319. 'change',
  320. '.wpforms-field-option-select .wpforms-field-option-row-multiple input',
  321. app.dropdownField.events.multiple
  322. );
  323. // Style option.
  324. $builder.on(
  325. 'change',
  326. '.wpforms-field-option-select .wpforms-field-option-row-style select, .wpforms-field-option-payment-select .wpforms-field-option-row-style select',
  327. app.dropdownField.events.applyStyle
  328. );
  329. },
  330. /**
  331. * Field events.
  332. *
  333. * @since 1.6.1
  334. */
  335. events: {
  336. /**
  337. * Load Choices.js library.
  338. *
  339. * @since 1.6.1
  340. *
  341. * @param {object} $element jQuery element selector.
  342. */
  343. choicesInit: function( $element ) {
  344. var instance = new Choices( $element[0], app.dropdownField.config.args );
  345. app.dropdownField.helpers.setInstance( $element, instance );
  346. app.dropdownField.helpers.addPlaceholderChoice( $element, instance );
  347. },
  348. /**
  349. * Multiple option callback.
  350. *
  351. * @since 1.6.1
  352. *
  353. * @param {object} event Event object.
  354. */
  355. multiple: function( event ) {
  356. var fieldId = $( this ).closest( '.wpforms-field-option-row-multiple' ).data().fieldId,
  357. $primary = app.dropdownField.helpers.getPrimarySelector( fieldId ),
  358. $optionChoicesItems = $( '#wpforms-field-option-row-' + fieldId + '-choices input.default' ),
  359. $placeholder = $primary.find( '.placeholder' ),
  360. isDynamicChoices = app.dropdownField.helpers.isDynamicChoices( fieldId ),
  361. isMultiple = event.target.checked,
  362. choicesType = isMultiple ? 'checkbox' : 'radio',
  363. selectedChoices;
  364. // Add/remove a `multiple` attribute.
  365. $primary.prop( 'multiple', isMultiple );
  366. // Change a `Choices` fields type:
  367. // checkbox - needed for multiple selection
  368. // radio - needed for single selection
  369. $optionChoicesItems.prop( 'type', choicesType );
  370. // Dynamic Choices doesn't have default choices (selected options) - make all as unselected.
  371. if ( isDynamicChoices ) {
  372. $primary.find( 'option:selected' ).prop( 'selected', false );
  373. }
  374. // Gets default choices.
  375. selectedChoices = $optionChoicesItems.filter( ':checked' );
  376. if ( ! isMultiple && selectedChoices.length ) {
  377. // Uncheck all choices.
  378. $optionChoicesItems.prop( 'checked', false );
  379. // For single selection we can choose only one.
  380. $( selectedChoices.get( 0 ) ).prop( 'checked', true );
  381. }
  382. // Toggle selection for a placeholder option based on a select type.
  383. if ( $placeholder.length ) {
  384. $placeholder.prop( 'selected', ! isMultiple );
  385. }
  386. // Update a primary field.
  387. app.dropdownField.helpers.update( fieldId, isDynamicChoices );
  388. },
  389. /**
  390. * Apply a style to <select> - modern or classic.
  391. *
  392. * @since 1.6.1
  393. */
  394. applyStyle: function() {
  395. var $field = $( this ),
  396. fieldId = $field.closest( '.wpforms-field-option-row-style' ).data().fieldId,
  397. fieldVal = $field.val();
  398. if ( 'modern' === fieldVal ) {
  399. app.dropdownField.helpers.convertClassicToModern( fieldId );
  400. } else {
  401. app.dropdownField.helpers.convertModernToClassic( fieldId );
  402. }
  403. },
  404. },
  405. helpers: {
  406. /**
  407. * Get Modern select options and prepare them for the Classic <select>.
  408. *
  409. * @since 1.6.1
  410. *
  411. * @param {string} fieldId Field ID.
  412. */
  413. convertModernToClassic: function( fieldId ) {
  414. var $primary = app.dropdownField.helpers.getPrimarySelector( fieldId ),
  415. isDynamicChoices = app.dropdownField.helpers.isDynamicChoices( fieldId ),
  416. instance = app.dropdownField.helpers.getInstance( $primary );
  417. // Destroy the instance of Choices.js.
  418. instance.destroy();
  419. // Update a placeholder.
  420. app.dropdownField.helpers.updatePlaceholderChoice( instance, fieldId );
  421. // Update choices.
  422. if ( ! isDynamicChoices ) {
  423. app.fieldChoiceUpdate( 'select', fieldId );
  424. }
  425. },
  426. /**
  427. * Convert a Classic to Modern style selector.
  428. *
  429. * @since 1.6.1
  430. *
  431. * @param {string} fieldId Field ID.
  432. */
  433. convertClassicToModern: function( fieldId ) {
  434. var $primary = app.dropdownField.helpers.getPrimarySelector( fieldId ),
  435. isDynamicChoices = app.dropdownField.helpers.isDynamicChoices( fieldId );
  436. // Update choices.
  437. if ( ! isDynamicChoices ) {
  438. app.fieldChoiceUpdate( 'select', fieldId );
  439. }
  440. // Call a Choices.js initialization.
  441. app.dropdownField.events.choicesInit( $primary );
  442. },
  443. /**
  444. * Update a primary field.
  445. *
  446. * @since 1.6.1
  447. *
  448. * @param {string} fieldId Field ID.
  449. * @param {boolean} isDynamicChoices True if `Dynamic Choices` is turned on.
  450. */
  451. update: function( fieldId, isDynamicChoices ) {
  452. var $primary = app.dropdownField.helpers.getPrimarySelector( fieldId );
  453. if ( app.dropdownField.helpers.isModernSelect( $primary ) ) {
  454. // If we had a `Modern` select before, then we need to make re-init - destroy() + init().
  455. app.dropdownField.helpers.convertModernToClassic( fieldId );
  456. app.dropdownField.events.choicesInit( $primary );
  457. } else {
  458. // Update choices.
  459. if ( ! isDynamicChoices ) {
  460. app.fieldChoiceUpdate( 'select', fieldId );
  461. }
  462. }
  463. },
  464. /**
  465. * Add a new choice to behave like a placeholder.
  466. *
  467. * @since 1.6.1
  468. *
  469. * @param {object} $jquerySelector jQuery primary selector.
  470. * @param {object} instance The instance of Choices.js.
  471. *
  472. * @returns {boolean} False if a fake placeholder wasn't added.
  473. */
  474. addPlaceholderChoice: function( $jquerySelector, instance ) {
  475. var fieldId = $jquerySelector.closest( '.wpforms-field' ).data().fieldId,
  476. hasDefaults = app.dropdownField.helpers.hasDefaults( fieldId );
  477. if ( app.dropdownField.helpers.isDynamicChoices( fieldId ) ) {
  478. hasDefaults = false;
  479. }
  480. // Already has a placeholder.
  481. if ( false !== app.dropdownField.helpers.searchPlaceholderChoice( instance ) ) {
  482. return false;
  483. }
  484. // No choices.
  485. if ( ! instance.config.choices.length ) {
  486. return false;
  487. }
  488. var placeholder = instance.config.choices[0].label,
  489. isMultiple = $( instance.passedElement.element ).prop( 'multiple' ),
  490. selected = ! ( isMultiple || hasDefaults );
  491. // Add a new choice as a placeholder.
  492. instance.setChoices(
  493. [
  494. { value: '', label: placeholder, selected: selected, placeholder: true },
  495. ],
  496. 'value',
  497. 'label',
  498. false
  499. );
  500. // Additional case for multiple select.
  501. if ( isMultiple ) {
  502. $( instance.input.element ).prop( 'placeholder', placeholder );
  503. }
  504. return true;
  505. },
  506. /**
  507. * Search a choice-placeholder item.
  508. *
  509. * @since 1.6.1
  510. *
  511. * @param {object} instance The instance of Choices.js.
  512. *
  513. * @returns {boolean|object} False if a field doesn't have a choice-placeholder. Otherwise - return choice item.
  514. */
  515. searchPlaceholderChoice: function( instance ) {
  516. var find = false;
  517. instance.config.choices.forEach( function( item, i, choices ) {
  518. if ( 'undefined' !== typeof item.placeholder && true === item.placeholder ) {
  519. find = {
  520. key: i,
  521. item: item,
  522. };
  523. return false;
  524. }
  525. } );
  526. return find;
  527. },
  528. /**
  529. * Add/update a placeholder.
  530. *
  531. * @since 1.6.1
  532. *
  533. * @param {object} instance The instance of Choices.js.
  534. * @param {string} fieldId Field ID.
  535. */
  536. updatePlaceholderChoice: function( instance, fieldId ) {
  537. var $primary = $( instance.passedElement.element ),
  538. placeholderValue = wpf.sanitizeHTML( $( '#wpforms-field-option-' + fieldId + '-placeholder' ).val() ),
  539. placeholderChoice = app.dropdownField.helpers.searchPlaceholderChoice( instance ),
  540. $placeholderOption = {};
  541. // Get an option with placeholder.
  542. // Note: `.placeholder` class is skipped when calling Choices.js destroy() method.
  543. if ( 'object' === typeof placeholderChoice ) {
  544. $placeholderOption = $( $primary.find( 'option' ).get( placeholderChoice.key ) );
  545. }
  546. // We have a placeholder and need to update the UI with it.
  547. if ( '' !== placeholderValue ) {
  548. if ( ! $.isEmptyObject( $placeholderOption ) && $placeholderOption.length ) {
  549. // Update a placeholder option.
  550. $placeholderOption
  551. .addClass( 'placeholder' )
  552. .text( placeholderValue );
  553. } else {
  554. // Add a placeholder option.
  555. $primary.prepend( '<option value="" class="placeholder">' + placeholderValue + '</option>' );
  556. }
  557. } else {
  558. // Remove the placeholder as it's empty.
  559. if ( $placeholderOption.length ) {
  560. $placeholderOption.remove();
  561. }
  562. }
  563. },
  564. /**
  565. * Is it a `Modern` style dropdown field?
  566. *
  567. * @since 1.6.1
  568. *
  569. * @param {object} $jquerySelector jQuery primary selector.
  570. *
  571. * @returns {boolean} True if it's a `Modern` style select, false otherwise.
  572. */
  573. isModernSelect: function( $jquerySelector ) {
  574. var instance = app.dropdownField.helpers.getInstance( $jquerySelector );
  575. if ( 'object' !== typeof instance ) {
  576. return false;
  577. }
  578. if ( $.isEmptyObject( instance ) ) {
  579. return false;
  580. }
  581. return instance.initialised;
  582. },
  583. /**
  584. * Save an instance of Choices.js.
  585. *
  586. * @since 1.6.1
  587. *
  588. * @param {object} $jquerySelector jQuery primary selector.
  589. * @param {object} instance The instance of Choices.js.
  590. */
  591. setInstance: function( $jquerySelector, instance ) {
  592. $jquerySelector.data( 'choicesjs', instance );
  593. },
  594. /**
  595. * Retrieve an instance of Choices.js.
  596. *
  597. * @since 1.6.1
  598. *
  599. * @param {object} $jquerySelector jQuery primary selector.
  600. *
  601. * @returns {object} The instance of Choices.js.
  602. */
  603. getInstance: function( $jquerySelector ) {
  604. return $jquerySelector.data( 'choicesjs' );
  605. },
  606. /**
  607. * Is `Dynamic Choices` used?
  608. *
  609. * @since 1.6.1
  610. *
  611. * @param {string} fieldId Field ID.
  612. *
  613. * @returns {boolean} True if a `Dynamic Choices` active, false otherwise.
  614. */
  615. isDynamicChoices: function( fieldId ) {
  616. var $fieldOption = $( '#wpforms-field-option-' + fieldId + '-dynamic_choices' );
  617. if ( ! $fieldOption.length ) {
  618. return false;
  619. }
  620. return '' !== $fieldOption.val();
  621. },
  622. /**
  623. * Is a field has default choices?
  624. *
  625. * @since 1.6.1
  626. *
  627. * @param {string} fieldId Field ID.
  628. *
  629. * @returns {boolean} True if a field has default choices.
  630. */
  631. hasDefaults: function( fieldId ) {
  632. var $choicesList = $( '#wpforms-field-option-row-' + fieldId + '-choices .choices-list' );
  633. return !! $choicesList.find( 'input.default:checked' ).length;
  634. },
  635. /**
  636. * Retrieve a jQuery selector for the Primary field.
  637. *
  638. * @since 1.6.1
  639. *
  640. * @param {string} fieldId Field ID.
  641. *
  642. * @returns {object} jQuery primary selector.
  643. */
  644. getPrimarySelector: function( fieldId ) {
  645. return $( '#wpforms-field-' + fieldId + ' .primary-input' );
  646. },
  647. },
  648. },
  649. /**
  650. * Add number slider events listeners.
  651. *
  652. * @since 1.5.7
  653. *
  654. * @param {object} $builder JQuery object.
  655. */
  656. numberSliderEvents: function( $builder ) {
  657. // Minimum update.
  658. $builder.on(
  659. 'input',
  660. '.wpforms-field-option-row-min_max .wpforms-input-row .wpforms-number-slider-min',
  661. app.fieldNumberSliderUpdateMin
  662. );
  663. // Maximum update.
  664. $builder.on(
  665. 'input',
  666. '.wpforms-field-option-row-min_max .wpforms-input-row .wpforms-number-slider-max',
  667. app.fieldNumberSliderUpdateMax
  668. );
  669. // Change default input value.
  670. $builder.on(
  671. 'input',
  672. '.wpforms-number-slider-default-value',
  673. _.debounce( app.changeNumberSliderDefaultValue, 500 )
  674. );
  675. // Change step value.
  676. $builder.on(
  677. 'input',
  678. '.wpforms-number-slider-step',
  679. _.debounce( app.changeNumberSliderStep, 500 )
  680. );
  681. // Check step value.
  682. $builder.on(
  683. 'focusout',
  684. '.wpforms-number-slider-step',
  685. app.checkNumberSliderStep
  686. );
  687. // Change value display.
  688. $builder.on(
  689. 'input',
  690. '.wpforms-number-slider-value-display',
  691. _.debounce( app.changeNumberSliderValueDisplay, 500 )
  692. );
  693. // Change min value.
  694. $builder.on(
  695. 'input',
  696. '.wpforms-number-slider-min',
  697. _.debounce( app.changeNumberSliderMin, 500 )
  698. );
  699. // Change max value.
  700. $builder.on(
  701. 'input',
  702. '.wpforms-number-slider-max',
  703. _.debounce( app.changeNumberSliderMax, 500 )
  704. );
  705. },
  706. /**
  707. * Change number slider min option.
  708. *
  709. * @since 1.5.7
  710. *
  711. * @param {object} event Input event.
  712. */
  713. changeNumberSliderMin: function( event ) {
  714. var fieldID = $( event.target ).parents( '.wpforms-field-option-row' ).data( 'fieldId' );
  715. var value = parseFloat( event.target.value );
  716. if ( isNaN( value ) ) {
  717. return;
  718. }
  719. app.updateNumberSliderDefaultValueAttr( fieldID, event.target.value, 'min' );
  720. },
  721. /**
  722. * Change number slider max option.
  723. *
  724. * @since 1.5.7
  725. *
  726. * @param {object} event Input event.
  727. */
  728. changeNumberSliderMax: function( event ) {
  729. var fieldID = $( event.target ).parents( '.wpforms-field-option-row' ).data( 'fieldId' );
  730. var value = parseFloat( event.target.value );
  731. if ( isNaN( value ) ) {
  732. return;
  733. }
  734. app.updateNumberSliderDefaultValueAttr( fieldID, event.target.value, 'max' )
  735. .updateNumberSliderStepValueMaxAttr( fieldID, event.target.value );
  736. },
  737. /**
  738. * Change number slider value display option.
  739. *
  740. * @since 1.5.7
  741. *
  742. * @param {object} event Input event.
  743. */
  744. changeNumberSliderValueDisplay: function( event ) {
  745. var str = event.target.value;
  746. var fieldID = $( event.target ).parents( '.wpforms-field-option-row' ).data( 'fieldId' );
  747. var defaultValue = document.getElementById( 'wpforms-field-option-' + fieldID + '-default_value' );
  748. if ( defaultValue ) {
  749. app.updateNumberSliderHintStr( fieldID, str )
  750. .updateNumberSliderHint( fieldID, defaultValue.value );
  751. }
  752. },
  753. /**
  754. * Change number slider step option.
  755. *
  756. * @since 1.5.7
  757. *
  758. * @param {object} event Input event.
  759. */
  760. changeNumberSliderStep: function( event ) {
  761. var value = parseFloat( event.target.value );
  762. if ( isNaN( value ) ) {
  763. return;
  764. }
  765. var max = parseFloat( event.target.max );
  766. var min = parseFloat( event.target.min );
  767. var fieldID = $( event.target ).parents( '.wpforms-field-option-row' ).data( 'fieldId' );
  768. if ( value <= 0 ) {
  769. return;
  770. }
  771. if ( value > max ) {
  772. event.target.value = max;
  773. return;
  774. }
  775. if ( value < min ) {
  776. event.target.value = min;
  777. return;
  778. }
  779. app.updateNumberSliderAttr( fieldID, value, 'step' )
  780. .updateNumberSliderDefaultValueAttr( fieldID, value, 'step' );
  781. },
  782. /**
  783. * Check number slider step option.
  784. *
  785. * @since 1.6.2.3
  786. *
  787. * @param {object} event Focusout event object.
  788. */
  789. checkNumberSliderStep: function( event ) {
  790. var value = parseFloat( event.target.value ),
  791. $input = $( this );
  792. if ( ! isNaN( value ) && value > 0 ) {
  793. return;
  794. }
  795. $.confirm( {
  796. title: wpforms_builder.heads_up,
  797. content: wpforms_builder.error_number_slider_increment,
  798. icon: 'fa fa-exclamation-circle',
  799. type: 'orange',
  800. buttons: {
  801. confirm: {
  802. text: wpforms_builder.ok,
  803. btnClass: 'btn-confirm',
  804. keys: [ 'enter' ],
  805. action: function() {
  806. $input.val( '' ).focus();
  807. },
  808. },
  809. },
  810. } );
  811. },
  812. /**
  813. * Change number slider default value option.
  814. *
  815. * @since 1.5.7
  816. *
  817. * @param {object} event Input event.
  818. */
  819. changeNumberSliderDefaultValue: function( event ) {
  820. var value = parseFloat( event.target.value );
  821. if ( ! isNaN( value ) ) {
  822. var max = parseFloat( event.target.max );
  823. var min = parseFloat( event.target.min );
  824. var fieldID = $( event.target ).parents( '.wpforms-field-option-row-default_value' ).data( 'fieldId' );
  825. if ( value > max ) {
  826. event.target.value = max;
  827. return;
  828. }
  829. if ( value < min ) {
  830. event.target.value = min;
  831. return;
  832. }
  833. app.updateNumberSlider( fieldID, value )
  834. .updateNumberSliderHint( fieldID, value );
  835. }
  836. },
  837. /**
  838. * Update number slider default value attribute.
  839. *
  840. * @since 1.5.7
  841. *
  842. * @param {number} fieldID Field ID.
  843. * @param {*} newValue Default value attribute.
  844. * @param {*} attr Attribute name.
  845. *
  846. * @returns {object} App instance.
  847. */
  848. updateNumberSliderDefaultValueAttr: function( fieldID, newValue, attr ) {
  849. var input = document.getElementById( 'wpforms-field-option-' + fieldID + '-default_value' );
  850. if ( input ) {
  851. var value = parseFloat( input.value );
  852. input.setAttribute( attr, newValue );
  853. newValue = parseFloat( newValue );
  854. if ( 'max' === attr && value > newValue ) {
  855. input.value = newValue;
  856. $( input ).trigger( 'input' );
  857. }
  858. if ( 'min' === attr && value < newValue ) {
  859. input.value = newValue;
  860. $( input ).trigger( 'input' );
  861. }
  862. }
  863. return this;
  864. },
  865. /**
  866. * Update number slider value.
  867. *
  868. * @since 1.5.7
  869. *
  870. * @param {number} fieldID Field ID.
  871. * @param {string} value Number slider value.
  872. *
  873. * @returns {object} App instance.
  874. */
  875. updateNumberSlider: function( fieldID, value ) {
  876. var numberSlider = document.getElementById( 'wpforms-number-slider-' + fieldID );
  877. if ( numberSlider ) {
  878. numberSlider.value = value;
  879. }
  880. return this;
  881. },
  882. /**
  883. * Update number slider attribute.
  884. *
  885. * @since 1.5.7
  886. *
  887. * @param {number} fieldID Field ID.
  888. * @param {mixed} value Attribute value.
  889. * @param {*} attr Attribute name.
  890. *
  891. * @returns {object} App instance.
  892. */
  893. updateNumberSliderAttr: function( fieldID, value, attr ) {
  894. var numberSlider = document.getElementById( 'wpforms-number-slider-' + fieldID );
  895. if ( numberSlider ) {
  896. numberSlider.setAttribute( attr, value );
  897. }
  898. return this;
  899. },
  900. /**
  901. * Update number slider hint string.
  902. *
  903. * @since 1.5.7
  904. *
  905. * @param {number} fieldID Field ID.
  906. * @param {string} str Hint string.
  907. *
  908. * @returns {object} App instance.
  909. */
  910. updateNumberSliderHintStr: function( fieldID, str ) {
  911. var hint = document.getElementById( 'wpforms-number-slider-hint-' + fieldID );
  912. if ( hint ) {
  913. hint.dataset.hint = str;
  914. }
  915. return this;
  916. },
  917. /**
  918. * Update number slider Hint value.
  919. *
  920. * @since 1.5.7
  921. *
  922. * @param {number} fieldID Field ID.
  923. * @param {string} value Hint value.
  924. *
  925. * @returns {object} App instance.
  926. */
  927. updateNumberSliderHint: function( fieldID, value ) {
  928. var hint = document.getElementById( 'wpforms-number-slider-hint-' + fieldID );
  929. if ( hint ) {
  930. hint.innerHTML = wpf.sanitizeHTML( hint.dataset.hint ).replace( '{value}', '<b>' + value + '</b>' );
  931. }
  932. return this;
  933. },
  934. /**
  935. * Update min attribute.
  936. *
  937. * @since 1.5.7
  938. *
  939. * @param {object} event Input event.
  940. */
  941. fieldNumberSliderUpdateMin: function( event ) {
  942. var $options = $( event.target ).parents( '.wpforms-field-option-row-min_max' );
  943. var max = parseFloat( $options.find( '.wpforms-number-slider-max' ).val() );
  944. var current = parseFloat( event.target.value );
  945. if ( isNaN( current ) ) {
  946. return;
  947. }
  948. if ( max <= current ) {
  949. event.preventDefault();
  950. this.value = max;
  951. return;
  952. }
  953. var fieldId = $options.data( 'field-id' );
  954. var numberSlider = $builder.find( '#wpforms-field-' + fieldId + ' input[type="range"]' );
  955. numberSlider.attr( 'min', current );
  956. },
  957. /**
  958. * Update max attribute.
  959. *
  960. * @since 1.5.7
  961. *
  962. * @param {object} event Input event.
  963. */
  964. fieldNumberSliderUpdateMax: function( event ) {
  965. var $options = $( event.target ).parents( '.wpforms-field-option-row-min_max' );
  966. var min = parseFloat( $options.find( '.wpforms-number-slider-min' ).val() );
  967. var current = parseFloat( event.target.value );
  968. if ( isNaN( current ) ) {
  969. return;
  970. }
  971. if ( min >= current ) {
  972. event.preventDefault();
  973. this.value = min;
  974. return;
  975. }
  976. var fieldId = $options.data( 'field-id' );
  977. var numberSlider = $builder.find( '#wpforms-field-' + fieldId + ' input[type="range"]' );
  978. numberSlider.attr( 'max', current );
  979. },
  980. /**
  981. * Update max attribute for step value.
  982. *
  983. * @since 1.5.7
  984. *
  985. * @param {number} fieldID Field ID.
  986. * @param {*} newValue Default value attribute.
  987. *
  988. * @returns {object} App instance.
  989. */
  990. updateNumberSliderStepValueMaxAttr: function( fieldID, newValue ) {
  991. var input = document.getElementById( 'wpforms-field-option-' + fieldID + '-step' );
  992. if ( input ) {
  993. var value = parseFloat( input.value );
  994. input.setAttribute( 'max', newValue );
  995. newValue = parseFloat( newValue );
  996. if ( value > newValue ) {
  997. input.value = newValue;
  998. $( input ).trigger( 'input' );
  999. }
  1000. }
  1001. return this;
  1002. },
  1003. /**
  1004. * Update upload selector.
  1005. *
  1006. * @since 1.5.6
  1007. *
  1008. * @param {object} target Changed :input.
  1009. */
  1010. fieldFileUploadPreviewUpdate: function( target ) {
  1011. var $options = $( target ).parents( '.wpforms-field-option-file-upload' );
  1012. var fieldId = $options.data( 'field-id' );
  1013. var styleOption = $options.find( '#wpforms-field-option-' + fieldId + '-style' ).val();
  1014. var $maxFileNumberRow = $options.find( '#wpforms-field-option-row-' + fieldId + '-max_file_number' );
  1015. var maxFileNumber = parseInt( $maxFileNumberRow.find( 'input' ).val(), 10 );
  1016. var $preview = $( '#wpforms-field-' + fieldId );
  1017. var classicPreview = '.wpforms-file-upload-builder-classic';
  1018. var modernPreview = '.wpforms-file-upload-builder-modern';
  1019. if ( styleOption === 'classic' ) {
  1020. $( classicPreview, $preview ).removeClass( 'wpforms-hide' );
  1021. $( modernPreview, $preview ).addClass( 'wpforms-hide' );
  1022. $maxFileNumberRow.addClass( 'wpforms-hidden' );
  1023. } else {
  1024. // Change hint and title.
  1025. if ( maxFileNumber > 1 ) {
  1026. $preview
  1027. .find( '.modern-title' )
  1028. .text( wpforms_builder.file_upload.preview_title_plural );
  1029. $preview
  1030. .find( '.modern-hint' )
  1031. .text( wpforms_builder.file_upload.preview_hint.replace( '{maxFileNumber}', maxFileNumber ) )
  1032. .removeClass( 'wpforms-hide' );
  1033. } else {
  1034. $preview
  1035. .find( '.modern-title' )
  1036. .text( wpforms_builder.file_upload.preview_title_single );
  1037. $preview
  1038. .find( '.modern-hint' )
  1039. .text( wpforms_builder.file_upload.preview_hint.replace( '{maxFileNumber}', 1 ) )
  1040. .addClass( 'wpforms-hide' );
  1041. }
  1042. // Display the preview.
  1043. $( classicPreview, $preview ).addClass( 'wpforms-hide' );
  1044. $( modernPreview, $preview ).removeClass( 'wpforms-hide' );
  1045. $maxFileNumberRow.removeClass( 'wpforms-hidden' );
  1046. }
  1047. },
  1048. /**
  1049. * Update limit controls by changing checkbox.
  1050. *
  1051. * @since 1.5.6
  1052. *
  1053. * @param {number} id Field id.
  1054. * @param {bool} checked Whether an option is checked or not.
  1055. */
  1056. updateTextFieldsLimitControls: function( id, checked ) {
  1057. if ( ! checked ) {
  1058. $( '#wpforms-field-option-row-' + id + '-limit_controls' ).addClass( 'wpforms-hide' );
  1059. } else {
  1060. $( '#wpforms-field-option-row-' + id + '-limit_controls' ).removeClass( 'wpforms-hide' );
  1061. }
  1062. },
  1063. /**
  1064. * Update Password Strength controls by changing checkbox.
  1065. *
  1066. * @since 1.6.7
  1067. *
  1068. * @param {number} id Field id.
  1069. * @param {bool} checked Whether an option is checked or not.
  1070. */
  1071. updatePasswordStrengthControls: function( id, checked ) {
  1072. var $strengthControls = $( '#wpforms-field-option-row-' + id + '-password-strength-level' );
  1073. if ( checked ) {
  1074. $strengthControls.removeClass( 'wpforms-hidden' );
  1075. } else {
  1076. $strengthControls.addClass( 'wpforms-hidden' );
  1077. }
  1078. },
  1079. /**
  1080. * Update Rich Text media controls by changing checkbox.
  1081. *
  1082. * @since 1.7.0
  1083. */
  1084. updateRichTextMediaFieldsLimitControls: function() {
  1085. var $this = $( this ),
  1086. fieldId = $this.closest( '.wpforms-field-option-row-media_enabled' ).data( 'field-id' ),
  1087. $mediaControls = $( '#wpforms-field-option-row-' + fieldId + '-media_controls' ),
  1088. $toolbar = $( '#wpforms-field-' + fieldId + ' .wpforms-richtext-wrap .mce-toolbar-grp' );
  1089. if ( ! $this.is( ':checked' ) ) {
  1090. $mediaControls.hide();
  1091. $toolbar.removeClass( 'wpforms-field-richtext-media-enabled' );
  1092. } else {
  1093. $mediaControls.show();
  1094. $toolbar.addClass( 'wpforms-field-richtext-media-enabled' );
  1095. }
  1096. },
  1097. /**
  1098. * Update Rich Text style preview by changing select.
  1099. *
  1100. * @since 1.7.0
  1101. */
  1102. updateRichTextStylePreview: function() {
  1103. var $this = $( this ),
  1104. fieldId = $this.closest( '.wpforms-field-option-row-style' ).data( 'field-id' ),
  1105. $toolbar = $( '#wpforms-field-' + fieldId + ' .wpforms-richtext-wrap .mce-toolbar-grp' );
  1106. $toolbar.toggleClass( 'wpforms-field-richtext-toolbar-basic', $this.val() !== 'full' );
  1107. },
  1108. /**
  1109. * Element bindings.
  1110. *
  1111. * @since 1.0.0
  1112. */
  1113. bindUIActions: function() {
  1114. // General Panels.
  1115. app.bindUIActionsPanels();
  1116. // Fields Panel.
  1117. app.bindUIActionsFields();
  1118. // Settings Panel.
  1119. app.bindUIActionsSettings();
  1120. // Save and Exit.
  1121. app.bindUIActionsSaveExit();
  1122. // General/ global.
  1123. app.bindUIActionsGeneral();
  1124. },
  1125. //--------------------------------------------------------------------//
  1126. // General Panels
  1127. //--------------------------------------------------------------------//
  1128. /**
  1129. * Element bindings for general panel tasks.
  1130. *
  1131. * @since 1.0.0
  1132. */
  1133. bindUIActionsPanels: function() {
  1134. // Panel switching.
  1135. $builder.on('click', '#wpforms-panels-toggle button, .wpforms-panel-switch', function(e) {
  1136. e.preventDefault();
  1137. app.panelSwitch($(this).data('panel'));
  1138. });
  1139. // Panel sections switching.
  1140. $builder.on('click', '.wpforms-panel .wpforms-panel-sidebar-section', function(e) {
  1141. app.panelSectionSwitch(this, e);
  1142. });
  1143. },
  1144. /**
  1145. * Switch Panels.
  1146. *
  1147. * @since 1.0.0
  1148. * @since 1.5.9 Added `wpformsPanelSwitched` triger.
  1149. *
  1150. * @param {string} panel Panel slug.
  1151. *
  1152. * @returns {mixed} Void or false.
  1153. */
  1154. panelSwitch: function( panel ) {
  1155. var $panel = $( '#wpforms-panel-' + panel ),
  1156. $panelBtn = $( '.wpforms-panel-' + panel + '-button' );
  1157. if ( ! $panel.hasClass( 'active' ) ) {
  1158. $builder.trigger( 'wpformsPanelSwitch', [ panel ] );
  1159. if ( ! wpforms_panel_switch ) {
  1160. return false;
  1161. }
  1162. $( '#wpforms-panels-toggle' ).find( 'button' ).removeClass( 'active' );
  1163. $( '.wpforms-panel' ).removeClass( 'active' );
  1164. $panelBtn.addClass( 'active' );
  1165. $panel.addClass( 'active' );
  1166. history.replaceState( {}, null, wpf.updateQueryString( 'view', panel ) );
  1167. $builder.trigger( 'wpformsPanelSwitched', [ panel ] );
  1168. }
  1169. },
  1170. /**
  1171. * Switch Panel section.
  1172. *
  1173. * @since 1.0.0
  1174. */
  1175. panelSectionSwitch: function(el, e) {
  1176. if (e) {
  1177. e.preventDefault();
  1178. }
  1179. var $this = $(el),
  1180. $panel = $this.parent().parent(),
  1181. section = $this.data('section'),
  1182. $sectionButtons = $panel.find('.wpforms-panel-sidebar-section'),
  1183. $sectionButton = $panel.find('.wpforms-panel-sidebar-section-'+section);
  1184. if ( $this.hasClass( 'upgrade-modal' ) || $this.hasClass( 'education-modal' ) ) {
  1185. return;
  1186. }
  1187. if ( ! $sectionButton.hasClass('active') ) {
  1188. $builder.trigger('wpformsPanelSectionSwitch', section);
  1189. $sectionButtons.removeClass('active');
  1190. $sectionButton.addClass('active');
  1191. $panel.find('.wpforms-panel-content-section').hide();
  1192. $panel.find('.wpforms-panel-content-section-'+section).show();
  1193. }
  1194. },
  1195. //--------------------------------------------------------------------//
  1196. // Setup Panel
  1197. //--------------------------------------------------------------------//
  1198. /**
  1199. * Element bindings for Setup panel.
  1200. *
  1201. * @since 1.0.0
  1202. * @since 1.6.8 Deprecated.
  1203. *
  1204. * @deprecated Use `WPForms.Admin.Builder.Setup.events()` instead.
  1205. */
  1206. bindUIActionsSetup: function() {
  1207. console.warn( 'WARNING! Function "WPFormsBuilder.bindUIActionsSetup()" has been deprecated, please use the new "WPForms.Admin.Builder.Setup.events()" function instead!' );
  1208. WPForms.Admin.Builder.Setup.events();
  1209. },
  1210. /**
  1211. * Select template.
  1212. *
  1213. * @since 1.0.0
  1214. * @since 1.6.8 Deprecated.
  1215. *
  1216. * @deprecated Use `WPForms.Admin.Builder.Setup.selectTemplate()` instead.
  1217. *
  1218. * @param {object} el DOM element object.
  1219. * @param {object} e Event object.
  1220. */
  1221. templateSelect: function( el, e ) {
  1222. console.warn( 'WARNING! Function "WPFormsBuilder.templateSelect()" has been deprecated, please use the new "WPForms.Admin.Builder.Setup.selectTemplate()" function instead!' );
  1223. WPForms.Admin.Builder.Setup.selectTemplate( e );
  1224. },
  1225. //--------------------------------------------------------------------//
  1226. // Fields Panel
  1227. //--------------------------------------------------------------------//
  1228. /**
  1229. * Element bindings for Fields panel.
  1230. *
  1231. * @since 1.0.0
  1232. */
  1233. bindUIActionsFields: function() {
  1234. // Field sidebar tab toggle
  1235. $builder.on('click', '.wpforms-tab a', function(e) {
  1236. e.preventDefault();
  1237. app.fieldTabToggle($(this).parent().attr('id'));
  1238. });
  1239. // Field sidebar group toggle
  1240. $builder.on('click', '.wpforms-add-fields-heading', function(e) {
  1241. e.preventDefault();
  1242. app.fieldGroupToggle($(this), 'click');
  1243. });
  1244. // Form field preview clicking.
  1245. $builder.on( 'click', '.wpforms-field', function( e ) {
  1246. if ( app.isFieldPreviewActionsDisabled( this ) ) {
  1247. return;
  1248. }
  1249. app.fieldTabToggle( $( this ).data( 'field-id' ) );
  1250. } );
  1251. // Prevent interactions with inputs on the preview panel.
  1252. $builder.on( 'mousedown click', '.wpforms-field input, .wpforms-field select, .wpforms-field textarea', function( e ) {
  1253. e.preventDefault();
  1254. this.blur();
  1255. } );
  1256. // Field delete.
  1257. $builder.on( 'click', '.wpforms-field-delete', function( e ) {
  1258. e.preventDefault();
  1259. e.stopPropagation();
  1260. if ( app.isFieldPreviewActionsDisabled( this ) ) {
  1261. return;
  1262. }
  1263. app.fieldDelete( $( this ).parent().data( 'field-id' ) );
  1264. } );
  1265. // Field duplicate.
  1266. $builder.on( 'click', '.wpforms-field-duplicate', function( e ) {
  1267. e.preventDefault();
  1268. e.stopPropagation();
  1269. if ( app.isFieldPreviewActionsDisabled( this ) ) {
  1270. return;
  1271. }
  1272. app.fieldDuplicate( $( this ).parent().data( 'field-id' ) );
  1273. } );
  1274. // Field add
  1275. $builder.on( 'click', '.wpforms-add-fields-button', function( e ) {
  1276. e.preventDefault();
  1277. var $field = $( this );
  1278. if ( $field.hasClass( 'ui-draggable-disabled' ) ) {
  1279. return;
  1280. }
  1281. app.fieldAdd( $field.data( 'field-type' ) );
  1282. } );
  1283. // New field choices should be sortable
  1284. $builder.on('wpformsFieldAdd', function(event, id, type) {
  1285. if (type === 'select' || type === 'radio' || type === 'checkbox' || type === 'payment-multiple' || type === 'payment-checkbox' || type === 'payment-select' ) {
  1286. app.fieldChoiceSortable(type,'#wpforms-field-option-row-' + id + '-choices ul');
  1287. }
  1288. });
  1289. // Field choice add new
  1290. $builder.on('click', '.wpforms-field-option-row-choices .add', function(e) {
  1291. app.fieldChoiceAdd(e, $(this));
  1292. });
  1293. // Field choice delete
  1294. $builder.on('click', '.wpforms-field-option-row-choices .remove', function(e) {
  1295. app.fieldChoiceDelete(e, $(this));
  1296. });
  1297. // Field choices defaults - before change
  1298. $builder.on('mousedown', '.wpforms-field-option-row-choices input[type=radio]', function(e) {
  1299. var $this = $(this);
  1300. if ( $this.is(':checked') ) {
  1301. $this.attr('data-checked', '1');
  1302. } else {
  1303. $this.attr('data-checked', '0');
  1304. }
  1305. });
  1306. // Field choices defaults
  1307. $builder.on('click', '.wpforms-field-option-row-choices input[type=radio]', function(e) {
  1308. var $this = $(this),
  1309. list = $this.parent().parent();
  1310. $this.parent().parent().find('input[type=radio]').not(this).prop('checked',false);
  1311. if ( $this.attr('data-checked') === '1' ) {
  1312. $this.prop( 'checked', false );
  1313. $this.attr('data-checked', '0');
  1314. }
  1315. app.fieldChoiceUpdate(list.data('field-type'),list.data('field-id') );
  1316. });
  1317. // Field choices update preview area
  1318. $builder.on( 'change', '.wpforms-field-option-row-choices input[type=checkbox]', function( e ) {
  1319. var list = $( this ).parent().parent();
  1320. app.fieldChoiceUpdate( list.data( 'field-type' ), list.data( 'field-id' ) );
  1321. } );
  1322. // Field choices display value toggle
  1323. $builder.on('change', '.wpforms-field-option-row-show_values input', function(e) {
  1324. $(this).closest('.wpforms-field-option').find('.wpforms-field-option-row-choices ul').toggleClass('show-values');
  1325. });
  1326. // Field choices image toggle.
  1327. $builder.on( 'change', '.wpforms-field-option-row-choices_images input', function() {
  1328. var $this = $( this ),
  1329. $optionRow = $this.closest( '.wpforms-field-option-row' ),
  1330. fieldID = $optionRow.data( 'field-id' ),
  1331. $fieldOptions = $( '#wpforms-field-option-' + fieldID ),
  1332. checked = $this.is( ':checked' ),
  1333. type = $fieldOptions.find( '.wpforms-field-option-hidden-type' ).val();
  1334. $optionRow.find( '.wpforms-alert' ).toggleClass( 'wpforms-hidden' );
  1335. $fieldOptions.find( '.wpforms-field-option-row-choices ul' ).toggleClass( 'show-images' );
  1336. $fieldOptions.find( '.wpforms-field-option-row-choices_images_style' ).toggleClass( 'wpforms-hidden' );
  1337. if ( checked ) {
  1338. $( '#wpforms-field-option-' + fieldID + '-input_columns' ).val( 'inline' ).trigger( 'change' );
  1339. } else {
  1340. $( '#wpforms-field-option-' + fieldID + '-input_columns' ).val( '' ).trigger( 'change' );
  1341. }
  1342. app.fieldChoiceUpdate( type, fieldID );
  1343. } );
  1344. // Field choices image upload add/remove image.
  1345. $builder.on( 'wpformsImageUploadAdd wpformsImageUploadRemove', function( event, $this, $container ) {
  1346. var $list = $container.closest( '.choices-list' ),
  1347. fieldID = $list.data( 'field-id' ),
  1348. type = $list.data( 'field-type' );
  1349. app.fieldChoiceUpdate( type, fieldID );
  1350. } );
  1351. // Field choices image style toggle.
  1352. $builder.on( 'change', '.wpforms-field-option-row-choices_images_style select', function() {
  1353. var fieldID = $( this ).parent().data( 'field-id' ),
  1354. type = $( '#wpforms-field-option-'+fieldID ).find( '.wpforms-field-option-hidden-type' ).val();
  1355. app.fieldChoiceUpdate( type, fieldID );
  1356. } );
  1357. // Updates field choices text in almost real time.
  1358. $builder.on( 'keyup', '.wpforms-field-option-row-choices input.label, .wpforms-field-option-row-choices input.value', function( e ) {
  1359. var $list = $( this ).parent().parent();
  1360. app.fieldChoiceUpdate( $list.data( 'field-type' ), $list.data( 'field-id' ) );
  1361. } );
  1362. // Field Choices Bulk Add
  1363. $builder.on('click', '.toggle-bulk-add-display', function(e) {
  1364. e.preventDefault();
  1365. app.fieldChoiceBulkAddToggle(this);
  1366. });
  1367. $builder.on('click', '.toggle-bulk-add-presets', function(e) {
  1368. e.preventDefault();
  1369. var $presetList = $(this).closest('.bulk-add-display').find('ul');
  1370. if ( $presetList.css('display') === 'block' ) {
  1371. $(this).text(wpforms_builder.bulk_add_presets_show);
  1372. } else {
  1373. $(this).text(wpforms_builder.bulk_add_presets_hide);
  1374. }
  1375. $presetList.stop().slideToggle();
  1376. });
  1377. $builder.on('click', '.bulk-add-preset-insert', function(e) {
  1378. e.preventDefault();
  1379. var $this = $(this),
  1380. preset = $this.data('preset'),
  1381. $container = $this.closest('.bulk-add-display'),
  1382. $presetList = $container.find('ul'),
  1383. $presetToggle = $container.find('.toggle-bulk-add-presets'),
  1384. $textarea = $container.find('textarea');
  1385. $textarea.val('');
  1386. $textarea.insertAtCaret(wpforms_preset_choices[preset].choices.join("\n"));
  1387. $presetToggle.text(wpforms_builder.bulk_add_presets_show);
  1388. $presetList.slideUp();
  1389. });
  1390. $builder.on('click', '.bulk-add-insert', function(e) {
  1391. e.preventDefault();
  1392. app.fieldChoiceBulkAddInsert(this);
  1393. });
  1394. // Field Options group tabs.
  1395. $builder.on( 'click', '.wpforms-field-option-group-toggle:not(.education-modal)', function( e ) {
  1396. e.preventDefault();
  1397. var $group = $( this ).closest( '.wpforms-field-option-group' );
  1398. $group.siblings( '.wpforms-field-option-group' ).removeClass( 'active' );
  1399. $group.addClass( 'active' );
  1400. } );
  1401. // Display toggle for Address field hide address line 2 option.
  1402. $builder.on( 'change', '.wpforms-field-option-address input.wpforms-subfield-hide', function( e ) {
  1403. var $optionRow = $( this ).closest( '.wpforms-field-option-row' ),
  1404. id = $optionRow.data( 'field-id' ),
  1405. subfield = $optionRow.data( 'subfield' );
  1406. $( '#wpforms-field-' + id ).find( '.wpforms-' + subfield ).toggleClass( 'wpforms-hide' );
  1407. } );
  1408. // Real-time updates for "Show Label" field option
  1409. $builder.on( 'input', '.wpforms-field-option-row-label input, .wpforms-field-option-row-name input', function( e ) {
  1410. var $this = $( this ),
  1411. value = $this.val(),
  1412. id = $this.parent().data( 'field-id' ),
  1413. $preview = $( '#wpforms-field-' + id ),
  1414. type = $preview.data( 'field-type' ),
  1415. showClass = value.length === 0;
  1416. // Do not modify label of the HTML field.
  1417. if ( type === 'html' ) {
  1418. showClass = false;
  1419. }
  1420. if ( showClass ) {
  1421. value = wpforms_builder.empty_label;
  1422. }
  1423. $preview.toggleClass( 'label_empty', showClass ).find( '.label-title .text' ).text( value );
  1424. } );
  1425. // Real-time updates for "Description" field option
  1426. $builder.on( 'input', '.wpforms-field-option-row-description textarea', function() {
  1427. var $this = $( this ),
  1428. value = wpf.sanitizeHTML( $this.val() ),
  1429. id = $this.parent().data( 'field-id' ),
  1430. $desc = $( '#wpforms-field-'+id ).find( '.description' );
  1431. app.updateDescription( $desc, value );
  1432. });
  1433. // Real-time updates for "Required" field option
  1434. $builder.on( 'change', '.wpforms-field-option-row-required input', function( e ) {
  1435. var id = $( this ).closest( '.wpforms-field-option-row' ).data( 'field-id' );
  1436. $( '#wpforms-field-' + id ).toggleClass( 'required' );
  1437. } );
  1438. // Real-time updates for "Confirmation" field option
  1439. $builder.on( 'change', '.wpforms-field-option-row-confirmation input', function( e ) {
  1440. var id = $( this ).closest( '.wpforms-field-option-row' ).data( 'field-id' );
  1441. $( '#wpforms-field-' + id ).find( '.wpforms-confirm' ).toggleClass( 'wpforms-confirm-enabled wpforms-confirm-disabled' );
  1442. $( '#wpforms-field-option-' + id ).toggleClass( 'wpforms-confirm-enabled wpforms-confirm-disabled' );
  1443. } );
  1444. // Real-time updates for "Filter" field option
  1445. $builder.on( 'change', '.wpforms-field-option-row-filter_type select', function() {
  1446. var id = $( this ).parent().data( 'field-id' ),
  1447. $toggledField = $( '#wpforms-field-option-' + id );
  1448. if ( $( this ).val() ) {
  1449. $toggledField.removeClass( 'wpforms-filter-allowlist' );
  1450. $toggledField.removeClass( 'wpforms-filter-denylist' );
  1451. $toggledField.addClass( 'wpforms-filter-' + $( this ).val() );
  1452. } else {
  1453. $toggledField.removeClass( 'wpforms-filter-allowlist' );
  1454. $toggledField.removeClass( 'wpforms-filter-denylist' );
  1455. }
  1456. } );
  1457. $builder.on( 'focusout', '.wpforms-field-option-row-allowlist textarea,.wpforms-field-option-row-denylist textarea', function() {
  1458. var $field = $( this );
  1459. $.get(
  1460. wpforms_builder.ajax_url,
  1461. {
  1462. nonce: wpforms_builder.nonce,
  1463. content: $field.val(),
  1464. action: 'wpforms_sanitize_restricted_rules',
  1465. },
  1466. function( res ) {
  1467. if ( res.success ) {
  1468. $field.val( res.data );
  1469. }
  1470. }
  1471. );
  1472. } );
  1473. // Real-time updates for "Size" field option
  1474. $builder.on('change', '.wpforms-field-option-row-size select', function(e) {
  1475. var $this = $(this),
  1476. value = $this.val(),
  1477. id = $this.parent().data('field-id');
  1478. $('#wpforms-field-'+id).removeClass('size-small size-medium size-large').addClass('size-'+value);
  1479. });
  1480. // Real-time updates for "Placeholder" field option.
  1481. $builder.on( 'input', '.wpforms-field-option-row-placeholder input', function() {
  1482. var $this = $( this ),
  1483. value = wpf.sanitizeHTML( $this.val() ),
  1484. id = $this.parent().data( 'field-id' ),
  1485. $primary = $( '#wpforms-field-' + id + ' .primary-input' );
  1486. // Set the placeholder value for `input` fields.
  1487. if ( ! $primary.is( 'select' ) ) {
  1488. $primary.prop( 'placeholder', value );
  1489. return;
  1490. }
  1491. // Modern select style.
  1492. if ( app.dropdownField.helpers.isModernSelect( $primary ) ) {
  1493. var choicejsInstance = app.dropdownField.helpers.getInstance( $primary );
  1494. // Additional case for multiple select.
  1495. if ( $primary.prop( 'multiple' ) ) {
  1496. $( choicejsInstance.input.element ).prop( 'placeholder', value );
  1497. } else {
  1498. choicejsInstance.setChoiceByValue( '' );
  1499. $primary.closest( '.choices' ).find( '.choices__inner .choices__placeholder' ).text( value );
  1500. var isDynamicChoices = $( '#wpforms-field-option-' + id + '-dynamic_choices' ).val();
  1501. // We need to re-initialize modern dropdown to properly determine and update placeholder.
  1502. app.dropdownField.helpers.update( id, isDynamicChoices );
  1503. }
  1504. return;
  1505. }
  1506. var $placeholder = $primary.find( '.placeholder' );
  1507. // Classic select style.
  1508. if ( ! value.length && $placeholder.length ) {
  1509. $placeholder.remove();
  1510. } else {
  1511. if ( $placeholder.length ) {
  1512. $placeholder.text( value );
  1513. } else {
  1514. $primary.prepend( '<option value="" class="placeholder">' + value + '</option>' );
  1515. }
  1516. $primary.find( '.placeholder' ).prop( 'selected', ! $primary.prop( 'multiple' ) );
  1517. }
  1518. } );
  1519. // Real-time updates for "Confirmation Placeholder" field option
  1520. $builder.on('input', '.wpforms-field-option-row-confirmation_placeholder input', function(e) {
  1521. var $this = $(this),
  1522. value = $this.val(),
  1523. id = $this.parent().data('field-id');
  1524. $('#wpforms-field-'+id).find('.secondary-input').attr('placeholder', value);
  1525. });
  1526. // Real-time updates for "Hide Label" field option.
  1527. $builder.on( 'change', '.wpforms-field-option-row-label_hide input', function( e ) {
  1528. var id = $( this ).closest( '.wpforms-field-option-row' ).data( 'field-id' );
  1529. $( '#wpforms-field-' + id ).toggleClass( 'label_hide' );
  1530. } );
  1531. // Real-time updates for Sub Label visbility field option.
  1532. $builder.on( 'change', '.wpforms-field-option-row-sublabel_hide input', function( e ) {
  1533. var id = $( this ).closest( '.wpforms-field-option-row' ).data( 'field-id' );
  1534. $( '#wpforms-field-' + id ).toggleClass( 'sublabel_hide' );
  1535. } );
  1536. // Real-time updates for Date/Time and Name "Format" option
  1537. $builder.on('change', '.wpforms-field-option-row-format select', function(e) {
  1538. var $this = $(this),
  1539. value = $this.val(),
  1540. id = $this.parent().data('field-id');
  1541. $('#wpforms-field-'+id).find('.format-selected').removeClass().addClass('format-selected format-selected-'+value);
  1542. $('#wpforms-field-option-'+id).find('.format-selected').removeClass().addClass('format-selected format-selected-'+value);
  1543. });
  1544. // Real-time updates specific for Address "Scheme" option
  1545. $builder.on('change', '.wpforms-field-option-row-scheme select', function(e) {
  1546. var $this = $(this),
  1547. value = $this.val(),
  1548. id = $this.parent().data('field-id'),
  1549. $field = $('#wpforms-field-'+id);
  1550. $field.find('.wpforms-address-scheme').addClass('wpforms-hide');
  1551. $field.find('.wpforms-address-scheme-'+value).removeClass('wpforms-hide');
  1552. if ( $field.find('.wpforms-address-scheme-'+value+' .wpforms-country' ).children().length == 0 ) {
  1553. $('#wpforms-field-option-'+id).find('.wpforms-field-option-row-country').addClass('wpforms-hidden');
  1554. } else {
  1555. $('#wpforms-field-option-'+id).find('.wpforms-field-option-row-country').removeClass('wpforms-hidden');
  1556. }
  1557. });
  1558. // Real-time updates for Address, Date/Time, and Name "Placeholder" field options
  1559. $builder.on( 'input', '.wpforms-field-option .format-selected input.placeholder, .wpforms-field-option-address input.placeholder', function( e ) {
  1560. var $this = $( this ),
  1561. value = $this.val(),
  1562. $fieldOptionRow = $this.closest( '.wpforms-field-option-row' ),
  1563. id = $fieldOptionRow.data( 'field-id' ),
  1564. subfield = $fieldOptionRow.data( 'subfield' );
  1565. $( '#wpforms-field-' + id ).find( '.wpforms-' + subfield + ' input' ).attr( 'placeholder', value );
  1566. } );
  1567. // Real-time updates for Date/Time date type
  1568. $builder.on( 'change', '.wpforms-field-option-row-date .type select', function( e ) {
  1569. var $this = $( this ),
  1570. value = $this.val(),
  1571. id = $( this ).closest( '.wpforms-field-option-row' ).data( 'field-id' ),
  1572. addClass = value === 'datepicker' ? 'wpforms-date-type-datepicker' : 'wpforms-date-type-dropdown',
  1573. removeClass = value === 'datepicker' ? 'wpforms-date-type-dropdown' : 'wpforms-date-type-datepicker';
  1574. $( '#wpforms-field-' + id ).find( '.wpforms-date' ).addClass( addClass ).removeClass( removeClass );
  1575. $( '#wpforms-field-option-' + id ).addClass( addClass ).removeClass( removeClass );
  1576. var $limitDays = $this.closest( '.wpforms-field-option-group-advanced' )
  1577. .find( '.wpforms-field-option-row-date_limit_days, .wpforms-field-option-row-date_limit_days_options, .wpforms-field-option-row-date_disable_past_dates' ),
  1578. $limitDaysOptions = $( '#wpforms-field-option-row-' + id + '-date_limit_days_options' );
  1579. if ( value === 'dropdown' ) {
  1580. var $dateSelect = $( '#wpforms-field-option-' + id + '-date_format' );
  1581. if ( $dateSelect.find( 'option:selected' ).hasClass( 'datepicker-only' ) ) {
  1582. $dateSelect.prop( 'selectedIndex', 0 ).trigger( 'change' );
  1583. }
  1584. $limitDays.hide();
  1585. } else {
  1586. $limitDays.show();
  1587. $( '#wpforms-field-option-' + id + '-date_limit_days' ).is( ':checked' ) ?
  1588. $limitDaysOptions.show() : $limitDaysOptions.hide();
  1589. }
  1590. } );
  1591. // Real-time updates for Date/Time date select format
  1592. $builder.on( 'change', '.wpforms-field-option-row-date .format select', function( e ) {
  1593. var $this = $( this ),
  1594. value = $this.val(),
  1595. id = $( this ).closest( '.wpforms-field-option-row' ).data( 'field-id' ),
  1596. $field = $( '#wpforms-field-' + id );
  1597. if ( value === 'm/d/Y' ) {
  1598. $field.find( '.wpforms-date-dropdown .first option' ).text( wpforms_builder.date_select_month );
  1599. $field.find( '.wpforms-date-dropdown .second option' ).text( wpforms_builder.date_select_day );
  1600. } else if ( value === 'd/m/Y' ) {
  1601. $field.find( '.wpforms-date-dropdown .first option' ).text( wpforms_builder.date_select_day );
  1602. $field.find( '.wpforms-date-dropdown .second option' ).text( wpforms_builder.date_select_month );
  1603. }
  1604. } );
  1605. // Real-time updates for Date/Time time select format
  1606. $builder.on( 'change', '.wpforms-field-option-row-time .format select', function( e ) {
  1607. var $this = $( this ),
  1608. id = $( this ).closest( '.wpforms-field-option-row' ).data( 'field-id' ),
  1609. options = '',
  1610. hh;
  1611. // Determine time format type.
  1612. // If the format contains `g` or `h`, then this is 12 hours format, otherwise 24 hours.
  1613. var format = $this.val().match( /[gh]/ ) ? 12 : 24;
  1614. // Generate new set of hours options.
  1615. for ( var i = 0; i < format; i++ ) {
  1616. hh = i < 10 ? '0' + i : i;
  1617. options += '<option value="{hh}">{hh}</option>'.replace( /{hh}/g, hh );
  1618. }
  1619. _.forEach( [ 'start', 'end' ], function( field ) {
  1620. var $hour = $builder.find( '#wpforms-field-option-' + id + '-time_limit_hours_' + field + '_hour' ),
  1621. $ampm = $builder.find( '#wpforms-field-option-' + id + '-time_limit_hours_' + field + '_ampm' ),
  1622. hourValue = parseInt( $hour.val(), 10 ),
  1623. ampmValue = $ampm.val();
  1624. if ( format === 24 ) {
  1625. hourValue = ampmValue === 'pm' ? hourValue + 12 : hourValue;
  1626. } else {
  1627. ampmValue = hourValue > 12 ? 'pm' : 'am';
  1628. hourValue = hourValue > 12 ? hourValue - 12 : hourValue;
  1629. }
  1630. hourValue = hourValue < 10 ? '0' + hourValue : hourValue;
  1631. $hour.html( options ).val( hourValue );
  1632. $ampm.toggleClass( 'wpforms-hidden-strict', format === 24 ).val( ampmValue );
  1633. $ampm.nextAll( 'div' ).toggleClass( 'wpforms-hidden-strict', format === 12 );
  1634. } );
  1635. } );
  1636. // Consider the field active when a disabled nav button is clicked
  1637. $builder.on('click', '.wpforms-pagebreak-button', function(e) {
  1638. e.preventDefault();
  1639. $(this).closest('.wpforms-field').trigger('click');
  1640. });
  1641. /*
  1642. * Pagebreak field.
  1643. */
  1644. app.fieldPageBreakInitDisplayPrevious( $builder.find( '.wpforms-field-pagebreak.wpforms-pagebreak-normal:first' ) );
  1645. $builder
  1646. .on( 'input', '.wpforms-field-option-row-next input', function( e ) {
  1647. // Real-time updates for "Next" pagebreak field option.
  1648. var $this = $( this ),
  1649. value = $this.val(),
  1650. $next = $( '#wpforms-field-' + $this.parent().data( 'field-id' ) ).find( '.wpforms-pagebreak-next' );
  1651. if ( value ) {
  1652. $next.css( 'display', 'inline-block' ).text( value );
  1653. } else {
  1654. $next.css( 'display', 'none' ).empty();
  1655. }
  1656. } )
  1657. .on( 'input', '.wpforms-field-option-row-prev input', function( e ) {
  1658. // Real-time updates for "Prev" pagebreak field option.
  1659. var $this = $( this ),
  1660. value = $this.val().trim(),
  1661. $field = $( '#wpforms-field-' + $this.parent().data( 'field-id' ) ),
  1662. $prevBtn = $field.find( '.wpforms-pagebreak-prev' );
  1663. if ( value && $field.prevAll( '.wpforms-field-pagebreak.wpforms-pagebreak-normal' ).length > 0 ) {
  1664. $prevBtn.removeClass( 'wpforms-hidden' ).text( value );
  1665. } else {
  1666. $prevBtn.addClass( 'wpforms-hidden' ).empty();
  1667. }
  1668. } )
  1669. .on( 'change', '.wpforms-field-option-row-prev_toggle input', function( e ) {
  1670. // Real-time updates for "Display Previous" pagebreak field option.
  1671. var $input = $( this ),
  1672. $wrapper = $input.closest( '.wpforms-field-option-row-prev_toggle' ),
  1673. $prev = $input.closest( '.wpforms-field-option-group-inner' ).find( '.wpforms-field-option-row-prev' ),
  1674. $prevLabel = $prev.find( 'input' ),
  1675. $prevBtn = $( '#wpforms-field-' + $input.closest( '.wpforms-field-option' ).data( 'field-id' ) ).find( '.wpforms-pagebreak-prev' );
  1676. if ( $wrapper.hasClass( 'wpforms-entry-preview-block' ) ) {
  1677. return;
  1678. }
  1679. $prev.toggleClass( 'wpforms-hidden', ! $input.prop( 'checked' ) );
  1680. $prevBtn.toggleClass( 'wpforms-hidden', ! $input.prop( 'checked' ) );
  1681. if ( $input.prop( 'checked' ) && ! $prevLabel.val() ) {
  1682. var message = $prevLabel.data( 'last-value' );
  1683. message = message && message.trim() ? message.trim() : wpforms_builder.previous;
  1684. $prevLabel.val( message );
  1685. }
  1686. // Backward compatibility for forms that were created before the toggle was added.
  1687. if ( ! $input.prop( 'checked' ) ) {
  1688. $prevLabel.data( 'last-value', $prevLabel.val() );
  1689. $prevLabel.val( '' );
  1690. }
  1691. $prevLabel.trigger( 'input' );
  1692. } )
  1693. .on( 'wpformsFieldAdd', app.fieldPagebreakAdd )
  1694. .on( 'wpformsFieldDelete', app.fieldPagebreakDelete )
  1695. .on( 'wpformsBeforeFieldDelete', app.fieldEntryPreviewDelete );
  1696. // Update Display Previous option visibility for all Pagebreak fields.
  1697. $builder.on( 'wpformsFieldMove wpformsFieldAdd wpformsFieldDelete', function( e ) {
  1698. $builder.find( '.wpforms-field-pagebreak.wpforms-pagebreak-normal' ).each( function( i ) {
  1699. app.fieldPageBreakInitDisplayPrevious( $( this ) );
  1700. } );
  1701. } );
  1702. // Real-time updates for "Page Title" pagebreak field option
  1703. $builder.on( 'input', '.wpforms-field-option-row-title input', function( e ) {
  1704. var $this = $( this ),
  1705. value = $this.val(),
  1706. id = $this.parent().data( 'field-id' );
  1707. if ( value ) {
  1708. $( '#wpforms-field-' + id ).find( '.wpforms-pagebreak-title' ).text( '(' + value + ')' );
  1709. } else {
  1710. $( '#wpforms-field-' + id ).find( '.wpforms-pagebreak-title' ).empty();
  1711. }
  1712. } );
  1713. // Real-time updates for "Page Navigation Alignment" pagebreak field option
  1714. $builder.on( 'change', '.wpforms-field-option-row-nav_align select', function( e ) {
  1715. var $this = $( this ),
  1716. value = $this.val();
  1717. if ( ! value ) {
  1718. value = 'center';
  1719. }
  1720. $( '.wpforms-pagebreak-buttons' )
  1721. .removeClass( 'wpforms-pagebreak-buttons-center wpforms-pagebreak-buttons-left wpforms-pagebreak-buttons-right wpforms-pagebreak-buttons-split' )
  1722. .addClass( 'wpforms-pagebreak-buttons-' + value );
  1723. } );
  1724. // Real-time updates for Single Item field "Item Price" option.
  1725. $builder.on( 'input', '.wpforms-field-option-row-price input', function( e ) {
  1726. var $this = $( this ),
  1727. value = $this.val(),
  1728. id = $this.parent().data( 'field-id' ),
  1729. sanitized = wpf.amountSanitize( value ),
  1730. formatted = wpf.amountFormat( sanitized ),
  1731. singleItem;
  1732. if ( wpforms_builder.currency_symbol_pos === 'right' ) {
  1733. singleItem = formatted + ' ' + wpforms_builder.currency_symbol;
  1734. } else {
  1735. singleItem = wpforms_builder.currency_symbol + ' ' + formatted;
  1736. }
  1737. $( '#wpforms-field-' + id ).find( '.primary-input' ).val( formatted );
  1738. $( '#wpforms-field-' + id ).find( '.price' ).text( singleItem );
  1739. } );
  1740. // Real-time updates for payment CC icons
  1741. $builder.on( 'change', '.wpforms-field-option-credit-card .payment-icons input', function( e ) {
  1742. var $this = $( this ),
  1743. card = $this.data( 'card' ),
  1744. id = $this.parent().data( 'field-id' );
  1745. $( '#wpforms-field-' + id ).find( 'img.icon-' + card ).toggleClass( 'card_hide' );
  1746. } );
  1747. // Generic updates for various additional placeholder fields
  1748. $builder.on('input', '.wpforms-field-option input.placeholder-update', function(e) {
  1749. var $this = $(this),
  1750. value = $this.val(),
  1751. id = $this.data('field-id'),
  1752. subfield = $this.data('subfield');
  1753. $('#wpforms-field-'+id).find('.wpforms-'+ subfield+' input' ).attr('placeholder', value);
  1754. });
  1755. // Toggle Choice Layout advanced field option.
  1756. $builder.on( 'change', '.wpforms-field-option-row-input_columns select', function() {
  1757. var $this = $( this ),
  1758. value = $this.val(),
  1759. cls = '',
  1760. id = $this.parent().data( 'field-id' );
  1761. if ( value === '2' ) {
  1762. cls = 'wpforms-list-2-columns';
  1763. } else if ( value === '3' ) {
  1764. cls = 'wpforms-list-3-columns';
  1765. } else if ( value === 'inline' ) {
  1766. cls = 'wpforms-list-inline';
  1767. }
  1768. $( '#wpforms-field-' + id ).removeClass( 'wpforms-list-2-columns wpforms-list-3-columns wpforms-list-inline' ).addClass( cls );
  1769. });
  1770. // Toggle the toggle field.
  1771. $builder.on( 'change', '.wpforms-field-option-row .wpforms-toggle-control input', function( e ) {
  1772. var $check = $( this ),
  1773. $control = $check.closest( '.wpforms-toggle-control' ),
  1774. $status = $control.find( '.wpforms-toggle-control-status' ),
  1775. state = $check.is( ':checked' ) ? 'on' : 'off';
  1776. $status.html( $status.data( state ) );
  1777. } );
  1778. // Real-time updates for "Dynamic Choices" field option, for Dropdown,
  1779. // Checkboxes, and Multiple choice fields
  1780. $builder.on('change', '.wpforms-field-option-row-dynamic_choices select', function(e) {
  1781. app.fieldDynamicChoiceToggle($(this));
  1782. });
  1783. // Real-time updates for "Dynamic [type] Source" field option, for Dropdown,
  1784. // Checkboxes, and Multiple choice fields
  1785. $builder.on('change', '.wpforms-field-option-row-dynamic_taxonomy select, .wpforms-field-option-row-dynamic_post_type select', function(e) {
  1786. app.fieldDynamicChoiceSource($(this));
  1787. });
  1788. // Toggle Layout selector
  1789. $builder.on('click', '.toggle-layout-selector-display', function(e) {
  1790. e.preventDefault();
  1791. app.fieldLayoutSelectorToggle(this);
  1792. });
  1793. $builder.on('click', '.layout-selector-display-layout', function(e) {
  1794. e.preventDefault();
  1795. app.fieldLayoutSelectorLayout(this);
  1796. });
  1797. $builder.on('click', '.layout-selector-display-columns span', function(e) {
  1798. e.preventDefault();
  1799. app.fieldLayoutSelectorInsert(this);
  1800. });
  1801. // Real-time updates for Rating field scale option.
  1802. $( document ).on( 'change', '.wpforms-field-option-row-scale select', function() {
  1803. var $this = $( this ),
  1804. value = $this.val(),
  1805. id = $this.parent().data( 'field-id' ),
  1806. $icons = $( '#wpforms-field-'+id +' .rating-icon' ),
  1807. x = 1;
  1808. $icons.each( function( index ) {
  1809. if ( x <= value ) {
  1810. $( this ).show();
  1811. } else {
  1812. $( this ).hide();
  1813. }
  1814. x++;
  1815. });
  1816. });
  1817. // Real-time updates for Rating field icon option.
  1818. $( document ).on( 'change', '.wpforms-field-option-row-icon select', function() {
  1819. var $this = $( this ),
  1820. value = $this.val(),
  1821. id = $this.parent().data( 'field-id' ),
  1822. $icons = $( '#wpforms-field-'+id +' .rating-icon' ),
  1823. iconClass = 'fa-star';
  1824. if ( 'heart' === value ) {
  1825. iconClass = 'fa-heart';
  1826. } else if ( 'thumb' === value ) {
  1827. iconClass = 'fa-thumbs-up';
  1828. } else if ( 'smiley' === value ) {
  1829. iconClass = 'fa-smile-o';
  1830. }
  1831. $icons.removeClass( 'fa-star fa-heart fa-thumbs-up fa-smile-o' ).addClass( iconClass );
  1832. });
  1833. // Real-time updates for Rating field icon size option.
  1834. $( document ).on( 'change', '.wpforms-field-option-row-icon_size select', function() {
  1835. var $this = $( this ),
  1836. value = $this.val(),
  1837. id = $this.parent().data( 'field-id' ),
  1838. $icons = $( '#wpforms-field-'+id +' .rating-icon' );
  1839. fontSize = '28';
  1840. if ( 'small' === value ) {
  1841. fontSize = '18';
  1842. } else if ( 'large' === value ) {
  1843. fontSize = '38';
  1844. }
  1845. $icons.css( 'font-size', fontSize + 'px' );
  1846. });
  1847. // Real-time updates for Rating field icon color option.
  1848. $( document ).on( 'input', '.wpforms-field-option-row-icon_color input.wpforms-color-picker', function() {
  1849. var $this = $( this ),
  1850. value = $this.val(),
  1851. id = $this.closest( '.wpforms-field-option-row' ).data( 'field-id' ),
  1852. $icons = $( '#wpforms-field-' + id + ' > i.fa' );
  1853. $icons.css( 'color', value );
  1854. } );
  1855. // Real-time updates for Checkbox field Disclaimer option.
  1856. $( document ).on( 'change', '.wpforms-field-option-row-disclaimer_format input', function() {
  1857. var $this = $( this ),
  1858. id = $this.closest( '.wpforms-field-option-row' ).data( 'field-id' ),
  1859. $desc = $( '#wpforms-field-' + id + ' .description' );
  1860. $desc.toggleClass( 'disclaimer' );
  1861. } );
  1862. $builder.on(
  1863. 'change',
  1864. '.wpforms-field-option-row-limit_enabled input',
  1865. function( event ) {
  1866. app.updateTextFieldsLimitControls( $( event.target ).closest( '.wpforms-field-option-row-limit_enabled' ).data().fieldId, event.target.checked );
  1867. }
  1868. );
  1869. $builder.on(
  1870. 'change',
  1871. '.wpforms-field-option-row-password-strength input',
  1872. function( event ) {
  1873. app.updatePasswordStrengthControls( $( event.target ).parents( '.wpforms-field-option-row-password-strength' ).data().fieldId, event.target.checked );
  1874. }
  1875. );
  1876. $builder.on(
  1877. 'change',
  1878. '.wpforms-field-option-richtext .wpforms-field-option-row-media_enabled input',
  1879. app.updateRichTextMediaFieldsLimitControls
  1880. );
  1881. $builder.on(
  1882. 'change',
  1883. '.wpforms-field-option-richtext .wpforms-field-option-row-style select',
  1884. app.updateRichTextStylePreview
  1885. );
  1886. // File uploader - change style.
  1887. $builder
  1888. .on(
  1889. 'change',
  1890. '.wpforms-field-option-file-upload .wpforms-field-option-row-style select, .wpforms-field-option-file-upload .wpforms-field-option-row-max_file_number input',
  1891. function( event ) {
  1892. app.fieldFileUploadPreviewUpdate( event.target );
  1893. }
  1894. );
  1895. // Real-time updates for Number Slider field.
  1896. app.numberSliderEvents( $builder );
  1897. // Hide image choices if dynamic choices is not off.
  1898. app.fieldDynamicChoiceToggleImageChoices();
  1899. // Real-time updates for Payment field's 'Show price after item label' option.
  1900. $builder.on( 'change', '.wpforms-field-option-row-show_price_after_labels input', function( e ) {
  1901. var $input = $( this ),
  1902. $list = $input.closest( '.wpforms-field-option-group-basic' ).find( '.wpforms-field-option-row-choices .choices-list' );
  1903. app.fieldChoiceUpdate( $list.data( 'field-type' ), $list.data( 'field-id' ) );
  1904. } );
  1905. $builder
  1906. .on( 'input', '.wpforms-field-option-row-preview-notice textarea', app.updatePreviewNotice )
  1907. .on( 'change', '.wpforms-field-option-row-preview-notice-enable input', app.toggleEntryPreviewNotice )
  1908. .on( 'wpformsFieldAdd', app.maybeLockEntryPreviewGroupOnAdd )
  1909. .on( 'wpformsFieldMove', app.maybeLockEntryPreviewGroupOnMove )
  1910. .on( 'click', '.wpforms-entry-preview-block', app.entryPreviewBlockField );
  1911. app.defaultStateEntryPreviewNotice();
  1912. },
  1913. /**
  1914. * Determine if the field is disabled for selection/duplication/deletion.
  1915. *
  1916. * @since 1.7.1
  1917. *
  1918. * @param {mixed} el DOM element or jQuery object of some container on the field preview.
  1919. *
  1920. * @returns {bool} True if actions are disabled.
  1921. */
  1922. isFieldPreviewActionsDisabled: function( el ) {
  1923. return $( el ).closest( '.wpforms-field-wrap' ).hasClass( 'ui-sortable-disabled' );
  1924. },
  1925. /**
  1926. * Toggle field group visibility in the field sidebar.
  1927. *
  1928. * @since 1.0.0
  1929. *
  1930. * @param {mixed} el DOM element or jQuery object.
  1931. * @param {string} action Action.
  1932. */
  1933. fieldGroupToggle: function( el, action ) {
  1934. var $this = $( el ),
  1935. $buttons = $this.next( '.wpforms-add-fields-buttons' ),
  1936. $group = $buttons.parent(),
  1937. $icon = $this.find( 'i' ),
  1938. groupName = $this.data( 'group' ),
  1939. cookieName = 'wpforms_field_group_' + groupName;
  1940. if ( action === 'click' ) {
  1941. if ( $group.hasClass( 'wpforms-closed' ) ) {
  1942. wpCookies.remove( cookieName );
  1943. } else {
  1944. wpCookies.set( cookieName, 'true', 2592000 ); // 1 month
  1945. }
  1946. $icon.toggleClass( 'wpforms-angle-right' );
  1947. $buttons.stop().slideToggle( '', function() {
  1948. $group.toggleClass( 'wpforms-closed' );
  1949. } );
  1950. return;
  1951. }
  1952. if ( action === 'load' ) {
  1953. $buttons = $this.find( '.wpforms-add-fields-buttons' );
  1954. $icon = $this.find( '.wpforms-add-fields-heading i' );
  1955. groupName = $this.find( '.wpforms-add-fields-heading' ).data( 'group' );
  1956. cookieName = 'wpforms_field_group_' + groupName;
  1957. if ( wpCookies.get( cookieName ) === 'true' ) {
  1958. $icon.toggleClass( 'wpforms-angle-right' );
  1959. $buttons.hide();
  1960. $this.toggleClass( 'wpforms-closed' );
  1961. }
  1962. }
  1963. },
  1964. /**
  1965. * Update description.
  1966. *
  1967. * @since 1.6.9
  1968. *
  1969. * @param {jQuery} $el Element.
  1970. * @param {string} value Value.
  1971. */
  1972. updateDescription: function( $el, value ) {
  1973. if ( $el.hasClass( 'nl2br' ) ) {
  1974. value = value.replace( /\n/g, '<br>' );
  1975. }
  1976. $el.html( value );
  1977. },
  1978. /**
  1979. * Set default state for the entry preview notice field.
  1980. *
  1981. * @since 1.6.9
  1982. */
  1983. defaultStateEntryPreviewNotice: function() {
  1984. $( '.wpforms-field-option-row-preview-notice-enable input' ).each( function() {
  1985. $( this ).trigger( 'change' );
  1986. } );
  1987. },
  1988. /**
  1989. * Update a preview notice for the field preview.
  1990. *
  1991. * @since 1.6.9
  1992. */
  1993. updatePreviewNotice: function() {
  1994. var $this = $( this ),
  1995. value = wpf.sanitizeHTML( $this.val() ).trim(),
  1996. id = $this.parent().data( 'field-id' ),
  1997. $field = $( '#wpforms-field-' + id ).find( '.wpforms-entry-preview-notice' );
  1998. value = value ? value : wpforms_builder.entry_preview_default_notice;
  1999. app.updateDescription( $field, value );
  2000. },
  2001. /**
  2002. * Show/hide entry preview notice for the field preview.
  2003. *
  2004. * @since 1.6.9
  2005. */
  2006. toggleEntryPreviewNotice: function() {
  2007. var $this = $( this ),
  2008. id = $this.closest( '.wpforms-field-option' ).data( 'field-id' ),
  2009. $field = $( '#wpforms-field-' + id ),
  2010. $noticeField = $( '#wpforms-field-option-' + id + ' .wpforms-field-option-row-preview-notice' ),
  2011. $notice = $field.find( '.wpforms-entry-preview-notice' ),
  2012. $defaultNotice = $field.find( '.wpforms-alert-info' );
  2013. if ( $this.is( ':checked' ) ) {
  2014. $defaultNotice.hide();
  2015. $notice.show();
  2016. $noticeField.show();
  2017. return;
  2018. }
  2019. $noticeField.hide();
  2020. $notice.hide();
  2021. $defaultNotice.show();
  2022. },
  2023. /**
  2024. * Delete a field.
  2025. *
  2026. * @param {int} id Field ID.
  2027. *
  2028. * @since 1.0.0
  2029. * @since 1.6.9 Add the entry preview logic.
  2030. */
  2031. fieldDelete: function( id ) {
  2032. var $field = $( '#wpforms-field-' + id ),
  2033. type = $field.data( 'field-type' );
  2034. if ( type === 'pagebreak' && $field.hasClass( 'wpforms-field-entry-preview-not-deleted' ) ) {
  2035. app.youCantRemovePageBreakFieldPopup();
  2036. return;
  2037. }
  2038. if ( $field.hasClass( 'no-delete' ) ) {
  2039. app.youCantRemoveFieldPopup();
  2040. return;
  2041. }
  2042. app.confirmFieldDeletion( id, type );
  2043. },
  2044. /**
  2045. * Show the error message in the popup that you cannot remove the page break field.
  2046. *
  2047. * @since 1.6.9
  2048. */
  2049. youCantRemovePageBreakFieldPopup: function() {
  2050. $.alert( {
  2051. title: wpforms_builder.heads_up,
  2052. content: wpforms_builder.entry_preview_require_page_break,
  2053. icon: 'fa fa-exclamation-circle',
  2054. type: 'red',
  2055. buttons: {
  2056. confirm: {
  2057. text: wpforms_builder.ok,
  2058. btnClass: 'btn-confirm',
  2059. keys: [ 'enter' ],
  2060. },
  2061. },
  2062. } );
  2063. },
  2064. /**
  2065. * Show the error message in the popup that you cannot reorder the field.
  2066. *
  2067. * @since 1.7.1
  2068. */
  2069. youCantReorderFieldPopup: function() {
  2070. $.confirm( {
  2071. title: wpforms_builder.heads_up,
  2072. content: wpforms_builder.field_cannot_be_reordered,
  2073. icon: 'fa fa-exclamation-circle',
  2074. type: 'red',
  2075. buttons: {
  2076. confirm: {
  2077. text: wpforms_builder.ok,
  2078. btnClass: 'btn-confirm',
  2079. keys: [ 'enter' ],
  2080. },
  2081. },
  2082. } );
  2083. },
  2084. /**
  2085. * Show the error message in the popup that you cannot remove the field.
  2086. *
  2087. * @since 1.6.9
  2088. */
  2089. youCantRemoveFieldPopup: function() {
  2090. $.alert( {
  2091. title: wpforms_builder.field_locked,
  2092. content: wpforms_builder.field_locked_msg,
  2093. icon: 'fa fa-info-circle',
  2094. type: 'blue',
  2095. buttons: {
  2096. confirm: {
  2097. text: wpforms_builder.close,
  2098. btnClass: 'btn-confirm',
  2099. keys: [ 'enter' ],
  2100. },
  2101. },
  2102. } );
  2103. },
  2104. /**
  2105. * Show the confirmation popup before the field deletion.
  2106. *
  2107. * @param {int} id Field ID.
  2108. * @param {string} type Field type.
  2109. *
  2110. * @since 1.6.9
  2111. */
  2112. confirmFieldDeletion: function( id, type ) {
  2113. var fieldData = {
  2114. 'id' : id,
  2115. 'message' : wpforms_builder.delete_confirm,
  2116. };
  2117. $builder.trigger( 'wpformsBeforeFieldDeleteAlert', [ fieldData ] );
  2118. $.confirm( {
  2119. title : false,
  2120. content : fieldData.message,
  2121. icon : 'fa fa-exclamation-circle',
  2122. type : 'orange',
  2123. buttons: {
  2124. confirm: {
  2125. text : wpforms_builder.ok,
  2126. btnClass : 'btn-confirm',
  2127. keys : [ 'enter' ],
  2128. action: function() {
  2129. app.fieldDeleteById( id, type );
  2130. },
  2131. },
  2132. cancel: {
  2133. text: wpforms_builder.cancel,
  2134. },
  2135. },
  2136. } );
  2137. },
  2138. /**
  2139. * Remove the field by ID.
  2140. *
  2141. * @since 1.6.9
  2142. *
  2143. * @param {int} id Field ID.
  2144. * @param {string} type Field type.
  2145. */
  2146. fieldDeleteById: function( id, type ) {
  2147. $( '#wpforms-field-' + id ).fadeOut( 400, function() {
  2148. $builder.trigger( 'wpformsBeforeFieldDelete', [ id, type ] );
  2149. $( this ).remove();
  2150. $( '#wpforms-field-option-' + id ).remove();
  2151. $( '.wpforms-field, .wpforms-title-desc' ).removeClass( 'active' );
  2152. app.fieldTabToggle( 'add-fields' );
  2153. if ( $( '.wpforms-field' ).length < 1 ) {
  2154. elements.$fieldOptions.append( elements.$noFieldsOptions.clone() );
  2155. elements.$sortableFieldsWrap.append( elements.$noFieldsPreview.clone() );
  2156. $builder.find( '.wpforms-field-submit' ).hide();
  2157. }
  2158. $builder.trigger( 'wpformsFieldDelete', [ id, type ] );
  2159. } );
  2160. },
  2161. /**
  2162. * Load entry preview fields.
  2163. *
  2164. * @since 1.6.9
  2165. */
  2166. loadEntryPreviewFields: function() {
  2167. var $fields = $( '.wpforms-field-wrap .wpforms-field-entry-preview' );
  2168. if ( ! $fields.length ) {
  2169. return;
  2170. }
  2171. $fields.each( function() {
  2172. app.lockEntryPreviewFieldsPosition( $( this ).data( 'field-id' ) );
  2173. } );
  2174. },
  2175. /**
  2176. * Delete the entry preview field from the form preview.
  2177. *
  2178. * @since 1.6.9
  2179. *
  2180. * @param {Event} event Event.
  2181. * @param {int} id Field ID.
  2182. * @param {string} type Field type.
  2183. */
  2184. fieldEntryPreviewDelete: function( event, id, type ) {
  2185. if ( 'entry-preview' !== type ) {
  2186. return;
  2187. }
  2188. var $field = $( '#wpforms-field-' + id ),
  2189. $previousPageBreakField = $field.prevAll( '.wpforms-field-pagebreak' ),
  2190. $nextPageBreakField = $field.nextAll( '.wpforms-field-pagebreak' ),
  2191. nextPageBreakId = $nextPageBreakField.data( 'field-id' ),
  2192. $nextPageBreakOptions = $( '#wpforms-field-option-' + nextPageBreakId );
  2193. $previousPageBreakField.removeClass( 'wpforms-field-not-draggable wpforms-field-entry-preview-not-deleted' );
  2194. $nextPageBreakOptions.find( '.wpforms-entry-preview-block' ).removeClass( 'wpforms-entry-preview-block' );
  2195. },
  2196. /**
  2197. * Maybe lock the entry preview and fields nearby after move event.
  2198. *
  2199. * @since 1.6.9
  2200. *
  2201. * @param {Event} e Event.
  2202. * @param {object} ui UI sortable object.
  2203. */
  2204. maybeLockEntryPreviewGroupOnMove: function( e, ui ) {
  2205. if ( ! ui.item.hasClass( 'wpforms-field-pagebreak' ) ) {
  2206. return;
  2207. }
  2208. app.maybeLockEntryPreviewGroupOnAdd( e, ui.item.data( 'field-id' ), 'pagebreak' );
  2209. },
  2210. /**
  2211. * Maybe lock the entry preview and fields nearby after add event.
  2212. *
  2213. * @since 1.6.9
  2214. *
  2215. * @param {Event} e Event.
  2216. * @param {int} fieldId Field id.
  2217. * @param {string} type Field type.
  2218. */
  2219. maybeLockEntryPreviewGroupOnAdd: function( e, fieldId, type ) {
  2220. if ( type !== 'pagebreak' ) {
  2221. return;
  2222. }
  2223. var $currentField = $( '#wpforms-field-' + fieldId ),
  2224. $currentFieldPrevToggle = $( '#wpforms-field-option-' + fieldId + ' .wpforms-field-option-row-prev_toggle' ),
  2225. $currentFieldPrevToggleField = $currentFieldPrevToggle.find( 'input' ),
  2226. $prevField = $currentField.prevAll( '.wpforms-field-entry-preview,.wpforms-field-pagebreak' ).first(),
  2227. $prevFieldPrevToggle = $( '#wpforms-field-option-' + $prevField.data( 'field-id' ) + ' .wpforms-field-option-row-prev_toggle' ),
  2228. $prevFieldPrevToggleField = $prevFieldPrevToggle.find( 'input' ),
  2229. $nextField = $currentField.nextAll( '.wpforms-field-entry-preview,.wpforms-field-pagebreak' ).first(),
  2230. $nextFieldPrevToggle = $( '#wpforms-field-option-' + $nextField.data( 'field-id' ) + ' .wpforms-field-option-row-prev_toggle' );
  2231. if ( ! $prevField.hasClass( 'wpforms-field-entry-preview' ) && ! $nextField.hasClass( 'wpforms-field-entry-preview' ) ) {
  2232. return;
  2233. }
  2234. if ( $prevField.hasClass( 'wpforms-field-entry-preview' ) ) {
  2235. $currentFieldPrevToggleField.attr( 'checked', 'checked' ).trigger( 'change' );
  2236. $currentFieldPrevToggle.addClass( 'wpforms-entry-preview-block' );
  2237. $nextFieldPrevToggle.removeClass( 'wpforms-entry-preview-block' );
  2238. return;
  2239. }
  2240. $currentField.addClass( 'wpforms-field-not-draggable wpforms-field-entry-preview-not-deleted' );
  2241. $prevField.removeClass( 'wpforms-field-not-draggable wpforms-field-entry-preview-not-deleted' );
  2242. if ( $prevField.prevAll( '.wpforms-field-entry-preview,.wpforms-field-pagebreak' ).first().hasClass( 'wpforms-field-entry-preview' ) ) {
  2243. $prevFieldPrevToggleField.attr( 'checked', 'checked' ).trigger( 'change' );
  2244. $prevFieldPrevToggle.addClass( 'wpforms-entry-preview-block' );
  2245. }
  2246. },
  2247. /**
  2248. * Show the error popup that the entry preview field blocks the field.
  2249. *
  2250. * @since 1.6.9
  2251. *
  2252. * @param {Event} e Event.
  2253. */
  2254. entryPreviewBlockField: function( e ) {
  2255. e.preventDefault();
  2256. $.alert( {
  2257. title: wpforms_builder.heads_up,
  2258. content: wpforms_builder.entry_preview_require_previous_button,
  2259. icon: 'fa fa-exclamation-circle',
  2260. type: 'red',
  2261. buttons: {
  2262. confirm: {
  2263. text: wpforms_builder.ok,
  2264. btnClass: 'btn-confirm',
  2265. keys: [ 'enter' ],
  2266. },
  2267. },
  2268. } );
  2269. },
  2270. /**
  2271. * Is it an entry preview field that should be checked before adding?
  2272. *
  2273. * @since 1.6.9
  2274. *
  2275. * @param {string} type Field type.
  2276. * @param {object} options Field options.
  2277. *
  2278. * @returns {boolean} True when we should check it.
  2279. */
  2280. isUncheckedEntryPreviewField: function( type, options ) {
  2281. return type === 'entry-preview' && ( ! options || options && ! options.passed );
  2282. },
  2283. /**
  2284. * Add an entry preview field to the form preview.
  2285. *
  2286. * @since 1.6.9
  2287. *
  2288. * @param {string} type Field type.
  2289. * @param {object} options Field options.
  2290. */
  2291. addEntryPreviewField: function( type, options ) { // eslint-disable-line complexity
  2292. var addButton = $( '#wpforms-add-fields-entry-preview' );
  2293. if ( addButton.hasClass( 'wpforms-entry-preview-adding' ) ) {
  2294. return;
  2295. }
  2296. var $fields = $( '.wpforms-field-wrap .wpforms-field' ),
  2297. position = options && options.position ? options.position : $fields.length,
  2298. needPageBreakBefore = app.isEntryPreviewFieldRequiresPageBreakBefore( $fields, position ),
  2299. needPageBreakAfter = app.isEntryPreviewFieldRequiresPageBreakAfter( $fields, position );
  2300. addButton.addClass( 'wpforms-entry-preview-adding' );
  2301. if ( ! options ) {
  2302. options = {};
  2303. }
  2304. options.passed = true;
  2305. if ( ! needPageBreakBefore && ! needPageBreakAfter ) {
  2306. app.fieldAdd( 'entry-preview', options ).done( function( res ) {
  2307. app.lockEntryPreviewFieldsPosition( res.data.field.id );
  2308. } );
  2309. return;
  2310. }
  2311. if ( needPageBreakBefore ) {
  2312. app.addPageBreakAndEntryPreviewFields( options, position );
  2313. return;
  2314. }
  2315. app.addEntryPreviewAndPageBreakFields( options, position );
  2316. },
  2317. /**
  2318. * Add the entry preview field after the page break field.
  2319. * We should wait for the page break adding to avoid id duplication.
  2320. *
  2321. * @since 1.6.9
  2322. *
  2323. * @param {object} options Field options.
  2324. */
  2325. addEntryPreviewFieldAfterPageBreak: function( options ) {
  2326. var checkExist = setInterval( function() {
  2327. if ( $( '.wpforms-field-wrap .wpforms-pagebreak-bottom, .wpforms-field-wrap .wpforms-pagebreak-top' ).length === 2 ) {
  2328. app.fieldAdd( 'entry-preview', options ).done( function( res ) {
  2329. app.lockEntryPreviewFieldsPosition( res.data.field.id );
  2330. } );
  2331. clearInterval( checkExist );
  2332. }
  2333. }, 100 );
  2334. },
  2335. /**
  2336. * Add the entry preview field after the page break field.
  2337. *
  2338. * @since 1.6.9
  2339. *
  2340. * @param {object} options Field options.
  2341. * @param {int} position The field position.
  2342. */
  2343. addPageBreakAndEntryPreviewFields: function( options, position ) {
  2344. var hasPageBreak = $( '.wpforms-field-wrap .wpforms-field-pagebreak' ).length >= 3;
  2345. app.fieldAdd( 'pagebreak', { 'position': position } ).done( function( res ) {
  2346. options.position = hasPageBreak ? position + 1 : position + 2;
  2347. app.addEntryPreviewFieldAfterPageBreak( options );
  2348. var $pageBreakOptions = $( '#wpforms-field-option-' + res.data.field.id ),
  2349. $pageBreakPrevToggle = $pageBreakOptions.find( '.wpforms-field-option-row-prev_toggle' ),
  2350. $pageBreakPrevToggleField = $pageBreakPrevToggle.find( 'input' );
  2351. $pageBreakPrevToggleField.attr( 'checked', 'checked' ).trigger( 'change' );
  2352. $pageBreakPrevToggle.addClass( 'wpforms-entry-preview-block' );
  2353. } );
  2354. },
  2355. /**
  2356. * Duplicate field
  2357. *
  2358. * @since 1.2.9
  2359. */
  2360. fieldDuplicate: function(id) {
  2361. var $field = $('#wpforms-field-'+id),
  2362. type = $field.data('field-type');
  2363. if ($field.hasClass('no-duplicate')) {
  2364. $.alert({
  2365. title: wpforms_builder.field_locked,
  2366. content: wpforms_builder.field_locked_msg,
  2367. icon: 'fa fa-info-circle',
  2368. type: 'blue',
  2369. buttons : {
  2370. confirm : {
  2371. text: wpforms_builder.close,
  2372. btnClass: 'btn-confirm',
  2373. keys: [ 'enter' ],
  2374. }
  2375. }
  2376. });
  2377. } else {
  2378. $.confirm({
  2379. title: false,
  2380. content: wpforms_builder.duplicate_confirm,
  2381. icon: 'fa fa-exclamation-circle',
  2382. type: 'orange',
  2383. buttons: {
  2384. confirm: {
  2385. text: wpforms_builder.ok,
  2386. btnClass: 'btn-confirm',
  2387. keys: [ 'enter' ],
  2388. action: function() {
  2389. var $fieldOptions = $( '#wpforms-field-option-' + id ),
  2390. isModernDropdown = app.dropdownField.helpers.isModernSelect( $field.find( '.primary-input' ) );
  2391. // Restore tooltips before cloning.
  2392. wpf.restoreTooltips( $fieldOptions );
  2393. // Force Modern Dropdown conversion to classic before cloning.
  2394. if ( isModernDropdown ) {
  2395. app.dropdownField.helpers.convertModernToClassic( id );
  2396. }
  2397. var $newField = $field.clone(),
  2398. newFieldOptions = $fieldOptions.html(),
  2399. newFieldID = $('#wpforms-field-id').val(),
  2400. $labelField = $( '#wpforms-field-option-' + id + '-label' ).length ? $( '#wpforms-field-option-' + id + '-label' ) : $( '#wpforms-field-option-' + id + '-name' ),
  2401. newFieldLabel = $labelField.length ? $labelField.val() + ' ' + wpforms_builder.duplicate_copy : wpforms_builder.field + ' #' + id + ' ' + wpforms_builder.duplicate_copy,
  2402. nextID = Number(newFieldID)+1,
  2403. regex_fieldOptionsID = new RegExp( 'ID #'+id, "g"),
  2404. regex_fieldID = new RegExp( 'fields\\['+id+'\\]', "g"),
  2405. regex_dataFieldID = new RegExp( 'data-field-id="'+id+'"', "g"),
  2406. regex_referenceID = new RegExp( 'data-reference="'+id+'"', "g"),
  2407. regex_elementID = new RegExp( '\\b(id|for)="wpforms-(.*?)'+id+'(.*?)"', "ig");
  2408. // Toggle visibility states
  2409. $field.after($newField);
  2410. $field.removeClass('active');
  2411. $newField.addClass('active').attr({
  2412. 'id' : 'wpforms-field-'+newFieldID,
  2413. 'data-field-id': newFieldID
  2414. });
  2415. // Various regex to adjust the field options to work with
  2416. // the new field ID
  2417. function regex_elementID_replace(match, p1, p2, p3, offset, string) {
  2418. return p1+'="wpforms-'+p2+newFieldID+p3+'"';
  2419. }
  2420. newFieldOptions = newFieldOptions.replace(regex_fieldOptionsID, 'ID #'+newFieldID);
  2421. newFieldOptions = newFieldOptions.replace(regex_fieldID, 'fields['+newFieldID+']');
  2422. newFieldOptions = newFieldOptions.replace(regex_dataFieldID, 'data-field-id="'+newFieldID+'"');
  2423. newFieldOptions = newFieldOptions.replace(regex_referenceID, 'data-reference="'+newFieldID+'"');
  2424. newFieldOptions = newFieldOptions.replace(regex_elementID, regex_elementID_replace);
  2425. // Add new field options panel
  2426. $fieldOptions.hide().after( '<div class="' + $fieldOptions.attr( 'class' ) + '" id="wpforms-field-option-' + newFieldID + '" data-field-id="' + newFieldID + '">' + newFieldOptions + '</div>' );
  2427. var $newFieldOptions = $('#wpforms-field-option-'+newFieldID);
  2428. // Copy over values
  2429. $fieldOptions.find(':input').each(function(index, el) {
  2430. var $this = $(this),
  2431. name = $this.attr('name');
  2432. if ( ! name ) {
  2433. return 'continue';
  2434. }
  2435. var newName = name.replace(regex_fieldID, 'fields['+newFieldID+']'),
  2436. type = $this.attr('type');
  2437. if ( type === 'checkbox' || type === 'radio' ) {
  2438. if ($this.is(':checked')){
  2439. $newFieldOptions.find('[name="'+newName+'"]').prop('checked', true).attr('checked','checked');
  2440. } else {
  2441. $newFieldOptions.find('[name="'+newName+'"]').prop('checked', false).attr('checked',false);
  2442. }
  2443. } else if ($this.is('select')) {
  2444. if ($this.find('option:selected').length) {
  2445. var optionVal = $this.find('option:selected').val();
  2446. $newFieldOptions.find('[name="'+newName+'"]').find('[value="'+optionVal+'"]').prop('selected',true);
  2447. }
  2448. } else {
  2449. if ($this.val() !== '') {
  2450. $newFieldOptions.find('[name="'+newName+'"]').val( $this.val() );
  2451. } else if ( $this.hasClass( 'wpforms-money-input' ) ) {
  2452. $newFieldOptions.find( '[name="' + newName + '"]' ).val(
  2453. wpf.numberFormat( '0', wpforms_builder.currency_decimals, wpforms_builder.currency_decimal, wpforms_builder.currency_thousands )
  2454. );
  2455. }
  2456. }
  2457. });
  2458. // ID adjustments.
  2459. $( '#wpforms-field-option-' + newFieldID ).find( '.wpforms-field-option-hidden-id' ).val( newFieldID );
  2460. $( '#wpforms-field-id' ).val( nextID );
  2461. // Adjust label to indicate this is a copy.
  2462. $( '#wpforms-field-option-' + newFieldID + '-label' ).val( newFieldLabel );
  2463. // For the HTML field we should change the internal label called `name`.
  2464. if ( type === 'html' ) {
  2465. $( '#wpforms-field-option-' + newFieldID + '-name' ).val( newFieldLabel );
  2466. }
  2467. $newField.find( '.label-title .text' ).text( newFieldLabel );
  2468. // Fire field add custom event.
  2469. $builder.trigger( 'wpformsFieldAdd', [ newFieldID, type ] );
  2470. // Re-init tooltips for new field options panel.
  2471. wpf.initTooltips();
  2472. // Re-init Modern Dropdown.
  2473. if ( isModernDropdown ) {
  2474. app.dropdownField.helpers.convertClassicToModern( id );
  2475. app.dropdownField.helpers.convertClassicToModern( newFieldID );
  2476. }
  2477. // Re-init instance in choices related fields.
  2478. app.fieldChoiceUpdate( $newField.data( 'field-type' ), newFieldID );
  2479. // Lastly, update the next ID stored in database.
  2480. $.post(
  2481. wpforms_builder.ajax_url,
  2482. {
  2483. form_id : s.formID,
  2484. nonce : wpforms_builder.nonce,
  2485. action : 'wpforms_builder_increase_next_field_id',
  2486. }
  2487. );
  2488. },
  2489. },
  2490. cancel: {
  2491. text: wpforms_builder.cancel,
  2492. },
  2493. },
  2494. } );
  2495. }
  2496. },
  2497. /**
  2498. * Add the entry preview field before the page break field.
  2499. *
  2500. * @since 1.6.9
  2501. *
  2502. * @param {object} options Field options.
  2503. * @param {int} position The field position.
  2504. */
  2505. addEntryPreviewAndPageBreakFields: function( options, position ) {
  2506. app.fieldAdd( 'entry-preview', options ).done( function( res ) {
  2507. var entryPreviewId = res.data.field.id;
  2508. app.fieldAdd( 'pagebreak', { 'position': position + 1 } ).done( function() {
  2509. app.lockEntryPreviewFieldsPosition( entryPreviewId );
  2510. } );
  2511. } );
  2512. },
  2513. /**
  2514. * Stick an entry preview field after adding.
  2515. *
  2516. * @since 1.6.9
  2517. *
  2518. * @param {int} id ID.
  2519. */
  2520. lockEntryPreviewFieldsPosition: function( id ) {
  2521. var $entryPreviewField = $( '#wpforms-field-' + id ),
  2522. $pageBreakField = $entryPreviewField.prevAll( '.wpforms-field-pagebreak:not(.wpforms-pagebreak-bottom)' ).first(),
  2523. $nextPageBreakField = $entryPreviewField.nextAll( '.wpforms-field-pagebreak' ).first(),
  2524. pageBreakFieldId = $nextPageBreakField.data( 'field-id' ),
  2525. $pageBreakOptions = $( '#wpforms-field-option-' + pageBreakFieldId ),
  2526. $pageBreakPrevToggle = $pageBreakOptions.find( '.wpforms-field-option-row-prev_toggle' ),
  2527. $pageBreakPrevToggleField = $pageBreakPrevToggle.find( 'input' );
  2528. $entryPreviewField.addClass( 'wpforms-field-not-draggable' );
  2529. $pageBreakField.addClass( 'wpforms-field-not-draggable wpforms-field-entry-preview-not-deleted' );
  2530. $pageBreakPrevToggleField.attr( 'checked', 'checked' ).trigger( 'change' );
  2531. $pageBreakPrevToggle.addClass( 'wpforms-entry-preview-block' );
  2532. $( '#wpforms-add-fields-entry-preview' ).removeClass( 'wpforms-entry-preview-adding' );
  2533. },
  2534. /**
  2535. * An entry preview field requires a page break that locates before.
  2536. *
  2537. * @since 1.6.9
  2538. *
  2539. * @param {jQuery} $fields List of fields in the form preview.
  2540. * @param {int} position The field position.
  2541. *
  2542. * @returns {boolean} True if need add an page break field before.
  2543. */
  2544. isEntryPreviewFieldRequiresPageBreakBefore: function( $fields, position ) {
  2545. var $beforeFields = $fields.slice( 0, position ).filter( '.wpforms-field-pagebreak,.wpforms-field-entry-preview' ),
  2546. needPageBreakBefore = true;
  2547. if ( ! $beforeFields.length ) {
  2548. return needPageBreakBefore;
  2549. }
  2550. $( $beforeFields.get().reverse() ).each( function() {
  2551. var $this = $( this );
  2552. if ( $this.hasClass( 'wpforms-field-entry-preview' ) ) {
  2553. return false;
  2554. }
  2555. if ( $this.hasClass( 'wpforms-field-pagebreak' ) && ! $this.hasClass( 'wpforms-field-stick' ) ) {
  2556. needPageBreakBefore = false;
  2557. return false;
  2558. }
  2559. } );
  2560. return needPageBreakBefore;
  2561. },
  2562. /**
  2563. * An entry preview field requires a page break that locates after.
  2564. *
  2565. * @since 1.6.9
  2566. *
  2567. * @param {jQuery} $fields List of fields in the form preview.
  2568. * @param {int} position The field position.
  2569. *
  2570. * @returns {boolean} True if need add an page break field after.
  2571. */
  2572. isEntryPreviewFieldRequiresPageBreakAfter: function( $fields, position ) {
  2573. var $afterFields = $fields.slice( position ).filter( '.wpforms-field-pagebreak,.wpforms-field-entry-preview' ),
  2574. needPageBreakAfter = Boolean( $afterFields.length );
  2575. if ( ! $afterFields.length ) {
  2576. return needPageBreakAfter;
  2577. }
  2578. $afterFields.each( function() {
  2579. var $this = $( this );
  2580. if ( $this.hasClass( 'wpforms-field-entry-preview' ) ) {
  2581. return false;
  2582. }
  2583. if ( $this.hasClass( 'wpforms-field-pagebreak' ) ) {
  2584. needPageBreakAfter = false;
  2585. return false;
  2586. }
  2587. } );
  2588. return needPageBreakAfter;
  2589. },
  2590. /**
  2591. * Add new field.
  2592. *
  2593. * @since 1.0.0
  2594. * @since 1.6.4 Added hCaptcha support.
  2595. */
  2596. fieldAdd: function(type, options) {
  2597. var $btn = $( '#wpforms-add-fields-' + type );
  2598. if ( $btn.hasClass( 'upgrade-modal' ) || $btn.hasClass( 'education-modal' ) || $btn.hasClass( 'warning-modal' ) ) {
  2599. return;
  2600. }
  2601. if ( -1 !== $.inArray( type, [ 'captcha_hcaptcha', 'captcha_recaptcha', 'captcha_none' ] ) ) {
  2602. app.captchaUpdate();
  2603. return;
  2604. }
  2605. adding = true;
  2606. app.disableDragAndDrop();
  2607. if ( app.isUncheckedEntryPreviewField( type, options ) ) {
  2608. app.addEntryPreviewField( type, options );
  2609. return;
  2610. }
  2611. var defaults = {
  2612. position : 'bottom',
  2613. placeholder: false,
  2614. scroll : true,
  2615. defaults : false
  2616. };
  2617. options = $.extend( {}, defaults, options);
  2618. var data = {
  2619. action : 'wpforms_new_field_'+type,
  2620. id : s.formID,
  2621. type : type,
  2622. defaults: options.defaults,
  2623. nonce : wpforms_builder.nonce
  2624. };
  2625. return $.post(wpforms_builder.ajax_url, data, function(res) {
  2626. if (res.success) {
  2627. var totalFields = $('.wpforms-field').length,
  2628. $preview = $('#wpforms-panel-fields .wpforms-panel-content-wrap'),
  2629. $lastField = $('.wpforms-field').last(),
  2630. $newField = $(res.data.preview),
  2631. $newOptions = $(res.data.options);
  2632. adding = false;
  2633. $newField.css( 'display', 'none' );
  2634. if (options.placeholder) {
  2635. options.placeholder.remove();
  2636. }
  2637. // Determine where field gets placed
  2638. if ( options.position === 'bottom' ) {
  2639. if ( $lastField.length && $lastField.hasClass( 'wpforms-field-stick' ) ) {
  2640. // Check to see if the last field we have is configured to
  2641. // be stuck to the bottom, if so add the field above it.
  2642. $( '.wpforms-field-wrap' ).children( ':eq(' + ( totalFields - 1 ) + ')' ).before( $newField );
  2643. $( '.wpforms-field-options' ).children( ':eq(' + ( totalFields - 1 ) + ')' ).before( $newOptions );
  2644. } else {
  2645. // Add field to bottom
  2646. $( '.wpforms-field-wrap' ).append( $newField );
  2647. $( '.wpforms-field-options' ).append( $newOptions );
  2648. }
  2649. } else if ( options.position === 'top' ) {
  2650. // Add field to top, scroll to
  2651. $( '.wpforms-field-wrap' ).prepend( $newField );
  2652. $( '.wpforms-field-options' ).prepend( $newOptions );
  2653. } else {
  2654. if ( options.position === totalFields && $lastField.length && $lastField.hasClass( 'wpforms-field-stick' ) ) {
  2655. // Check to see if the user tried to add the field at
  2656. // the end BUT the last field we have is configured to
  2657. // be stuck to the bottom, if so add the field above it.
  2658. $( '.wpforms-field-wrap' ).children( ':eq(' + ( totalFields - 1 ) + ')' ).before( $newField );
  2659. $( '.wpforms-field-options' ).children( ':eq(' + ( totalFields - 1 ) + ')' ).before( $newOptions );
  2660. } else if ( $( '.wpforms-field-wrap' ).find( '.wpforms-field' ).eq( options.position ).length ) {
  2661. // Add field to a specific location.
  2662. $( '.wpforms-field-wrap' ).find( '.wpforms-field' ).eq( options.position ).before( $newField );
  2663. $( '.wpforms-field-options' ).find( '.wpforms-field-option' ).eq( options.position ).before( $newOptions );
  2664. } else {
  2665. // Something's wrong, just add the field. This should never occur.
  2666. $( '.wpforms-field-wrap' ).append( $newField );
  2667. $( '.wpforms-field-options' ).append( $newOptions );
  2668. }
  2669. }
  2670. $newField.fadeIn();
  2671. $builder.find( '.no-fields, .no-fields-preview' ).remove();
  2672. $builder.find( '.wpforms-field-submit' ).show();
  2673. // Scroll to the desired position.
  2674. if ( options.scroll && options.position.length ) {
  2675. var scrollTop = $preview.scrollTop(),
  2676. newFieldPosition = $newField.position().top,
  2677. scrollAmount = newFieldPosition > scrollTop ? newFieldPosition - scrollTop : newFieldPosition + scrollTop;
  2678. $preview.animate(
  2679. {
  2680. // Position `bottom` actually means that we need to scroll to the newly added field.
  2681. scrollTop: options.position === 'bottom' ? scrollAmount : 0,
  2682. },
  2683. 1000
  2684. );
  2685. }
  2686. $( '#wpforms-field-id' ).val( res.data.field.id + 1 );
  2687. wpf.initTooltips();
  2688. app.loadColorPickers();
  2689. app.toggleAllOptionGroups();
  2690. $builder.trigger( 'wpformsFieldAdd', [ res.data.field.id, type ] );
  2691. } else {
  2692. console.log( res );
  2693. }
  2694. } ).fail( function( xhr, textStatus, e ) {
  2695. adding = false;
  2696. console.log( xhr.responseText );
  2697. } ).always( function() {
  2698. $builder.find( '.wpforms-add-fields .wpforms-add-fields-button' ).prop( 'disabled', false );
  2699. if ( ! adding ) {
  2700. app.enableDragAndDrop();
  2701. }
  2702. } );
  2703. },
  2704. /**
  2705. * Update CAPTCHA form setting.
  2706. *
  2707. * @since 1.6.4
  2708. *
  2709. * @returns {object} jqXHR
  2710. */
  2711. captchaUpdate: function() {
  2712. var data = {
  2713. action : 'wpforms_update_field_captcha',
  2714. id : s.formID,
  2715. nonce : wpforms_builder.nonce,
  2716. };
  2717. return $.post( wpforms_builder.ajax_url, data, function( res ) {
  2718. if ( res.success ) {
  2719. var args = {
  2720. title: false,
  2721. content: false,
  2722. icon: 'fa fa-exclamation-circle',
  2723. type: 'orange',
  2724. boxWidth: '450px',
  2725. buttons: {
  2726. confirm: {
  2727. text: wpforms_builder.ok,
  2728. btnClass: 'btn-confirm',
  2729. keys: [ 'enter' ],
  2730. },
  2731. },
  2732. },
  2733. $enableCheckbox = $( '#wpforms-panel-field-settings-recaptcha' ),
  2734. caseName = res.data.current;
  2735. $enableCheckbox.data( 'provider', res.data.provider );
  2736. // Possible cases:
  2737. //
  2738. // not_configured - IF CAPTCHA is not configured in the WPForms plugin settings
  2739. // configured_not_enabled - IF CAPTCHA is configured in WPForms plugin settings, but wasn't set in form settings
  2740. // configured_enabled - IF CAPTCHA is configured in WPForms plugin and form settings
  2741. if ( 'configured_not_enabled' === caseName || 'configured_enabled' === caseName ) {
  2742. // Get a correct case name.
  2743. caseName = $enableCheckbox.prop( 'checked' ) ? 'configured_enabled' : 'configured_not_enabled';
  2744. // Check/uncheck a `CAPTCHA` checkbox in form setting.
  2745. args.buttons.confirm.action = function() {
  2746. $enableCheckbox.prop( 'checked', ( 'configured_not_enabled' === caseName ) ).trigger( 'change' );
  2747. };
  2748. }
  2749. args.title = res.data.cases[ caseName ].title;
  2750. args.content = res.data.cases[ caseName ].content;
  2751. // Do you need a Cancel button?
  2752. if ( res.data.cases[ caseName ].cancel ) {
  2753. args.buttons.cancel = {
  2754. text: wpforms_builder.cancel,
  2755. keys: [ 'esc' ],
  2756. };
  2757. }
  2758. // Call a Confirm modal.
  2759. $.confirm( args );
  2760. } else {
  2761. console.log( res );
  2762. }
  2763. } ).fail( function( xhr, textStatus, e ) {
  2764. console.log( xhr.responseText );
  2765. } );
  2766. },
  2767. /**
  2768. * Disable drag & drop.
  2769. *
  2770. * @since 1.7.1
  2771. */
  2772. disableDragAndDrop: function() {
  2773. elements.$addFieldsButtons.filter( '.ui-draggable' ).draggable( 'disable' );
  2774. elements.$sortableFieldsWrap.filter( '.ui-sortable' ).sortable( 'disable' );
  2775. },
  2776. /**
  2777. * Enable drag & drop.
  2778. *
  2779. * @since 1.7.1
  2780. */
  2781. enableDragAndDrop: function() {
  2782. elements.$addFieldsButtons.filter( '.ui-draggable' ).draggable( 'enable' );
  2783. elements.$sortableFieldsWrap.filter( '.ui-sortable' ).sortable( 'enable' );
  2784. },
  2785. /**
  2786. * Sortable fields in the builder form preview area.
  2787. *
  2788. * @since 1.0.0
  2789. */
  2790. fieldSortable: function() {
  2791. var fieldOptions = $( '.wpforms-field-options' ),
  2792. fieldReceived = false,
  2793. fieldIndex,
  2794. fieldIndexNew,
  2795. field,
  2796. fieldNew,
  2797. $scrollContainer = $( '#wpforms-panel-fields .wpforms-panel-content-wrap' ),
  2798. currentlyScrolling = false;
  2799. elements.$sortableFieldsWrap.sortable( {
  2800. items : '> .wpforms-field:not(.wpforms-field-stick):not(.no-fields-preview)',
  2801. axis : 'y',
  2802. delay : 100,
  2803. opacity: 0.75,
  2804. cursor : 'move',
  2805. start: function( e, ui ) {
  2806. fieldIndex = ui.item.index();
  2807. field = fieldOptions[0].children[fieldIndex];
  2808. },
  2809. stop: function( e, ui ) {
  2810. fieldIndexNew = ui.item.index();
  2811. fieldNew = fieldOptions[ 0 ].children[ fieldIndexNew ];
  2812. if ( fieldIndex < fieldIndexNew ) {
  2813. $( fieldNew ).after( field );
  2814. } else {
  2815. $( fieldNew ).before( field );
  2816. }
  2817. $builder.trigger( 'wpformsFieldMove', ui );
  2818. fieldReceived = false;
  2819. },
  2820. over: function( e, ui ) {
  2821. var $el = ui.item.first();
  2822. $el.addClass( 'wpforms-field-dragging' );
  2823. if ( $el.hasClass( 'wpforms-field-drag' ) ) {
  2824. var width = $( '.wpforms-field' ).outerWidth() || elements.$sortableFieldsWrap.find( '.no-fields-preview' ).outerWidth();
  2825. $el.addClass( 'wpforms-field-drag-over' ).removeClass( 'wpforms-field-drag-out' ).css( 'width', width ).css( 'height', 'auto' );
  2826. }
  2827. },
  2828. out: function( e, ui ) {
  2829. var $el = ui.item.first();
  2830. $el.removeClass( 'wpforms-field-dragging' );
  2831. if ( ! fieldReceived ) {
  2832. var width = $el.attr( 'data-original-width' );
  2833. if ( $el.hasClass( 'wpforms-field-drag' ) ) {
  2834. $el.addClass( 'wpforms-field-drag-out' ).removeClass( 'wpforms-field-drag-over' ).css( { 'width': width, 'left': '', 'top': '' } );
  2835. }
  2836. }
  2837. $el.css( {
  2838. 'top': '',
  2839. 'left': '',
  2840. 'z-index': '',
  2841. } );
  2842. },
  2843. receive: function( e, ui ) {
  2844. fieldReceived = true;
  2845. var pos = $( this ).data( 'ui-sortable' ).currentItem.index(),
  2846. $el = ui.helper,
  2847. type = $el.attr( 'data-field-type' );
  2848. $el.addClass( 'wpforms-field-drag-over wpforms-field-drag-pending' ).removeClass( 'wpforms-field-drag-out' ).css( 'width', '100%' );
  2849. $el.append( s.spinnerInline );
  2850. $builder.find( '.wpforms-add-fields .wpforms-add-fields-button' ).prop( 'disabled', true );
  2851. $builder.find( '.no-fields-preview' ).remove();
  2852. app.fieldAdd( type, { position: pos, placeholder: $el } );
  2853. },
  2854. sort: function( e, ui ) {
  2855. if ( currentlyScrolling ) {
  2856. return;
  2857. }
  2858. var scrollAreaHeight = 50,
  2859. operator,
  2860. mouseYPosition = e.clientY,
  2861. containerOffset = $scrollContainer.offset(),
  2862. containerHeight = $scrollContainer.height(),
  2863. containerBottom = containerOffset.top + containerHeight;
  2864. if (
  2865. mouseYPosition > containerOffset.top &&
  2866. mouseYPosition < ( containerOffset.top + scrollAreaHeight )
  2867. ) {
  2868. operator = '-=';
  2869. } else if (
  2870. mouseYPosition > ( containerBottom - scrollAreaHeight ) &&
  2871. mouseYPosition < containerBottom
  2872. ) {
  2873. operator = '+=';
  2874. } else {
  2875. return;
  2876. }
  2877. currentlyScrolling = true;
  2878. $scrollContainer.animate(
  2879. {
  2880. scrollTop: operator + containerHeight / 3 + 'px',
  2881. },
  2882. 800,
  2883. function() {
  2884. currentlyScrolling = false;
  2885. }
  2886. );
  2887. },
  2888. cancel: '.wpforms-field-not-draggable',
  2889. } );
  2890. // Show popup in case if field is not draggable, cancel moving.
  2891. var startTopPosition;
  2892. $( '.wpforms-field-not-draggable, .wpforms-field-stick' ).draggable( {
  2893. revert: true,
  2894. axis: 'y',
  2895. delay : 100,
  2896. opacity: 0.75,
  2897. cursor : 'move',
  2898. start: function( event, ui ) {
  2899. startTopPosition = ui.position.top;
  2900. },
  2901. drag: function( event, ui ) {
  2902. if ( Math.abs( ui.position.top ) - Math.abs( startTopPosition ) > 15 ) {
  2903. app.youCantReorderFieldPopup();
  2904. return false;
  2905. }
  2906. },
  2907. } );
  2908. $( '.wpforms-add-fields-button' )
  2909. .not( '.not-draggable' )
  2910. .not( '.warning-modal' )
  2911. .not( '.education-modal' )
  2912. .draggable( {
  2913. connectToSortable: '.wpforms-field-wrap',
  2914. delay: 200,
  2915. helper: function() {
  2916. var $this = $( this ),
  2917. width = $this.outerWidth(),
  2918. text = $this.html(),
  2919. type = $this.data( 'field-type' ),
  2920. $el = $( '<div class="wpforms-field-drag-out wpforms-field-drag">' );
  2921. return $el.html( text ).css( 'width', width ).attr( {'data-original-width': width, 'data-field-type': type } );
  2922. },
  2923. revert: 'invalid',
  2924. cancel: false,
  2925. scroll: false,
  2926. opacity: 0.75,
  2927. containment: 'document',
  2928. } );
  2929. elements.$addFieldsButtons.draggable( {
  2930. connectToSortable: '.wpforms-field-wrap',
  2931. delay: 200,
  2932. helper: function() {
  2933. var $this = $( this ),
  2934. width = $this.outerWidth(),
  2935. text = $this.html(),
  2936. type = $this.data( 'field-type' ),
  2937. $el = $( '<div class="wpforms-field-drag-out wpforms-field-drag">' );
  2938. return $el.html( text ).css( 'width', width ).attr( { 'data-original-width': width, 'data-field-type': type } );
  2939. },
  2940. revert: 'invalid',
  2941. cancel: false,
  2942. scroll: false,
  2943. opacity: 0.75,
  2944. containment: 'document',
  2945. } );
  2946. },
  2947. /**
  2948. * Add new field choice
  2949. *
  2950. * @since 1.0.0
  2951. */
  2952. fieldChoiceAdd: function( event, el ) {
  2953. event.preventDefault();
  2954. var $this = $( el ),
  2955. $parent = $this.parent(),
  2956. checked = $parent.find( 'input.default' ).is( ':checked' ),
  2957. fieldID = $this.closest( '.wpforms-field-option-row-choices' ).data( 'field-id' ),
  2958. id = $parent.parent().attr( 'data-next-id' ),
  2959. type = $parent.parent().data( 'field-type' ),
  2960. $choice = $parent.clone().insertAfter( $parent );
  2961. $choice.attr( 'data-key', id );
  2962. $choice.find( 'input.label' ).val( '' ).attr( 'name', 'fields['+fieldID+'][choices]['+id+'][label]' );
  2963. $choice.find( 'input.value' ).val( '' ).attr( 'name', 'fields['+fieldID+'][choices]['+id+'][value]' );
  2964. $choice.find( 'input.source' ).val( '' ).attr( 'name', 'fields['+fieldID+'][choices]['+id+'][image]' );
  2965. $choice.find( 'input.default').attr( 'name', 'fields['+fieldID+'][choices]['+id+'][default]' ).prop( 'checked', false );
  2966. $choice.find( '.preview' ).empty();
  2967. $choice.find( '.wpforms-image-upload-add' ).show();
  2968. $choice.find( '.wpforms-money-input' ).trigger( 'focusout' );
  2969. if ( checked === true ) {
  2970. $parent.find( 'input.default' ).prop( 'checked', true );
  2971. }
  2972. id++;
  2973. $parent.parent().attr( 'data-next-id', id );
  2974. $builder.trigger( 'wpformsFieldChoiceAdd' );
  2975. app.fieldChoiceUpdate( type, fieldID );
  2976. },
  2977. /**
  2978. * Delete field choice
  2979. *
  2980. * @since 1.0.0
  2981. */
  2982. fieldChoiceDelete: function( e, el ) {
  2983. e.preventDefault();
  2984. var $this = $( el ),
  2985. $list = $this.parent().parent(),
  2986. total = $list.find( 'li' ).length,
  2987. fieldData = {
  2988. 'id' : $list.data( 'field-id' ),
  2989. 'choiceId' : $this.closest( 'li' ).data( 'key' ),
  2990. 'message' : '<strong>' + wpforms_builder.delete_choice_confirm + '</strong>',
  2991. 'trigger' : false,
  2992. };
  2993. $builder.trigger( 'wpformsBeforeFieldDeleteAlert', [ fieldData ] );
  2994. if ( total === 1 ) {
  2995. app.fieldChoiceDeleteAlert();
  2996. } else {
  2997. var deleteChoice = function() {
  2998. $this.parent().remove();
  2999. app.fieldChoiceUpdate( $list.data( 'field-type' ), $list.data( 'field-id' ) );
  3000. $builder.trigger( 'wpformsFieldChoiceDelete' );
  3001. };
  3002. if ( ! fieldData.trigger ) {
  3003. deleteChoice();
  3004. return;
  3005. }
  3006. $.confirm( {
  3007. title: false,
  3008. content: fieldData.message,
  3009. icon: 'fa fa-exclamation-circle',
  3010. type: 'orange',
  3011. buttons: {
  3012. confirm: {
  3013. text: wpforms_builder.ok,
  3014. btnClass: 'btn-confirm',
  3015. keys: [ 'enter' ],
  3016. action: function() {
  3017. deleteChoice();
  3018. },
  3019. },
  3020. cancel: {
  3021. text: wpforms_builder.cancel,
  3022. },
  3023. },
  3024. } );
  3025. }
  3026. },
  3027. /**
  3028. * Field choice delete error alert.
  3029. *
  3030. * @since 1.6.7
  3031. */
  3032. fieldChoiceDeleteAlert: function() {
  3033. $.alert( {
  3034. title: false,
  3035. content: wpforms_builder.error_choice,
  3036. icon: 'fa fa-info-circle',
  3037. type: 'blue',
  3038. buttons: {
  3039. confirm: {
  3040. text: wpforms_builder.ok,
  3041. btnClass: 'btn-confirm',
  3042. keys: [ 'enter' ],
  3043. },
  3044. },
  3045. } );
  3046. },
  3047. /**
  3048. * Make field choices sortable.
  3049. *
  3050. * Currently used for select, radio, and checkboxes field types
  3051. *
  3052. * @since 1.0.0
  3053. */
  3054. fieldChoiceSortable: function(type, selector) {
  3055. selector = typeof selector !== 'undefined' ? selector : '.wpforms-field-option-'+type+' .wpforms-field-option-row-choices ul';
  3056. $(selector).sortable({
  3057. items : 'li',
  3058. axis : 'y',
  3059. delay : 100,
  3060. opacity: 0.6,
  3061. handle : '.move',
  3062. stop:function(e,ui){
  3063. var id = ui.item.parent().data('field-id');
  3064. app.fieldChoiceUpdate(type, id);
  3065. $builder.trigger('wpformsFieldChoiceMove', ui);
  3066. },
  3067. update:function(e,ui){
  3068. }
  3069. });
  3070. },
  3071. /**
  3072. * Generate Choice label. Used in field preview template.
  3073. *
  3074. * @since 1.6.2
  3075. *
  3076. * @param {object} data Template data.
  3077. * @param {numeric} choiceID Choice ID.
  3078. *
  3079. * @returns {string} Label.
  3080. */
  3081. fieldChoiceLabel: function( data, choiceID ) {
  3082. var label = typeof data.settings.choices[choiceID].label !== 'undefined' && data.settings.choices[choiceID].label.length !== 0 ?
  3083. wpf.sanitizeHTML( data.settings.choices[choiceID].label ) :
  3084. wpforms_builder.choice_empty_label_tpl.replace( '{number}', choiceID );
  3085. if ( data.settings.show_price_after_labels ) {
  3086. label += ' - ' + wpf.amountFormatCurrency( data.settings.choices[choiceID].value );
  3087. }
  3088. return label;
  3089. },
  3090. /**
  3091. * Update field choices in preview area, for the Fields panel.
  3092. *
  3093. * Currently used for select, radio, and checkboxes field types.
  3094. *
  3095. * @since 1.0.0
  3096. */
  3097. fieldChoiceUpdate: function( type, id ) {
  3098. var $primary = $( '#wpforms-field-' + id + ' .primary-input' );
  3099. // Radio, Checkbox, and Payment Multiple/Checkbox use _ template.
  3100. if ( 'radio' === type || 'checkbox' === type || 'payment-multiple' === type || 'payment-checkbox' === type ) {
  3101. var fieldSettings = wpf.getField( id ),
  3102. order = wpf.getChoicesOrder( id ),
  3103. slicedChoices = {},
  3104. slicedOrder = order.slice( 0, 20 ),
  3105. tmpl = wp.template( 'wpforms-field-preview-checkbox-radio-payment-multiple' ),
  3106. data = {
  3107. settings: fieldSettings,
  3108. order: slicedOrder,
  3109. type: 'radio',
  3110. };
  3111. // Slice choices for preview.
  3112. slicedOrder.forEach( function( entry ) {
  3113. slicedChoices[ entry ] = fieldSettings.choices[ entry ];
  3114. } );
  3115. fieldSettings.choices = slicedChoices;
  3116. if ( 'checkbox' === type || 'payment-checkbox' === type ) {
  3117. data.type = 'checkbox';
  3118. }
  3119. $( '#wpforms-field-' + id ).find( 'ul.primary-input' ).replaceWith( tmpl( data ) );
  3120. // Toggle limit choices alert message.
  3121. app.firstNChoicesAlert( id, order.length );
  3122. return;
  3123. }
  3124. var isModernSelect = app.dropdownField.helpers.isModernSelect( $primary ),
  3125. newChoice = '';
  3126. // Multiple payment choices are radio buttons.
  3127. if ( 'payment-multiple' === type ) {
  3128. type = 'radio';
  3129. }
  3130. // Checkbox payment choices are checkboxes.
  3131. if ( 'payment-checkbox' === type ) {
  3132. type = 'checkbox';
  3133. }
  3134. // Dropdown payment choices are selects.
  3135. if ( 'payment-select' === type ) {
  3136. type = 'select';
  3137. }
  3138. if ( 'select' === type ) {
  3139. newChoice = '<option value="{label}">{label}</option>';
  3140. $primary.find( 'option' ).not( '.placeholder' ).remove();
  3141. } else if ( 'radio' === type || 'checkbox' === type || 'gdpr-checkbox' === type ) {
  3142. type = 'gdpr-checkbox' === type ? 'checkbox' : type;
  3143. $primary.find( 'li' ).remove();
  3144. newChoice = '<li><input type="' + type + '" disabled>{label}</li>';
  3145. }
  3146. // Building an inner content for Primary field.
  3147. var $choicesList = $( '#wpforms-field-option-row-' + id + '-choices .choices-list' ),
  3148. $choicesToRender = $choicesList.find( 'li' ).slice( 0, 20 ),
  3149. hasDefaults = !! $choicesList.find( 'input.default:checked' ).length,
  3150. modernSelectChoices = [],
  3151. showPriceAfterLabels = $( '#wpforms-field-option-' + id + '-show_price_after_labels' ).prop( 'checked' );
  3152. $choicesToRender.each( function() {// eslint-disable-line complexity
  3153. var $this = $( this ),
  3154. label = wpf.sanitizeHTML( $this.find( 'input.label' ).val().trim() ),
  3155. value = $this.find( 'input.value' ).val(),
  3156. selected = $this.find( 'input.default' ).is( ':checked' ),
  3157. choiceID = $this.data( 'key' ),
  3158. $choice;
  3159. label = label !== '' ? label : wpforms_builder.choice_empty_label_tpl.replace( '{number}', choiceID );
  3160. label += ( showPriceAfterLabels && value ) ? ' - ' + wpf.amountFormatCurrency( value ) : '';
  3161. // Append a new choice.
  3162. if ( ! isModernSelect ) {
  3163. $choice = $( newChoice.replace( /{label}/g, label ) );
  3164. $primary.append( $choice );
  3165. } else {
  3166. modernSelectChoices.push(
  3167. {
  3168. value: label,
  3169. label: label,
  3170. }
  3171. );
  3172. }
  3173. if ( true === selected ) {
  3174. switch ( type ) {
  3175. case 'select':
  3176. if ( ! isModernSelect ) {
  3177. $choice.prop( 'selected', 'true' );
  3178. } else {
  3179. modernSelectChoices[ modernSelectChoices.length - 1 ].selected = true;
  3180. }
  3181. break;
  3182. case 'radio':
  3183. case 'checkbox':
  3184. $choice.find( 'input' ).prop( 'checked', 'true' );
  3185. break;
  3186. }
  3187. }
  3188. } );
  3189. if ( isModernSelect ) {
  3190. var placeholderClass = $primary.prop( 'multiple' ) ? 'input.choices__input' : '.choices__inner .choices__placeholder',
  3191. choicesjsInstance = app.dropdownField.helpers.getInstance( $primary ),
  3192. isDynamicChoices = $( '#wpforms-field-option-' + id + '-dynamic_choices' ).val();
  3193. choicesjsInstance.removeActiveItems();
  3194. choicesjsInstance.setChoices( modernSelectChoices, 'value', 'label', true );
  3195. // Re-initialize modern dropdown to properly determine and update placeholder.
  3196. app.dropdownField.helpers.update( id, isDynamicChoices );
  3197. // Hide/show a placeholder for Modern select if it has or not default choices.
  3198. $primary
  3199. .closest( '.choices' )
  3200. .find( placeholderClass )
  3201. .toggleClass( 'wpforms-hidden', hasDefaults );
  3202. }
  3203. },
  3204. /**
  3205. * Field choice bulk add toggling.
  3206. *
  3207. * @since 1.3.7
  3208. */
  3209. fieldChoiceBulkAddToggle: function(el) {
  3210. var $this = $(el),
  3211. $label = $this.closest('label');
  3212. if ( $this.hasClass('bulk-add-showing') ) {
  3213. // Import details is showing, so hide/remove it
  3214. var $selector = $label.next('.bulk-add-display');
  3215. $selector.slideUp(400, function() {
  3216. $selector.remove();
  3217. });
  3218. $this.find('span').text(wpforms_builder.bulk_add_show);
  3219. } else {
  3220. var importOptions = '<div class="bulk-add-display unfoldable-cont">';
  3221. importOptions += '<p class="heading wpforms-clear">'+wpforms_builder.bulk_add_heading+' <a href="#" class="toggle-bulk-add-presets">'+wpforms_builder.bulk_add_presets_show+'</a></p>';
  3222. importOptions += '<ul>';
  3223. for(var key in wpforms_preset_choices) {
  3224. importOptions += '<li><a href="#" data-preset="'+key+'" class="bulk-add-preset-insert">'+wpforms_preset_choices[key].name+'</a></li>';
  3225. }
  3226. importOptions += '</ul>';
  3227. importOptions += '<textarea placeholder="' + wpforms_builder.bulk_add_placeholder + '"></textarea>';
  3228. importOptions += '<button class="bulk-add-insert wpforms-btn wpforms-btn-sm wpforms-btn-blue">' + wpforms_builder.bulk_add_button + '</button>';
  3229. importOptions += '</div>';
  3230. $label.after(importOptions);
  3231. $label.next('.bulk-add-display').slideDown(400, function() {
  3232. $(this).find('textarea').focus();
  3233. });
  3234. $this.find('span').text(wpforms_builder.bulk_add_hide);
  3235. }
  3236. $this.toggleClass('bulk-add-showing');
  3237. },
  3238. /**
  3239. * Field choice bulk insert the new choices.
  3240. *
  3241. * @since 1.3.7
  3242. *
  3243. * @param {object} el DOM element.
  3244. */
  3245. fieldChoiceBulkAddInsert: function( el ) {
  3246. var $this = $( el ),
  3247. $container = $this.closest( '.wpforms-field-option-row' ),
  3248. $textarea = $container.find( 'textarea' ),
  3249. $list = $container.find( '.choices-list' ),
  3250. $choice = $list.find( 'li:first-of-type' ).clone().wrap( '<div>' ).parent(),
  3251. choice = '',
  3252. fieldID = $container.data( 'field-id' ),
  3253. type = $list.data( 'field-type' ),
  3254. nextID = Number( $list.attr( 'data-next-id' ) ),
  3255. newValues = $textarea.val().split( '\n' ),
  3256. newChoices = '';
  3257. $this.prop( 'disabled', true ).html( $this.html() + ' ' + s.spinner );
  3258. $choice.find( 'input.value,input.label' ).attr( 'value', '' );
  3259. choice = $choice.html();
  3260. for ( var key in newValues ) {
  3261. if ( ! newValues.hasOwnProperty( key ) ) {
  3262. continue;
  3263. }
  3264. var value = wpf.sanitizeHTML( newValues[ key ] ).trim().replace( /"/g, '&quot;' ),
  3265. newChoice = choice;
  3266. newChoice = newChoice.replace( /\[choices\]\[(\d+)\]/g, '[choices][' + nextID + ']' );
  3267. newChoice = newChoice.replace( /data-key="(\d+)"/g, 'data-key="' + nextID + '"' );
  3268. newChoice = newChoice.replace( /value="" class="label"/g, 'value="' + value + '" class="label"' );
  3269. // For some reasons IE has its own attribute order.
  3270. newChoice = newChoice.replace( /class="label" type="text" value=""/g, 'class="label" type="text" value="' + value + '"' );
  3271. newChoices += newChoice;
  3272. nextID++;
  3273. }
  3274. $list.attr( 'data-next-id', nextID ).append( newChoices );
  3275. app.fieldChoiceUpdate( type, fieldID );
  3276. $builder.trigger( 'wpformsFieldChoiceAdd' );
  3277. app.fieldChoiceBulkAddToggle( $container.find( '.toggle-bulk-add-display' ) );
  3278. },
  3279. /**
  3280. * Toggle fields tabs (Add Fields, Field Options.
  3281. *
  3282. * @since 1.0.0
  3283. */
  3284. fieldTabToggle: function( id ) {
  3285. $( '.wpforms-tab a' ).removeClass( 'active' );
  3286. $( '.wpforms-field, .wpforms-title-desc' ).removeClass( 'active' );
  3287. if ( id === 'add-fields' ) {
  3288. $( '#add-fields a' ).addClass( 'active' );
  3289. $( '.wpforms-field-options' ).hide();
  3290. $( '.wpforms-add-fields' ).show();
  3291. } else {
  3292. $( '#field-options a' ).addClass( 'active' );
  3293. if ( id === 'field-options' ) {
  3294. var $field = $( '.wpforms-field' ).first();
  3295. $field.addClass( 'active' );
  3296. id = $field.data( 'field-id' );
  3297. } else {
  3298. $( '#wpforms-field-' + id ).addClass( 'active' );
  3299. }
  3300. $( '.wpforms-field-option' ).hide();
  3301. $( '#wpforms-field-option-' + id ).show();
  3302. $( '.wpforms-add-fields' ).hide();
  3303. $( '.wpforms-field-options' ).show();
  3304. }
  3305. },
  3306. /**
  3307. * Watches fields being added and listens for a pagebreak field.
  3308. *
  3309. * If a pagebreak field is added, and it's the first one, then we
  3310. * automatically add the top and bottom pagebreak elements to the
  3311. * builder.
  3312. *
  3313. * @param {object} event Current DOM event.
  3314. * @param {number} id Field ID.
  3315. * @param {string} type Field type.
  3316. *
  3317. * @since 1.2.1
  3318. */
  3319. fieldPagebreakAdd: function( event, id, type ) {
  3320. if ( 'pagebreak' !== type ) {
  3321. return;
  3322. }
  3323. var options;
  3324. if ( ! s.pagebreakTop ) {
  3325. s.pagebreakTop = true;
  3326. options = {
  3327. position: 'top',
  3328. scroll: false,
  3329. defaults: {
  3330. position: 'top',
  3331. nav_align: 'left',
  3332. },
  3333. };
  3334. app.fieldAdd( 'pagebreak', options ).done( function( res ) {
  3335. s.pagebreakTop = res.data.field.id;
  3336. var $preview = $( '#wpforms-field-' + res.data.field.id ),
  3337. $options = $( '#wpforms-field-option-' + res.data.field.id );
  3338. $options.find( '.wpforms-field-option-group' ).addClass( 'wpforms-pagebreak-top' );
  3339. $preview.addClass( 'wpforms-field-stick wpforms-pagebreak-top' );
  3340. } );
  3341. } else if ( ! s.pagebreakBottom ) {
  3342. s.pagebreakBottom = true;
  3343. options = {
  3344. position: 'bottom',
  3345. scroll: false,
  3346. defaults: {
  3347. position: 'bottom',
  3348. },
  3349. };
  3350. app.fieldAdd( 'pagebreak', options ).done( function( res ) {
  3351. s.pagebreakBottom = res.data.field.id;
  3352. var $preview = $( '#wpforms-field-' + res.data.field.id ),
  3353. $options = $( '#wpforms-field-option-' + res.data.field.id );
  3354. $options.find( '.wpforms-field-option-group' ).addClass( 'wpforms-pagebreak-bottom' );
  3355. $preview.addClass( 'wpforms-field-stick wpforms-pagebreak-bottom' );
  3356. } );
  3357. }
  3358. },
  3359. /**
  3360. * Watches fields being deleted and listens for a pagebreak field.
  3361. *
  3362. * If a pagebreak field is added, and it's the first one, then we
  3363. * automatically add the top and bottom pagebreak elements to the
  3364. * builder.
  3365. *
  3366. * @param {object} event Current DOM event.
  3367. * @param {number} id Field ID.
  3368. * @param {string} type Field type.
  3369. *
  3370. * @since 1.2.1
  3371. */
  3372. fieldPagebreakDelete: function( event, id, type ) {
  3373. if ( 'pagebreak' !== type ) {
  3374. return;
  3375. }
  3376. var pagebreaksRemaining = $( '.wpforms-field-pagebreak' ).not( '.wpforms-pagebreak-top, .wpforms-pagebreak-bottom' ).length;
  3377. if ( pagebreaksRemaining ) {
  3378. return;
  3379. }
  3380. // All pagebreaks, excluding top/bottom, are gone.
  3381. // So we need to remove the top and bottom pagebreak.
  3382. var $preview = $( '.wpforms-preview-wrap' ),
  3383. $top = $preview.find( '.wpforms-pagebreak-top' ),
  3384. topID = $top.data( 'field-id' ),
  3385. $bottom = $preview.find( '.wpforms-pagebreak-bottom' ),
  3386. bottomID = $bottom.data( 'field-id' );
  3387. $top.remove();
  3388. $( '#wpforms-field-option-' + topID ).remove();
  3389. s.pagebreakTop = false;
  3390. $bottom.remove();
  3391. $( '#wpforms-field-option-' + bottomID ).remove();
  3392. s.pagebreakBottom = false;
  3393. },
  3394. /**
  3395. * Init Display Previous option for Pagebreak field.
  3396. *
  3397. * @since 1.5.8
  3398. *
  3399. * @param {jQuery} $field Page Break field jQuery object.
  3400. */
  3401. fieldPageBreakInitDisplayPrevious: function( $field ) {
  3402. var id = $field.data( 'field-id' ),
  3403. $prevToggle = $( '#wpforms-field-option-row-' + id + '-prev_toggle' ),
  3404. $prev = $( '#wpforms-field-option-row-' + id + '-prev' ),
  3405. $prevBtn = $field.find( '.wpforms-pagebreak-prev' );
  3406. if ( $field.prevAll( '.wpforms-field-pagebreak.wpforms-pagebreak-normal' ).length > 0 ) {
  3407. $prevToggle.removeClass( 'hidden' );
  3408. $prev.removeClass( 'hidden' );
  3409. if ( $prevToggle.find( 'input' ).is( ':checked' ) ) {
  3410. $prevBtn.removeClass( 'wpforms-hidden' ).text( $prev.find( 'input' ).val() );
  3411. }
  3412. } else {
  3413. $prevToggle.addClass( 'hidden' );
  3414. $prev.addClass( 'hidden' );
  3415. $prevBtn.addClass( 'wpforms-hidden' );
  3416. }
  3417. },
  3418. /**
  3419. * Field Dynamic Choice toggle.
  3420. *
  3421. * @since 1.2.8
  3422. */
  3423. fieldDynamicChoiceToggle: function( el ) {
  3424. var $this = $( el ),
  3425. $thisOption = $this.parent(),
  3426. value = $this.val(),
  3427. id = $thisOption.data( 'field-id' ),
  3428. type = $( '#wpforms-field-option-' + id ).find( '.wpforms-field-option-hidden-type' ).val(),
  3429. $field = $( '#wpforms-field-' + id ),
  3430. $choices = $( '#wpforms-field-option-row-' + id + '-choices' ),
  3431. $images = $( '#wpforms-field-option-' + id + '-choices_images' );
  3432. // Hide image choices if dynamic choices is not off.
  3433. app.fieldDynamicChoiceToggleImageChoices();
  3434. // Fire an event when a field's dynamic choices option was changed.
  3435. $builder.trigger( 'wpformsFieldDynamicChoiceToggle' );
  3436. // Loading
  3437. wpf.fieldOptionLoading( $thisOption );
  3438. // Remove previous dynamic post type or taxonomy source options.
  3439. $( '#wpforms-field-option-row-' + id + '-dynamic_post_type' ).remove();
  3440. $( '#wpforms-field-option-row-' + id + '-dynamic_taxonomy' ).remove();
  3441. /*
  3442. * Post type or Taxonomy based dynamic populating.
  3443. */
  3444. if ( '' !== value ) {
  3445. // Hide choice images option, not applicable.
  3446. $images.addClass( 'wpforms-hidden' );
  3447. // Hide `Bulk Add` toggle.
  3448. $choices.find( '.toggle-bulk-add-display' ).addClass( 'wpforms-hidden' );
  3449. var data = {
  3450. type : value,
  3451. field_id: id, // eslint-disable-line camelcase
  3452. action : 'wpforms_builder_dynamic_choices',
  3453. nonce : wpforms_builder.nonce,
  3454. };
  3455. $.post( wpforms_builder.ajax_url, data, function( res ) {
  3456. if ( res.success ) {
  3457. // New option markup.
  3458. $thisOption.after( res.data.markup );
  3459. } else {
  3460. console.log( res );
  3461. }
  3462. // Hide loading indicator.
  3463. wpf.fieldOptionLoading( $thisOption, true );
  3464. // Re-init tooltips for new field.
  3465. wpf.initTooltips();
  3466. // Trigger Dynamic source updates.
  3467. $( '#wpforms-field-option-' + id + '-dynamic_' + value ).find( 'option:first' ).prop( 'selected', true );
  3468. $( '#wpforms-field-option-' + id + '-dynamic_' + value ).trigger( 'change' );
  3469. } ).fail( function( xhr, textStatus, e ) {
  3470. console.log( xhr.responseText );
  3471. } );
  3472. return; // Nothing more for dynamic populating.
  3473. }
  3474. /*
  3475. * "Off" - no dynamic populating.
  3476. */
  3477. // Show choice images option.
  3478. $images.removeClass( 'wpforms-hidden' );
  3479. // Show `Bulk Add` toggle.
  3480. $choices.find( '.toggle-bulk-add-display' ).removeClass( 'wpforms-hidden' );
  3481. $( '#wpforms-field-' + id ).find( '.wpforms-alert' ).remove();
  3482. if ( [ 'checkbox', 'radio', 'payment-multiple', 'payment-checkbox' ].indexOf( type ) > -1 ) {
  3483. app.fieldChoiceUpdate( type, id );
  3484. // Toggle elements and hide loading indicator.
  3485. $choices.find( 'ul' ).removeClass( 'wpforms-hidden' );
  3486. $choices.find( '.wpforms-alert' ).addClass( 'wpforms-hidden' );
  3487. wpf.fieldOptionLoading( $thisOption, true );
  3488. return; // Nothing more for those types.
  3489. }
  3490. // Get original field choices.
  3491. var choices = [],
  3492. $primary = $field.find( '.primary-input' ),
  3493. key;
  3494. $( '#wpforms-field-option-row-' + id + '-choices li' ).each( function() {
  3495. var $this = $( this );
  3496. choices.push( {
  3497. label: wpf.sanitizeHTML( $this.find( '.label' ).val() ),
  3498. selected: $this.find( '.default' ).is( ':checked' ),
  3499. } );
  3500. } );
  3501. // Restore field to display original field choices.
  3502. if ( $field.hasClass( 'wpforms-field-select' ) ) {
  3503. var isModernSelect = app.dropdownField.helpers.isModernSelect( $primary ),
  3504. optionHTML = '',
  3505. selected = false;
  3506. // Remove previous items.
  3507. $primary.find( 'option' ).not( '.placeholder' ).remove();
  3508. // Update Modern Dropdown.
  3509. if ( isModernSelect && choices.length ) {
  3510. app.dropdownField.helpers.update( id, false );
  3511. } else {
  3512. // Update Classic select field.
  3513. for ( key in choices ) {
  3514. selected = choices[ key ].selected;
  3515. optionHTML = '<option';
  3516. optionHTML += selected ? ' selected>' : '>';
  3517. optionHTML += choices[ key ].label + '</option>';
  3518. $primary.append( optionHTML );
  3519. }
  3520. }
  3521. } else {
  3522. type = 'radio';
  3523. if ( $field.hasClass( 'wpforms-field-checkbox' ) ) {
  3524. type = 'checkbox';
  3525. }
  3526. // Remove previous items.
  3527. $primary.empty();
  3528. // Add new items to radio or checkbox field.
  3529. for ( key in choices ) {
  3530. optionHTML = '<li><input type="' + type + '" disabled';
  3531. optionHTML += choices[ key ].selected ? ' selected>' : '>';
  3532. optionHTML += choices[ key ].label + '</li>';
  3533. $primary.append( optionHTML );
  3534. }
  3535. }
  3536. // Toggle elements and hide loading indicator.
  3537. $choices.find( 'ul' ).removeClass( 'wpforms-hidden' );
  3538. $choices.find( '.wpforms-alert' ).addClass( 'wpforms-hidden' );
  3539. wpf.fieldOptionLoading( $thisOption, true );
  3540. },
  3541. /**
  3542. * Field Dynamic Choice Source toggle.
  3543. *
  3544. * @since 1.2.8
  3545. */
  3546. fieldDynamicChoiceSource: function( el ) {
  3547. var $this = $( el ),
  3548. $thisOption = $this.parent(),
  3549. value = $this.val(),
  3550. id = $thisOption.data( 'field-id' ),
  3551. form_id = $( '#wpforms-builder-form' ).data( 'id' ),
  3552. $choices = $( '#wpforms-field-option-row-' + id + '-choices' ),
  3553. $field = $( '#wpforms-field-' + id ),
  3554. type = $( '#wpforms-field-option-' + id + '-dynamic_choices option:selected' ).val(),
  3555. limit = 20;
  3556. // Loading.
  3557. wpf.fieldOptionLoading( $thisOption );
  3558. var data = {
  3559. type : type,
  3560. source : value,
  3561. field_id: id,
  3562. form_id : form_id,
  3563. action : 'wpforms_builder_dynamic_source',
  3564. nonce : wpforms_builder.nonce
  3565. };
  3566. $.post( wpforms_builder.ajax_url, data, function( res ) {
  3567. if ( ! res.success ) {
  3568. console.log( res );
  3569. // Toggle elements and hide loading indicator.
  3570. wpf.fieldOptionLoading( $thisOption, true );
  3571. return;
  3572. }
  3573. // Update info box and remove old choices.
  3574. $choices.find( '.dynamic-name' ).text( res.data.source_name );
  3575. $choices.find( '.dynamic-type' ).text( res.data.type_name );
  3576. $choices.find( 'ul' ).addClass( 'wpforms-hidden' );
  3577. $choices.find( '.wpforms-alert' ).removeClass( 'wpforms-hidden' );
  3578. // Update items.
  3579. app.fieldDynamicChoiceSourceItems( $field, res.data.items );
  3580. if ( $field.hasClass( 'wpforms-field-select' ) ) {
  3581. limit = 200;
  3582. }
  3583. // If the source has more items than the field type can
  3584. // ideally handle alert the user.
  3585. if ( Number( res.data.total ) > limit ) {
  3586. var msg = wpforms_builder.dynamic_choice_limit;
  3587. msg = msg.replace( '{source}', res.data.source_name );
  3588. msg = msg.replace( '{type}', res.data.type_name );
  3589. msg = msg.replace( '{limit}', limit );
  3590. msg = msg.replace( '{total}', res.data.total );
  3591. $.alert( {
  3592. title: wpforms_builder.heads_up,
  3593. content: msg,
  3594. icon: 'fa fa-info-circle',
  3595. type: 'blue',
  3596. buttons: {
  3597. confirm: {
  3598. text: wpforms_builder.ok,
  3599. btnClass: 'btn-confirm',
  3600. keys: [ 'enter' ],
  3601. },
  3602. },
  3603. } );
  3604. }
  3605. // Toggle limit choices alert message.
  3606. app.firstNChoicesAlert( id, res.data.total );
  3607. // Toggle elements and hide loading indicator.
  3608. wpf.fieldOptionLoading( $thisOption, true );
  3609. } ).fail( function( xhr, textStatus, e ) {
  3610. console.log( xhr.responseText );
  3611. } );
  3612. },
  3613. /**
  3614. * Update a Field Items when `Dynamic Choice` Source is toggled.
  3615. *
  3616. * @since 1.6.1
  3617. *
  3618. * @param {object} $field jQuery selector for current field.
  3619. * @param {object} items Items collection.
  3620. */
  3621. fieldDynamicChoiceSourceItems: function( $field, items ) {
  3622. var $primary = $field.find( '.primary-input' ),
  3623. key = 0;
  3624. if ( $field.hasClass( 'wpforms-field-select' ) ) {
  3625. var isModernSelect = app.dropdownField.helpers.isModernSelect( $primary );
  3626. if ( isModernSelect ) {
  3627. app.fieldDynamicChoiceSourceForModernSelect( $primary, items );
  3628. } else {
  3629. app.fieldDynamicChoiceSourceForClassicSelect( $primary, items );
  3630. }
  3631. } else {
  3632. var type = 'radio';
  3633. if ( $field.hasClass( 'wpforms-field-checkbox' ) ) {
  3634. type = 'checkbox';
  3635. }
  3636. // Remove previous items.
  3637. $primary.empty();
  3638. // Add new items to radio or checkbox field.
  3639. for ( key in items ) {
  3640. $primary.append( '<li><input type="' + type + '" disabled> ' + wpf.sanitizeHTML( items[ key ] ) + '</li>' );
  3641. }
  3642. }
  3643. },
  3644. /**
  3645. * Update options for Modern style select when `Dynamic Choice` Source is toggled.
  3646. *
  3647. * @since 1.6.1
  3648. *
  3649. * @param {object} $jquerySelector jQuery selector for primary input.
  3650. * @param {object} items Items collection.
  3651. */
  3652. fieldDynamicChoiceSourceForModernSelect: function( $jquerySelector, items ) {
  3653. var instance = app.dropdownField.helpers.getInstance( $jquerySelector ),
  3654. fieldId = $jquerySelector.closest( '.wpforms-field' ).data().fieldId;
  3655. // Destroy the instance of Choices.js.
  3656. instance.destroy();
  3657. // Update a placeholder.
  3658. app.dropdownField.helpers.updatePlaceholderChoice( instance, fieldId );
  3659. // Update options.
  3660. app.fieldDynamicChoiceSourceForClassicSelect( $jquerySelector, items );
  3661. // Choices.js init.
  3662. app.dropdownField.events.choicesInit( $jquerySelector );
  3663. },
  3664. /**
  3665. * Update options for Classic style select when `Dynamic Choice` Source is toggled.
  3666. *
  3667. * @since 1.6.1
  3668. *
  3669. * @param {object} $jquerySelector jQuery selector for primary input.
  3670. * @param {object} items Items collection.
  3671. */
  3672. fieldDynamicChoiceSourceForClassicSelect: function( $jquerySelector, items ) {
  3673. var index = 0,
  3674. itemsSize = items.length;
  3675. // Clear.
  3676. $jquerySelector.find( 'option' ).not( '.placeholder' ).remove();
  3677. // Add options (items) to a single <select> field.
  3678. for ( ; index < itemsSize; index++ ) {
  3679. var item = wpf.sanitizeHTML( items[ index ] );
  3680. $jquerySelector.append( '<option value="' + item + '">' + item + '</option>' );
  3681. }
  3682. },
  3683. /**
  3684. * Image choice toggle, hide image choices, image choices style, choices if Dynamic choices is not OFF.
  3685. *
  3686. * @since 1.5.8
  3687. */
  3688. fieldDynamicChoiceToggleImageChoices: function() {
  3689. $( '#wpforms-builder .wpforms-field-options .wpforms-field-option' ).each(
  3690. function( key, value ) {
  3691. var $option = $( value ),
  3692. dynamicSelect = $option.find( '.wpforms-field-option-row-dynamic_choices select' );
  3693. if (
  3694. typeof dynamicSelect.val() !== 'undefined' &&
  3695. '' !== dynamicSelect.val()
  3696. ) {
  3697. $option.find( '.wpforms-field-option-row-choices_images' ).hide();
  3698. $option.find( '.wpforms-field-option-row-choices_images_style' ).hide();
  3699. } else {
  3700. $option.find( '.wpforms-field-option-row-choices_images' ).show();
  3701. $option.find( '.wpforms-field-option-row-choices_images_style' ).show();
  3702. }
  3703. }
  3704. );
  3705. },
  3706. /**
  3707. * Show choices limit alert message.
  3708. *
  3709. * @since 1.6.9
  3710. *
  3711. * @param {number} fieldId Field ID.
  3712. * @param {number} total Total number of choices.
  3713. */
  3714. firstNChoicesAlert: function( fieldId, total ) {
  3715. var tmpl = wp.template( 'wpforms-choices-limit-message' ),
  3716. data = {
  3717. total: total,
  3718. },
  3719. limit = 20,
  3720. $field = $( '#wpforms-field-' + fieldId );
  3721. // Don't show message for select fields.
  3722. if ( $field.hasClass( 'wpforms-field-select' ) ) {
  3723. return;
  3724. }
  3725. $field.find( '.wpforms-alert-dynamic' ).remove();
  3726. if ( total > limit ) {
  3727. $field.find( '.primary-input' ).after( tmpl( data ) );
  3728. }
  3729. },
  3730. /**
  3731. * Field layout selector toggling.
  3732. *
  3733. * @since 1.3.7
  3734. */
  3735. fieldLayoutSelectorToggle: function(el) {
  3736. var $this = $(el),
  3737. $label = $this.closest('label'),
  3738. layouts = {
  3739. 'layout-1' : [
  3740. {
  3741. 'class': 'one-half',
  3742. 'data' : 'wpforms-one-half wpforms-first'
  3743. },
  3744. {
  3745. 'class': 'one-half',
  3746. 'data' : 'wpforms-one-half'
  3747. }
  3748. ],
  3749. 'layout-2' : [
  3750. {
  3751. 'class': 'one-third',
  3752. 'data' : 'wpforms-one-third wpforms-first'
  3753. },
  3754. {
  3755. 'class': 'one-third',
  3756. 'data' : 'wpforms-one-third'
  3757. },
  3758. {
  3759. 'class': 'one-third',
  3760. 'data' : 'wpforms-one-third'
  3761. }
  3762. ],
  3763. 'layout-3' : [
  3764. {
  3765. 'class': 'one-fourth',
  3766. 'data' : 'wpforms-one-fourth wpforms-first'
  3767. },
  3768. {
  3769. 'class': 'one-fourth',
  3770. 'data' : 'wpforms-one-fourth'
  3771. },
  3772. {
  3773. 'class': 'one-fourth',
  3774. 'data' : 'wpforms-one-fourth'
  3775. },
  3776. {
  3777. 'class': 'one-fourth',
  3778. 'data' : 'wpforms-one-fourth'
  3779. }
  3780. ],
  3781. 'layout-4' : [
  3782. {
  3783. 'class': 'one-third',
  3784. 'data' : 'wpforms-one-third wpforms-first'
  3785. },
  3786. {
  3787. 'class': 'two-third',
  3788. 'data' : 'wpforms-two-thirds'
  3789. }
  3790. ],
  3791. 'layout-5' : [
  3792. {
  3793. 'class': 'two-third',
  3794. 'data' : 'wpforms-two-thirds wpforms-first'
  3795. },
  3796. {
  3797. 'class': 'one-third',
  3798. 'data' : 'wpforms-one-third'
  3799. }
  3800. ],
  3801. 'layout-6' : [
  3802. {
  3803. 'class': 'one-fourth',
  3804. 'data' : 'wpforms-one-fourth wpforms-first'
  3805. },
  3806. {
  3807. 'class': 'one-fourth',
  3808. 'data' : 'wpforms-one-fourth'
  3809. },
  3810. {
  3811. 'class': 'two-fourth',
  3812. 'data' : 'wpforms-two-fourths'
  3813. }
  3814. ],
  3815. 'layout-7' : [
  3816. {
  3817. 'class': 'two-fourth',
  3818. 'data' : 'wpforms-two-fourths wpforms-first'
  3819. },
  3820. {
  3821. 'class': 'one-fourth',
  3822. 'data' : 'wpforms-one-fourth'
  3823. },
  3824. {
  3825. 'class': 'one-fourth',
  3826. 'data' : 'wpforms-one-fourth'
  3827. }
  3828. ],
  3829. 'layout-8' : [
  3830. {
  3831. 'class': 'one-fourth',
  3832. 'data' : 'wpforms-one-fourth wpforms-first'
  3833. },
  3834. {
  3835. 'class': 'two-fourth',
  3836. 'data' : 'wpforms-two-fourths'
  3837. },
  3838. {
  3839. 'class': 'one-fourth',
  3840. 'data' : 'wpforms-one-fourth'
  3841. }
  3842. ]
  3843. };
  3844. if ( $this.hasClass('layout-selector-showing') ) {
  3845. // Selector is showing, so hide/remove it
  3846. var $selector = $label.next('.layout-selector-display');
  3847. $selector.slideUp(400, function() {
  3848. $selector.remove();
  3849. });
  3850. $this.find('span').text(wpforms_builder.layout_selector_show);
  3851. } else {
  3852. // Create selector options
  3853. var layoutOptions = '<div class="layout-selector-display unfoldable-cont">';
  3854. layoutOptions += '<p class="heading">' + wpforms_builder.layout_selector_layout + '</p>';
  3855. layoutOptions += '<div class="layouts">';
  3856. for ( var key in layouts ) {
  3857. var layout = layouts[ key ];
  3858. layoutOptions += '<div class="layout-selector-display-layout">';
  3859. for ( var i in layout ) {
  3860. layoutOptions += '<span class="' + layout[ i ].class + '" data-classes="' + layout[ i ].data + '"></span>';
  3861. }
  3862. layoutOptions += '</div>';
  3863. }
  3864. layoutOptions += '</div></div>';
  3865. $label.after( layoutOptions );
  3866. $label.next( '.layout-selector-display' ).slideDown();
  3867. $this.find( 'span' ).text( wpforms_builder.layout_selector_hide );
  3868. }
  3869. $this.toggleClass( 'layout-selector-showing' );
  3870. },
  3871. /**
  3872. * Field layout selector, selecting a layout.
  3873. *
  3874. * @since 1.3.7
  3875. */
  3876. fieldLayoutSelectorLayout: function(el) {
  3877. var $this = $(el),
  3878. $label = $this.closest('label');
  3879. $this.parent().find('.layout-selector-display-layout').not($this).remove();
  3880. $this.parent().find('.heading').text(wpforms_builder.layout_selector_column);
  3881. $this.toggleClass('layout-selector-display-layout layout-selector-display-columns')
  3882. },
  3883. /**
  3884. * Field layout selector, insert into class field.
  3885. *
  3886. * @since 1.3.7
  3887. */
  3888. fieldLayoutSelectorInsert: function(el) {
  3889. var $this = $(el),
  3890. $selector = $this.closest('.layout-selector-display'),
  3891. $parent = $selector.parent(),
  3892. $label = $parent.find('label'),
  3893. $input = $parent.find('input[type=text]'),
  3894. classes = $this.data('classes');
  3895. if ( $input.val() ) {
  3896. classes = ' ' + classes;
  3897. }
  3898. $input.insertAtCaret(classes);
  3899. // remove list, all done!
  3900. $selector.slideUp(400, function() {
  3901. $selector.remove();
  3902. });
  3903. $label.find('.toggle-layout-selector-display').removeClass('layout-selector-showing');
  3904. $label.find('.toggle-layout-selector-display span').text(wpforms_builder.layout_selector_show);
  3905. },
  3906. //--------------------------------------------------------------------//
  3907. // Settings Panel
  3908. //--------------------------------------------------------------------//
  3909. /**
  3910. * Element bindings for Settings panel.
  3911. *
  3912. * @since 1.0.0
  3913. */
  3914. bindUIActionsSettings: function() {
  3915. // Clicking form title/desc opens Settings panel.
  3916. $builder.on( 'click', '.wpforms-title-desc, .wpforms-field-submit-button, .wpforms-center-form-name', function( e ) {
  3917. e.preventDefault();
  3918. app.panelSwitch( 'settings' );
  3919. if ( $( this ).hasClass( 'wpforms-center-form-name' ) || $( this ).hasClass( 'wpforms-title-desc' ) ) {
  3920. setTimeout( function() {
  3921. $( '#wpforms-panel-field-settings-form_title' ).focus();
  3922. }, 300 );
  3923. }
  3924. } );
  3925. // Clicking form previous page break button.
  3926. $builder.on( 'click', '.wpforms-field-pagebreak-last button', function( e ) {
  3927. e.preventDefault();
  3928. app.panelSwitch( 'settings' );
  3929. $( '#wpforms-panel-field-settings-pagebreak_prev' ).focus();
  3930. } );
  3931. // Clicking form last page break button.
  3932. $builder.on( 'input', '#wpforms-panel-field-settings-pagebreak_prev', function() {
  3933. $( '.wpforms-field-pagebreak-last button' ).text( $( this ).val() );
  3934. } );
  3935. // Real-time updates for editing the form title.
  3936. $builder.on( 'input', '#wpforms-panel-field-settings-form_title, #wpforms-setup-name', function() {
  3937. var title = $.trim( $( this ).val() );
  3938. $( '.wpforms-preview .wpforms-form-name' ).text( title );
  3939. $( '.wpforms-center-form-name.wpforms-form-name' ).text( title );
  3940. app.trimFormTitle();
  3941. } );
  3942. // Real-time updates for editing the form description.
  3943. $builder.on( 'input', '#wpforms-panel-field-settings-form_desc', function() {
  3944. $( '.wpforms-form-desc' ).text( $( this ).val() );
  3945. } );
  3946. // Real-time updates for editing the form submit button.
  3947. $builder.on( 'input', '#wpforms-panel-field-settings-submit_text', function() {
  3948. $( '.wpforms-field-submit input[type=submit]' ).val( $( this ).val() );
  3949. } );
  3950. // Toggle form reCAPTCHA setting.
  3951. $builder.on( 'change', '#wpforms-panel-field-settings-recaptcha', function() {
  3952. app.captchaToggle();
  3953. } );
  3954. // Toggle form confirmation setting fields.
  3955. $builder.on( 'change', '.wpforms-panel-field-confirmations-type', function() {
  3956. app.confirmationFieldsToggle( $( this ) );
  3957. } );
  3958. $builder.on( 'change', '.wpforms-panel-field-confirmations-message_entry_preview', app.confirmationEntryPreviewToggle );
  3959. // Toggle form notification setting fields.
  3960. $builder.on( 'change', '#wpforms-panel-field-settings-notification_enable', function() {
  3961. app.notificationToggle();
  3962. } );
  3963. // Add new settings block.
  3964. $builder.on( 'click', '.wpforms-builder-settings-block-add', function( e ) {
  3965. e.preventDefault();
  3966. if ( ! wpforms_builder.pro ) {
  3967. return;
  3968. }
  3969. app.settingsBlockAdd( $( this ) );
  3970. } );
  3971. // Edit settings block name.
  3972. $builder.on( 'click', '.wpforms-builder-settings-block-edit', function( e ) {
  3973. e.preventDefault();
  3974. var $el = $( this );
  3975. if ( $el.parents( '.wpforms-builder-settings-block-header' ).find( '.wpforms-builder-settings-block-name' ).hasClass( 'editing' ) ) {
  3976. app.settingsBlockNameEditingHide( $el );
  3977. } else {
  3978. app.settingsBlockNameEditingShow( $el );
  3979. }
  3980. } );
  3981. // Update settings block name and close editing interface.
  3982. $builder.on( 'blur', '.wpforms-builder-settings-block-name-edit input', function( e ) {
  3983. // Do not fire if for onBlur user clicked on edit button - it has own event processing.
  3984. if ( ! $( e.relatedTarget ).hasClass( 'wpforms-builder-settings-block-edit' ) ) {
  3985. app.settingsBlockNameEditingHide( $( this ) );
  3986. }
  3987. } );
  3988. // Close settings block editing interface with pressed Enter.
  3989. $builder.on( 'keypress', '.wpforms-builder-settings-block-name-edit input', function( e ) {
  3990. // On Enter - hide editing interface.
  3991. if ( e.keyCode === 13 ) {
  3992. app.settingsBlockNameEditingHide( $( this ) );
  3993. // We need this preventDefault() to stop jumping to form name editing input.
  3994. e.preventDefault();
  3995. }
  3996. } );
  3997. // Clone settings block.
  3998. $builder.on( 'click', '.wpforms-builder-settings-block-clone', function( e ) {
  3999. e.preventDefault();
  4000. app.settingsBlockPanelClone( $( this ) );
  4001. } );
  4002. // Toggle settings block - slide up or down.
  4003. $builder.on( 'click', '.wpforms-builder-settings-block-toggle', function( e ) {
  4004. e.preventDefault();
  4005. app.settingsBlockPanelToggle( $( this ) );
  4006. } );
  4007. // Remove settings block.
  4008. $builder.on( 'click', '.wpforms-builder-settings-block-delete', function( e ) {
  4009. e.preventDefault();
  4010. app.settingsBlockDelete( $( this ) );
  4011. } );
  4012. },
  4013. /**
  4014. * Toggle displaying the CAPTCHA.
  4015. *
  4016. * @since 1.6.4
  4017. */
  4018. captchaToggle: function() {
  4019. var $preview = $builder.find( '.wpforms-field-recaptcha' ),
  4020. $setting = $( '#wpforms-panel-field-settings-recaptcha' ),
  4021. provider = $setting.data( 'provider' );
  4022. provider = provider || 'recaptcha';
  4023. if ( ! $preview.length ) {
  4024. return;
  4025. }
  4026. if ( $setting.is( ':checked' ) ) {
  4027. $preview
  4028. .show()
  4029. .toggleClass( 'is-recaptcha', 'recaptcha' === provider );
  4030. } else {
  4031. $preview.hide();
  4032. }
  4033. },
  4034. /**
  4035. * Setup the Confirmation blocks.
  4036. *
  4037. * @since 1.4.8
  4038. */
  4039. confirmationsSetup: function() {
  4040. // Toggle the setting fields in each confirmation block.
  4041. $( '.wpforms-panel-field-confirmations-type' ).each( function() {
  4042. app.confirmationFieldsToggle( $( this ) );
  4043. } );
  4044. // Init TinyMCE in each confirmation block.
  4045. $( '.wpforms-panel-field-confirmations-message' ).each( function() {
  4046. if ( typeof tinymce !== 'undefined' && typeof wp.editor !== 'undefined' ) {
  4047. wp.editor.initialize( $( this ).attr( 'id' ), s.tinymceDefaults );
  4048. }
  4049. } );
  4050. // Validate Confirmation Redirect URL.
  4051. $builder.on( 'focusout', '.wpforms-panel-field-confirmations-redirect', function( event ) {
  4052. var $field = $( this ),
  4053. url = $field.val().trim();
  4054. $field.val( url );
  4055. if ( wpf.isURL( url ) || url === '' ) {
  4056. return;
  4057. }
  4058. $.confirm( {
  4059. title: wpforms_builder.heads_up,
  4060. content: wpforms_builder.redirect_url_field_error,
  4061. icon: 'fa fa-exclamation-circle',
  4062. type: 'orange',
  4063. buttons: {
  4064. confirm: {
  4065. text: wpforms_builder.ok,
  4066. btnClass: 'btn-confirm',
  4067. keys: [ 'enter' ],
  4068. action: function() {
  4069. $field.focus();
  4070. },
  4071. },
  4072. },
  4073. } );
  4074. } );
  4075. },
  4076. /**
  4077. * Toggle the different form Confirmation setting fields.
  4078. *
  4079. * @since 1.4.8
  4080. *
  4081. * @param {jQuery} $el Element.
  4082. */
  4083. confirmationFieldsToggle: function( $el ) {
  4084. if ( ! $el.length ) {
  4085. return;
  4086. }
  4087. var type = $el.val(),
  4088. $block = $el.closest( '.wpforms-builder-settings-block-content' );
  4089. $block.find( '.wpforms-panel-field' )
  4090. .not( $el.parent() )
  4091. .not( '.wpforms-conditionals-enable-toggle' )
  4092. .hide();
  4093. $block.find( '.wpforms-panel-field-confirmations-' + type ).closest( '.wpforms-panel-field' ).show();
  4094. if ( type === 'message' ) {
  4095. $block.find( '.wpforms-panel-field-confirmations-message_scroll' ).closest( '.wpforms-panel-field' ).show();
  4096. $block.find( '.wpforms-panel-field-confirmations-message_entry_preview' ).trigger( 'change' ).closest( '.wpforms-panel-field' ).show();
  4097. }
  4098. },
  4099. /**
  4100. * Show/hide an entry preview message.
  4101. *
  4102. * @since 1.6.9
  4103. */
  4104. confirmationEntryPreviewToggle: function() {
  4105. var $this = $( this ),
  4106. $styleField = $this.closest( '.wpforms-builder-settings-block-content' ).find( '.wpforms-panel-field-confirmations-message_entry_preview_style' ).parent();
  4107. $this.is( ':checked' ) ? $styleField.show() : $styleField.hide();
  4108. },
  4109. /**
  4110. * Toggle the displaying notification settings depending on if the
  4111. * notifications are enabled.
  4112. *
  4113. * @since 1.1.9
  4114. */
  4115. notificationToggle: function() {
  4116. var $notification = $( '#wpforms-panel-field-settings-notification_enable' ),
  4117. $settingsBlock = $notification.closest( '.wpforms-panel-content-section' ).find( '.wpforms-builder-settings-block' ),
  4118. $enabled = $notification.is( ':checked' );
  4119. // Toggle Add new notification button.
  4120. $( '.wpforms-notifications-add' ).toggleClass( 'wpforms-hidden', ! $enabled );
  4121. $enabled ? $settingsBlock.show() : $settingsBlock.hide();
  4122. },
  4123. /**
  4124. * Notifications by status alerts.
  4125. *
  4126. * @since 1.6.6
  4127. */
  4128. notificationsByStatusAlerts: function() {
  4129. $builder.on( 'change', '.wpforms-panel-content-section-notifications .wpforms-notification-by-status-alert', function( e ) {
  4130. var $input = $( this );
  4131. if ( ! $input.prop( 'checked' ) ) {
  4132. return;
  4133. }
  4134. var $enabled = $( '.wpforms-radio-group-' + $input.attr( 'data-radio-group' ) + ':checked:not(#' + $input.attr( 'id' ) + ')' ),
  4135. alertText = '';
  4136. if ( $enabled.length === 0 ) {
  4137. alertText = wpforms_builder.notification_by_status_enable_alert;
  4138. alertText = alertText.replace( /%s/g, $input.data( 'provider-title' ) );
  4139. } else {
  4140. alertText = wpforms_builder.notification_by_status_switch_alert;
  4141. alertText = alertText.replace( /%2\$s/g, $enabled.data( 'provider-title' ) );
  4142. alertText = alertText.replace( /%1\$s/g, $input.data( 'provider-title' ) );
  4143. }
  4144. $.confirm( {
  4145. title: wpforms_builder.heads_up,
  4146. content: alertText,
  4147. icon: 'fa fa-exclamation-circle',
  4148. type: 'orange',
  4149. buttons: {
  4150. confirm: {
  4151. text: wpforms_builder.ok,
  4152. btnClass: 'btn-confirm',
  4153. },
  4154. },
  4155. } );
  4156. } );
  4157. },
  4158. /**
  4159. * Add new settings block.
  4160. *
  4161. * @since 1.4.8
  4162. * @since 1.6.1 Added processing for Field Map table.
  4163. * @since 1.6.1.2 Registered `wpformsSettingsBlockAdded` trigger.
  4164. *
  4165. * @param {jQuery} $el Settings Block jQuery object.
  4166. */
  4167. settingsBlockAdd: function($el) {
  4168. var nextID = Number( $el.attr( 'data-next-id' ) ),
  4169. panelID = $el.closest( '.wpforms-panel-content-section' ).data( 'panel' ),
  4170. blockType = $el.data( 'block-type' ),
  4171. namePrompt = wpforms_builder[ blockType + '_prompt' ],
  4172. nameField = '<input autofocus="" type="text" id="settings-block-name" placeholder="' + wpforms_builder[ blockType + '_ph' ] + '">',
  4173. nameError = '<p class="error">' + wpforms_builder[ blockType + '_error' ] + '</p>',
  4174. modalContent = namePrompt + nameField + nameError;
  4175. var modal = $.confirm( {
  4176. container: $builder,
  4177. title: false,
  4178. content: modalContent,
  4179. icon: 'fa fa-info-circle',
  4180. type: 'blue',
  4181. buttons: {
  4182. confirm: {
  4183. text: wpforms_builder.ok,
  4184. btnClass: 'btn-confirm',
  4185. keys: [ 'enter' ],
  4186. action: function() {
  4187. var settingsBlockName = $.trim( this.$content.find( 'input#settings-block-name' ).val() ),
  4188. error = this.$content.find( '.error' );
  4189. if ( settingsBlockName === '' ) {
  4190. error.show();
  4191. return false;
  4192. } else {
  4193. var $firstSettingsBlock = $el.closest( '.wpforms-panel-content-section' ).find( '.wpforms-builder-settings-block' ).first();
  4194. // Restore tooltips before cloning.
  4195. wpf.restoreTooltips( $firstSettingsBlock );
  4196. var $newSettingsBlock = $firstSettingsBlock.clone(),
  4197. blockID = $firstSettingsBlock.data( 'block-id' ),
  4198. newSettingsBlock;
  4199. $newSettingsBlock.attr( 'data-block-id', nextID );
  4200. $newSettingsBlock.find( '.wpforms-builder-settings-block-header span' ).text( settingsBlockName );
  4201. $newSettingsBlock.find( 'input, textarea, select' ).not( '.from-name input' ).not( '.from-email input' ).each( function( index, el ) {
  4202. var $this = $( this );
  4203. if ( $this.attr( 'name' ) ) {
  4204. $this.val( '' ).attr( 'name', $this.attr( 'name' ).replace( /\[(\d+)\]/, '[' + nextID + ']' ) );
  4205. if ( $this.is( 'select' ) ) {
  4206. $this.find( 'option' ).prop( 'selected', false ).attr( 'selected', false );
  4207. $this.find( 'option:first' ).prop( 'selected', true ).attr( 'selected', 'selected' );
  4208. } else if ( $this.attr( 'type' ) === 'checkbox' ) {
  4209. $this.prop( 'checked', false ).attr( 'checked', false ).val( '1' );
  4210. } else {
  4211. $this.val( '' ).attr( 'value', '' );
  4212. }
  4213. }
  4214. } );
  4215. // Update elements IDs.
  4216. var idPrefixPanel = 'wpforms-panel-field-' + panelID + '-',
  4217. idPrefixBlock = idPrefixPanel + blockID;
  4218. $newSettingsBlock.find( '[id^="' + idPrefixBlock + '"], [for^="' + idPrefixBlock + '"]' ).each( function( index, el ) {
  4219. var $el = $( this ),
  4220. attr = $el.prop( 'tagName' ) === 'LABEL' ? 'for' : 'id',
  4221. elID = $el.attr( attr ).replace( new RegExp( idPrefixBlock, 'g' ), idPrefixPanel + nextID );
  4222. $el.attr( attr, elID );
  4223. } );
  4224. // Update `notification by status` checkboxes.
  4225. var radioGroup = blockID + '-notification-by-status';
  4226. $newSettingsBlock.find( '[data-radio-group="' + radioGroup + '"]' ).each( function( index, el ) {
  4227. $( this )
  4228. .removeClass( 'wpforms-radio-group-' + radioGroup )
  4229. .addClass( 'wpforms-radio-group-' + nextID + '-notification-by-status' )
  4230. .attr( 'data-radio-group', nextID + '-notification-by-status' );
  4231. } );
  4232. $newSettingsBlock.find( '.wpforms-builder-settings-block-header input' ).val( settingsBlockName ).attr( 'value', settingsBlockName );
  4233. if ( blockType === 'notification' ) {
  4234. $newSettingsBlock.find( '.email-msg textarea' ).val( '{all_fields}' ).attr( 'value', '{all_fields}' );
  4235. $newSettingsBlock.find( '.email-recipient input' ).val( '{admin_email}' ).attr( 'value', '{admin_email}' );
  4236. }
  4237. $newSettingsBlock.removeClass( 'wpforms-builder-settings-block-default' );
  4238. if ( blockType === 'confirmation' ) {
  4239. $newSettingsBlock.find( '.wpforms-panel-field-tinymce' ).remove();
  4240. if ( typeof WPForms !== 'undefined' ) {
  4241. $newSettingsBlock.find( '.wpforms-panel-field-confirmations-type-wrap' )
  4242. .after( WPForms.Admin.Builder.Templates
  4243. .get( 'wpforms-builder-confirmations-message-field' )( {
  4244. id: nextID,
  4245. } )
  4246. );
  4247. }
  4248. }
  4249. // Conditional logic, if present
  4250. var $conditionalLogic = $newSettingsBlock.find( '.wpforms-conditional-block' );
  4251. if ( $conditionalLogic.length && typeof WPForms !== 'undefined' ) {
  4252. $conditionalLogic
  4253. .html( WPForms.Admin.Builder.Templates
  4254. .get( 'wpforms-builder-conditional-logic-toggle-field' )( {
  4255. id: nextID,
  4256. type: blockType,
  4257. actions: JSON.stringify( $newSettingsBlock.find( '.wpforms-panel-field-conditional_logic-checkbox' ).data( 'actions' ) ),
  4258. actionDesc: $newSettingsBlock.find( '.wpforms-panel-field-conditional_logic-checkbox' ).data( 'action-desc' ),
  4259. } )
  4260. );
  4261. }
  4262. // Fields Map Table, if present.
  4263. var $fieldsMapTable = $newSettingsBlock.find( '.wpforms-field-map-table' );
  4264. if ( $fieldsMapTable.length ) {
  4265. $fieldsMapTable.each( function( index, el ) {
  4266. var $table = $( el );
  4267. // Clean table fields.
  4268. $table.find( 'tr:not(:first-child)' ).remove();
  4269. var $input = $table.find( '.key input' ),
  4270. $select = $table.find( '.field select' ),
  4271. name = $select.data( 'name' );
  4272. $input.attr( 'value', '' );
  4273. $select
  4274. .attr( 'name', '' )
  4275. .attr( 'data-name', name.replace( /\[(\d+)\]/, '[' + nextID + ']' ) );
  4276. } );
  4277. }
  4278. newSettingsBlock = $newSettingsBlock.wrap( '<div>' ).parent().html();
  4279. newSettingsBlock = newSettingsBlock.replace( /\[conditionals\]\[(\d+)\]\[(\d+)\]/g, '[conditionals][0][0]' );
  4280. $firstSettingsBlock.before( newSettingsBlock );
  4281. var $addedSettingBlock = $firstSettingsBlock.prev();
  4282. // Reset the confirmation type to the 1st one.
  4283. if ( blockType === 'confirmation' ) {
  4284. app.confirmationFieldsToggle( $( '.wpforms-panel-field-confirmations-type' ).first() );
  4285. }
  4286. // Init the WP Editor.
  4287. if ( typeof tinymce !== 'undefined' && typeof wp.editor !== 'undefined' && blockType === 'confirmation' ) {
  4288. wp.editor.initialize( 'wpforms-panel-field-confirmations-message-' + nextID, s.tinymceDefaults );
  4289. }
  4290. // Init tooltips for new section.
  4291. wpf.initTooltips();
  4292. $builder.trigger( 'wpformsSettingsBlockAdded', [ $addedSettingBlock ] );
  4293. $el.attr( 'data-next-id', nextID + 1 );
  4294. }
  4295. },
  4296. },
  4297. cancel: {
  4298. text: wpforms_builder.cancel,
  4299. },
  4300. },
  4301. } );
  4302. // We need to process this event here, because we need a confirm modal object defined, so we can intrude into it.
  4303. // Pressing Enter will click the Ok button.
  4304. $builder.on( 'keypress', '#settings-block-name', function( e ) {
  4305. if ( e.keyCode === 13 ) {
  4306. $( modal.buttons.confirm.el ).trigger( 'click' );
  4307. }
  4308. } );
  4309. },
  4310. /**
  4311. * Show settings block editing interface.
  4312. *
  4313. * @since 1.4.8
  4314. */
  4315. settingsBlockNameEditingShow: function ($el) {
  4316. var header_holder = $el.parents('.wpforms-builder-settings-block-header'),
  4317. name_holder = header_holder.find('.wpforms-builder-settings-block-name');
  4318. name_holder
  4319. .addClass('editing')
  4320. .hide();
  4321. // Make the editing interface active and in focus
  4322. header_holder.find('.wpforms-builder-settings-block-name-edit').addClass('active');
  4323. wpf.focusCaretToEnd(header_holder.find('input'));
  4324. },
  4325. /**
  4326. * Update settings block name and hide editing interface.
  4327. *
  4328. * @since 1.4.8
  4329. */
  4330. settingsBlockNameEditingHide: function ($el) {
  4331. var header_holder = $el.parents('.wpforms-builder-settings-block-header'),
  4332. name_holder = header_holder.find('.wpforms-builder-settings-block-name'),
  4333. edit_holder = header_holder.find('.wpforms-builder-settings-block-name-edit'),
  4334. current_name = edit_holder.find('input').val().trim(),
  4335. blockType = $el.closest('.wpforms-builder-settings-block').data('block-type');
  4336. // Provide a default value for empty settings block name.
  4337. if (! current_name.length) {
  4338. current_name = wpforms_builder[blockType + '_def_name'];
  4339. }
  4340. // This is done for sanitizing.
  4341. edit_holder.find('input').val(current_name);
  4342. name_holder.text(current_name);
  4343. // Editing should be hidden, displaying - active.
  4344. name_holder
  4345. .removeClass('editing')
  4346. .show();
  4347. edit_holder.removeClass('active');
  4348. },
  4349. /**
  4350. * Clone the Notification block with all of its content and events.
  4351. * Put the newly created clone above the target.
  4352. *
  4353. * @since 1.6.5
  4354. *
  4355. * @param {object} $el Clone icon DOM element.
  4356. */
  4357. settingsBlockPanelClone: function( $el ) {
  4358. var $panel = $el.closest( '.wpforms-panel-content-section' ),
  4359. $addNewSettingButton = $panel.find( '.wpforms-builder-settings-block-add' ),
  4360. $settingsBlock = $el.closest( '.wpforms-builder-settings-block' ),
  4361. $settingBlockContent = $settingsBlock.find( '.wpforms-builder-settings-block-content' ),
  4362. settingsBlockId = parseInt( $addNewSettingButton.attr( 'data-next-id' ), 10 ),
  4363. settingsBlockType = $settingsBlock.data( 'block-type' ),
  4364. settingsBlockName = $settingsBlock.find( '.wpforms-builder-settings-block-name' ).text().trim() + wpforms_builder[ settingsBlockType + '_clone' ],
  4365. isVisibleContent = $settingBlockContent.is( ':hidden' );
  4366. // Restore tooltips before cloning.
  4367. wpf.restoreTooltips( $settingsBlock );
  4368. var $clone = $settingsBlock.clone( false, true );
  4369. // Save open/close state while cloning.
  4370. app.settingsBlockUpdateState( isVisibleContent, settingsBlockId, settingsBlockType );
  4371. // Change the cloned setting block ID and name.
  4372. $clone.data( 'block-id', settingsBlockId );
  4373. $clone.find( '.wpforms-builder-settings-block-header span' ).text( settingsBlockName );
  4374. $clone.find( '.wpforms-builder-settings-block-header input' ).val( settingsBlockName );
  4375. $clone.removeClass( 'wpforms-builder-settings-block-default' );
  4376. // Change the Next Settings block ID for "Add new" button.
  4377. $addNewSettingButton.attr( 'data-next-id', settingsBlockId + 1 );
  4378. // Change the name attribute.
  4379. $clone.find( 'input, textarea, select' ).each( function() {
  4380. var $this = $( this );
  4381. if ( $this.attr( 'name' ) ) {
  4382. $this.attr( 'name', $this.attr( 'name' ).replace( /\[(\d+)\]/, '[' + settingsBlockId + ']' ) );
  4383. }
  4384. if ( $this.data( 'name' ) ) {
  4385. $this.data( 'name', $this.data( 'name' ).replace( /\[(\d+)\]/, '[' + settingsBlockId + ']' ) );
  4386. }
  4387. if ( $this.attr( 'class' ) ) {
  4388. $this.attr( 'class', $this.attr( 'class' ).replace( /-(\d+)/, '-' + settingsBlockId ) );
  4389. }
  4390. if ( $this.attr( 'data-radio-group' ) ) {
  4391. $this.attr( 'data-radio-group', $this.attr( 'data-radio-group' ).replace( /(\d+)-/, settingsBlockId + '-' ) );
  4392. }
  4393. } );
  4394. // Change IDs/data-attributes in DOM elements.
  4395. $clone.find( '*' ).each( function() {
  4396. var $this = $( this );
  4397. if ( $this.attr( 'id' ) ) {
  4398. $this.attr( 'id', $this.attr( 'id' ).replace( /-(\d+)/, '-' + settingsBlockId ) );
  4399. }
  4400. if ( $this.attr( 'for' ) ) {
  4401. $this.attr( 'for', $this.attr( 'for' ).replace( /-(\d+)-/, '-' + settingsBlockId + '-' ) );
  4402. }
  4403. if ( $this.data( 'input-name' ) ) {
  4404. $this.data( 'input-name', $this.data( 'input-name' ).replace( /\[(\d+)\]/, '[' + settingsBlockId + ']' ) );
  4405. }
  4406. } );
  4407. // Transfer selected values to copied elements since jQuery doesn't clone the current selected state.
  4408. $settingsBlock.find( 'select' ).each( function() {
  4409. var baseSelectName = $( this ).attr( 'name' ),
  4410. clonedSelectName = $( this ).attr( 'name' ).replace( /\[(\d+)\]/, '[' + settingsBlockId + ']' );
  4411. $clone.find( 'select[name="' + clonedSelectName + '"]' ).val( $( this ).attr( 'name', baseSelectName ).val() );
  4412. } );
  4413. // Insert before the target settings block.
  4414. $clone
  4415. .css( 'display', 'none' )
  4416. .insertBefore( $settingsBlock )
  4417. .show( 'fast', function() {
  4418. // Init tooltips for new section.
  4419. wpf.initTooltips();
  4420. } );
  4421. },
  4422. /**
  4423. * Show or hide settings block panel content.
  4424. *
  4425. * @since 1.4.8
  4426. *
  4427. * @param {object} $el Toggle icon DOM element.
  4428. */
  4429. settingsBlockPanelToggle: function( $el ) {
  4430. var $settingsBlock = $el.closest( '.wpforms-builder-settings-block' ),
  4431. settingsBlockId = $settingsBlock.data( 'block-id' ),
  4432. settingsBlockType = $settingsBlock.data( 'block-type' ),
  4433. $content = $settingsBlock.find( '.wpforms-builder-settings-block-content' ),
  4434. isVisible = $content.is( ':visible' );
  4435. $content.stop().slideToggle( {
  4436. duration: 400,
  4437. start: function() {
  4438. // Send early to save fast.
  4439. // It's animation start, so we should save the state for animation end (reversed).
  4440. app.settingsBlockUpdateState( isVisible, settingsBlockId, settingsBlockType );
  4441. },
  4442. always: function() {
  4443. if ( $content.is( ':visible' ) ) {
  4444. $el.html( '<i class="fa fa-chevron-circle-up"></i>' );
  4445. } else {
  4446. $el.html( '<i class="fa fa-chevron-circle-down"></i>' );
  4447. }
  4448. },
  4449. } );
  4450. },
  4451. /**
  4452. * Delete settings block.
  4453. *
  4454. * @since 1.4.8
  4455. * @since 1.6.1.2 Registered `wpformsSettingsBlockDeleted` trigger.
  4456. *
  4457. * @param {jQuery} $el Delete button element.
  4458. */
  4459. settingsBlockDelete: function( $el ) {
  4460. var $contentSection = $el.closest( '.wpforms-panel-content-section' ),
  4461. $currentBlock = $el.closest( '.wpforms-builder-settings-block' ),
  4462. blockType = $currentBlock.data( 'block-type' );
  4463. // Skip if only one block persist.
  4464. // This condition should not execute in normal circumstances.
  4465. if ( $contentSection.find( '.wpforms-builder-settings-block' ).length < 2 ) {
  4466. return;
  4467. }
  4468. $.confirm( {
  4469. title: false,
  4470. content: wpforms_builder[ blockType + '_delete' ],
  4471. icon: 'fa fa-exclamation-circle',
  4472. type: 'orange',
  4473. buttons: {
  4474. confirm: {
  4475. text: wpforms_builder.ok,
  4476. btnClass: 'btn-confirm',
  4477. keys: [ 'enter' ],
  4478. action: function() {
  4479. var settingsBlockId = $currentBlock.data( 'block-id' ),
  4480. settingsBlockType = $currentBlock.data( 'block-type' );
  4481. /* eslint-disable camelcase */
  4482. $.post( wpforms_builder.ajax_url, {
  4483. action : 'wpforms_builder_settings_block_state_remove',
  4484. nonce : wpforms_builder.nonce,
  4485. block_id : settingsBlockId,
  4486. block_type: settingsBlockType,
  4487. form_id : s.formID,
  4488. } );
  4489. /* eslint-enable */
  4490. $currentBlock.remove();
  4491. $builder.trigger( 'wpformsSettingsBlockDeleted', [ blockType, settingsBlockId ] );
  4492. },
  4493. },
  4494. cancel: {
  4495. text: wpforms_builder.cancel,
  4496. },
  4497. },
  4498. } );
  4499. },
  4500. /**
  4501. * Change open/close state for setting block.
  4502. *
  4503. * @since 1.6.5
  4504. *
  4505. * @param {boolean} isVisible State status.
  4506. * @param {number} settingsBlockId Block ID.
  4507. * @param {string} settingsBlockType Block type.
  4508. *
  4509. */
  4510. settingsBlockUpdateState: function( isVisible, settingsBlockId, settingsBlockType ) {
  4511. $.post( wpforms_builder.ajax_url, {
  4512. action: 'wpforms_builder_settings_block_state_save',
  4513. state: isVisible ? 'closed' : 'opened',
  4514. form_id: s.formID,
  4515. block_id: settingsBlockId,
  4516. block_type: settingsBlockType,
  4517. nonce: wpforms_builder.nonce,
  4518. } );
  4519. },
  4520. //--------------------------------------------------------------------//
  4521. // Save and Exit
  4522. //--------------------------------------------------------------------//
  4523. /**
  4524. * Element bindings for Embed and Save/Exit items.
  4525. *
  4526. * @since 1.0.0
  4527. * @since 1.5.8 Added trigger on `wpformsSaved` event to remove a `newform` URL-parameter.
  4528. */
  4529. bindUIActionsSaveExit: function() {
  4530. // Embed form.
  4531. $builder.on( 'click', '#wpforms-embed', function( e ) {
  4532. e.preventDefault();
  4533. if ( $( this ).hasClass( 'wpforms-disabled' ) ) {
  4534. return;
  4535. }
  4536. WPFormsFormEmbedWizard.openPopup();
  4537. } );
  4538. // Save form.
  4539. $builder.on( 'click', '#wpforms-save', function( e ) {
  4540. e.preventDefault();
  4541. app.formSave( false );
  4542. } );
  4543. // Exit builder.
  4544. $builder.on( 'click', '#wpforms-exit', function( e ) {
  4545. e.preventDefault();
  4546. app.formExit();
  4547. } );
  4548. // After form save.
  4549. $builder.on( 'wpformsSaved', function( e, data ) {
  4550. /**
  4551. * Remove `newform` parameter, if it's in URL, otherwise we can to get a "race condition".
  4552. * E.g. form settings will be updated before some provider connection is loaded.
  4553. */
  4554. wpf.removeQueryParam( 'newform' );
  4555. } );
  4556. },
  4557. /**
  4558. * Save form.
  4559. *
  4560. * @since 1.0.0
  4561. */
  4562. formSave: function(redirect) {
  4563. var $saveBtn = $( '#wpforms-save' ),
  4564. $icon = $saveBtn.find( 'i.fa-check' ),
  4565. $spinner = $saveBtn.find( 'i.wpforms-loading-spinner' ),
  4566. $label = $saveBtn.find( 'span' ),
  4567. text = $label.text();
  4568. if ( typeof tinyMCE !== 'undefined' ) {
  4569. tinyMCE.triggerSave();
  4570. }
  4571. $label.text( wpforms_builder.saving );
  4572. $icon.addClass( 'wpforms-hidden' );
  4573. $spinner.removeClass( 'wpforms-hidden' );
  4574. var data = {
  4575. action: 'wpforms_save_form',
  4576. data : JSON.stringify($('#wpforms-builder-form').serializeArray()),
  4577. id : s.formID,
  4578. nonce : wpforms_builder.nonce
  4579. };
  4580. return $.post( wpforms_builder.ajax_url, data, function( response ) {
  4581. if ( response.success ) {
  4582. wpf.savedState = wpf.getFormState( '#wpforms-builder-form' );
  4583. wpf.initialSave = false;
  4584. $builder.trigger( 'wpformsSaved', response.data );
  4585. if ( true === redirect && app.isBuilderInPopup() ) {
  4586. app.builderInPopupClose( 'saved' );
  4587. return;
  4588. }
  4589. if ( true === redirect ) {
  4590. window.location.href = wpforms_builder.exit_url;
  4591. }
  4592. } else {
  4593. wpf.debug( response );
  4594. app.formSaveError( response.data );
  4595. }
  4596. } ).fail( function( xhr, textStatus, e ) {
  4597. wpf.debug( xhr );
  4598. app.formSaveError();
  4599. } ).always( function() {
  4600. $label.text( text );
  4601. $spinner.addClass( 'wpforms-hidden' );
  4602. $icon.removeClass( 'wpforms-hidden' );
  4603. } );
  4604. },
  4605. /**
  4606. * Form save error.
  4607. *
  4608. * @since 1.6.3
  4609. *
  4610. * @param {string} error Error message.
  4611. */
  4612. formSaveError: function( error ) {
  4613. // Default error message.
  4614. if ( wpf.empty( error ) ) {
  4615. error = wpforms_builder.error_save_form;
  4616. }
  4617. // Display error in modal window.
  4618. $.confirm( {
  4619. title: wpforms_builder.heads_up,
  4620. content: '<p>' + error + '</p><p>' + wpforms_builder.error_contact_support + '</p>',
  4621. icon: 'fa fa-exclamation-circle',
  4622. type: 'orange',
  4623. buttons: {
  4624. confirm: {
  4625. text: wpforms_builder.ok,
  4626. btnClass: 'btn-confirm',
  4627. keys: [ 'enter' ],
  4628. },
  4629. },
  4630. } );
  4631. },
  4632. /**
  4633. * Exit form builder.
  4634. *
  4635. * @since 1.0.0
  4636. */
  4637. formExit: function() {
  4638. if ( app.isBuilderInPopup() && app.formIsSaved() ) {
  4639. app.builderInPopupClose( 'saved' );
  4640. return;
  4641. }
  4642. if ( app.formIsSaved() ) {
  4643. window.location.href = wpforms_builder.exit_url;
  4644. } else {
  4645. $.confirm({
  4646. title: false,
  4647. content: wpforms_builder.exit_confirm,
  4648. icon: 'fa fa-exclamation-circle',
  4649. type: 'orange',
  4650. closeIcon: true,
  4651. buttons: {
  4652. confirm: {
  4653. text: wpforms_builder.save_exit,
  4654. btnClass: 'btn-confirm',
  4655. keys: [ 'enter' ],
  4656. action: function(){
  4657. app.formSave(true);
  4658. }
  4659. },
  4660. cancel: {
  4661. text: wpforms_builder.exit,
  4662. action: function() {
  4663. closeConfirmation = false;
  4664. if ( app.isBuilderInPopup() ) {
  4665. app.builderInPopupClose( 'canceled' );
  4666. return;
  4667. }
  4668. window.location.href = wpforms_builder.exit_url;
  4669. }
  4670. }
  4671. }
  4672. });
  4673. }
  4674. },
  4675. /**
  4676. * Close confirmation setter.
  4677. *
  4678. * @since {VESRSION}
  4679. *
  4680. * @param {boolean} confirm Close confirmation flag value.
  4681. */
  4682. setCloseConfirmation: function( confirm ) {
  4683. closeConfirmation = ! ! confirm;
  4684. },
  4685. /**
  4686. * Check current form state.
  4687. *
  4688. * @since 1.0.0
  4689. */
  4690. formIsSaved: function() {
  4691. if ( wpf.savedState == wpf.getFormState( '#wpforms-builder-form' ) ) {
  4692. return true;
  4693. } else {
  4694. return false;
  4695. }
  4696. },
  4697. /**
  4698. * Check if the builder opened in the popup (iframe).
  4699. *
  4700. * @since 1.6.2
  4701. *
  4702. * @returns {boolean} True if builder opened in the popup.
  4703. */
  4704. isBuilderInPopup: function() {
  4705. return window.self !== window.parent && window.self.frameElement.id === 'wpforms-builder-iframe';
  4706. },
  4707. /**
  4708. * Close popup with the form builder.
  4709. *
  4710. * @since 1.6.2
  4711. *
  4712. * @param {string} action Performed action: saved or canceled.
  4713. */
  4714. builderInPopupClose: function( action ) {
  4715. var $popup = window.parent.jQuery( '#wpforms-builder-elementor-popup' );
  4716. $popup.find( '#wpforms-builder-iframe' ).attr( 'src', 'about:blank' );
  4717. $popup.fadeOut();
  4718. $popup.trigger( 'wpformsBuilderInPopupClose', [ action, s.formID ] );
  4719. },
  4720. //--------------------------------------------------------------------//
  4721. // General / global
  4722. //--------------------------------------------------------------------//
  4723. /**
  4724. * Element bindings for general and global items
  4725. *
  4726. * @since 1.2.0
  4727. */
  4728. bindUIActionsGeneral: function() {
  4729. // Toggle Smart Tags
  4730. $builder.on( 'click', '.toggle-smart-tag-display', app.smartTagToggle );
  4731. $builder.on( 'click', '.smart-tags-list-display a', app.smartTagInsert );
  4732. // Toggle unfoldable group of fields
  4733. $builder.on( 'click', '.wpforms-panel-fields-group.unfoldable .wpforms-panel-fields-group-title', app.toggleUnfoldableGroup );
  4734. // Hide field preview helper box.
  4735. $builder.on( 'click', '.wpforms-field-helper-hide ', app.hideFieldHelper );
  4736. // Field map table, update key source
  4737. $builder.on('input', '.wpforms-field-map-table .key-source', function(){
  4738. var value = $(this).val(),
  4739. $dest = $(this).parent().parent().find('.key-destination'),
  4740. name = $dest.data('name');
  4741. if (value) {
  4742. $dest.attr('name', name.replace('{source}', value.replace(/[^0-9a-zA-Z_-]/gi, '')));
  4743. }
  4744. });
  4745. // Field map table, delete row
  4746. $builder.on('click', '.wpforms-field-map-table .remove', function(e) {
  4747. e.preventDefault();
  4748. app.fieldMapTableDeleteRow(e, $(this));
  4749. });
  4750. // Field map table, Add row
  4751. $builder.on('click', '.wpforms-field-map-table .add', function(e) {
  4752. e.preventDefault();
  4753. app.fieldMapTableAddRow(e, $(this));
  4754. });
  4755. // Global select field mapping
  4756. $(document).on('wpformsFieldUpdate', app.fieldMapSelect);
  4757. // Restrict user money input fields
  4758. $builder.on( 'input', '.wpforms-money-input', function( event ) {
  4759. var $this = $( this ),
  4760. amount = $this.val(),
  4761. start = $this[ 0 ].selectionStart,
  4762. end = $this[ 0 ].selectionEnd;
  4763. $this.val( amount.replace( /[^0-9.,]/g, '' ) );
  4764. $this[ 0 ].setSelectionRange( start, end );
  4765. } );
  4766. // Format user money input fields
  4767. $builder.on( 'focusout', '.wpforms-money-input', function( event ) {
  4768. var $this = $( this ),
  4769. amount = $this.val();
  4770. if ( ! amount ) {
  4771. return amount;
  4772. }
  4773. var sanitized = wpf.amountSanitize( amount ),
  4774. formatted = wpf.amountFormat( sanitized );
  4775. $this.val( formatted );
  4776. } );
  4777. // Show/hide a group of options.
  4778. $builder.on( 'change', '.wpforms-panel-field-toggle', function() {
  4779. var $input = $( this );
  4780. if ( $input.prop( 'disabled' ) ) {
  4781. return;
  4782. }
  4783. $input.prop( 'disabled', true );
  4784. app.toggleOptionsGroup( $input );
  4785. } );
  4786. // Don't allow users to enable payments if storing entries has
  4787. // been disabled in the General settings.
  4788. $builder.on( 'change', '#wpforms-panel-field-stripe-enable, #wpforms-panel-field-paypal_standard-enable, #wpforms-panel-field-authorize_net-enable, #wpforms-panel-field-square-enable', function( event ) {
  4789. var $this = $( this ),
  4790. gateway = $this.attr( 'id' ).replace( 'wpforms-panel-field-', '' ).replace( '-enable', '' ),
  4791. $notificationWrap = $( '.wpforms-panel-content-section-notifications [id*="-' + gateway + '-wrap"]' );
  4792. if ( $this.prop( 'checked' ) ) {
  4793. var disabled = $( '#wpforms-panel-field-settings-disable_entries' ).prop( 'checked' );
  4794. if ( disabled ) {
  4795. $.confirm( {
  4796. title: wpforms_builder.heads_up,
  4797. content: wpforms_builder.payments_entries_off,
  4798. icon: 'fa fa-exclamation-circle',
  4799. type: 'orange',
  4800. buttons: {
  4801. confirm: {
  4802. text: wpforms_builder.ok,
  4803. btnClass: 'btn-confirm',
  4804. keys: [ 'enter' ],
  4805. },
  4806. },
  4807. } );
  4808. $this.prop( 'checked', false );
  4809. } else {
  4810. $notificationWrap.removeClass( 'wpforms-hidden' );
  4811. }
  4812. } else {
  4813. $notificationWrap.addClass( 'wpforms-hidden' );
  4814. $notificationWrap.find( 'input[id*="-' + gateway + '"]' ).prop( 'checked', false );
  4815. }
  4816. } );
  4817. // Don't allow users to disable entries if payments has been enabled.
  4818. $builder.on( 'change', '#wpforms-panel-field-settings-disable_entries', function( event ) {
  4819. var $this = $( this );
  4820. if ( $this.prop( 'checked' ) ) {
  4821. var paymentsEnabled = $( '#wpforms-panel-field-stripe-enable' ).prop( 'checked' ) || $( '#wpforms-panel-field-paypal_standard-enable' ).prop( 'checked' ) || $( '#wpforms-panel-field-authorize_net-enable' ).prop( 'checked' ) || $( '#wpforms-panel-field-square-enable' ).prop( 'checked' );
  4822. if ( paymentsEnabled ) {
  4823. $.confirm( {
  4824. title: wpforms_builder.heads_up,
  4825. content: wpforms_builder.payments_on_entries_off,
  4826. icon: 'fa fa-exclamation-circle',
  4827. type: 'orange',
  4828. buttons: {
  4829. confirm: {
  4830. text: wpforms_builder.ok,
  4831. btnClass: 'btn-confirm',
  4832. keys: [ 'enter' ],
  4833. },
  4834. },
  4835. } );
  4836. $this.prop( 'checked', false );
  4837. } else {
  4838. $.alert( {
  4839. title: wpforms_builder.heads_up,
  4840. content: wpforms_builder.disable_entries,
  4841. icon: 'fa fa-exclamation-circle',
  4842. type: 'orange',
  4843. buttons: {
  4844. confirm: {
  4845. text: wpforms_builder.ok,
  4846. btnClass: 'btn-confirm',
  4847. keys: [ 'enter' ],
  4848. },
  4849. },
  4850. } );
  4851. }
  4852. }
  4853. } );
  4854. // Upload or add an image.
  4855. $builder.on( 'click', '.wpforms-image-upload-add', function( event ) {
  4856. event.preventDefault();
  4857. var $this = $( this ),
  4858. $container = $this.parent(),
  4859. mediaModal;
  4860. mediaModal = wp.media.frames.wpforms_media_frame = wp.media( {
  4861. className: 'media-frame wpforms-media-frame',
  4862. frame: 'select',
  4863. multiple: false,
  4864. title: wpforms_builder.upload_image_title,
  4865. library: {
  4866. type: 'image',
  4867. },
  4868. button: {
  4869. text: wpforms_builder.upload_image_button,
  4870. },
  4871. } );
  4872. mediaModal.on( 'select', function() {
  4873. var mediaAttachment = mediaModal.state().get( 'selection' ).first().toJSON();
  4874. $container.find( '.source' ).val( mediaAttachment.url );
  4875. $container.find( '.preview' ).empty();
  4876. $container.find( '.preview' ).prepend( '<img src="' + mediaAttachment.url + '"><a href="#" title="' + wpforms_builder.upload_image_remove + '" class="wpforms-image-upload-remove"><i class="fa fa-trash-o"></i></a>' );
  4877. if ( $this.data( 'after-upload' ) === 'hide' ) {
  4878. $this.hide();
  4879. }
  4880. $builder.trigger( 'wpformsImageUploadAdd', [ $this, $container ] );
  4881. } );
  4882. // Now that everything has been set, let's open up the frame.
  4883. mediaModal.open();
  4884. } );
  4885. // Remove and uploaded image.
  4886. $builder.on( 'click', '.wpforms-image-upload-remove', function( event ) {
  4887. event.preventDefault();
  4888. var $container = $( this ).parent().parent();
  4889. $container.find( '.preview' ).empty();
  4890. $container.find( '.wpforms-image-upload-add' ).show();
  4891. $container.find( '.source' ).val( '' );
  4892. $builder.trigger( 'wpformsImageUploadRemove', [ $( this ), $container ] );
  4893. });
  4894. // Validate email smart tags in Notifications fields.
  4895. $builder.on( 'blur', '.wpforms-notification .wpforms-panel-field-text input', function() {
  4896. app.validateEmailSmartTags( $( this ) );
  4897. });
  4898. $builder.on( 'blur', '.wpforms-notification .wpforms-panel-field-textarea textarea', function() {
  4899. app.validateEmailSmartTags( $( this ) );
  4900. });
  4901. // Mobile notice button click.
  4902. $builder.on( 'click', '.wpforms-fullscreen-notice-go-back', app.exitBack );
  4903. // License Alert close button click.
  4904. $( '#wpforms-builder-license-alert .close' ).on( 'click', app.exitBack );
  4905. },
  4906. /**
  4907. * Toggle a options group.
  4908. *
  4909. * @since 1.6.3
  4910. *
  4911. * @param {object} $input Toggled field.
  4912. */
  4913. toggleOptionsGroup: function( $input ) {
  4914. var name = $input.attr( 'name' ),
  4915. type = $input.attr( 'type' ),
  4916. value = '',
  4917. $body = $( '.wpforms-panel-field-toggle-body[data-toggle="' + name + '"]' ),
  4918. enableInput = function() {
  4919. $input.prop( 'disabled', false );
  4920. };
  4921. if ( $body.length === 0 ) {
  4922. enableInput();
  4923. return;
  4924. }
  4925. if ( 'checkbox' === type || 'radio' === type ) {
  4926. value = $input.prop( 'checked' ) ? $input.val() : '0';
  4927. } else {
  4928. value = $input.val();
  4929. }
  4930. $body.each( function() {
  4931. var $this = $( this );
  4932. $this.attr( 'data-toggle-value' ).toString() === value.toString() ?
  4933. $this.slideDown( '', enableInput ) :
  4934. $this.slideUp( '', enableInput );
  4935. } );
  4936. },
  4937. /**
  4938. * Toggle all option groups.
  4939. *
  4940. * @since 1.6.3
  4941. *
  4942. * @param {jQuery} $context Context container jQuery object.
  4943. */
  4944. toggleAllOptionGroups: function( $context ) {
  4945. $context = $context || $builder || $( '#wpforms-builder' ) || $( 'body' );
  4946. if ( ! $context ) {
  4947. return;
  4948. }
  4949. // Show a toggled bodies.
  4950. $context.find( '.wpforms-panel-field-toggle' ).each( function() {
  4951. var $input = $( this );
  4952. $input.prop( 'disabled', true );
  4953. app.toggleOptionsGroup( $input );
  4954. } );
  4955. },
  4956. /**
  4957. * Toggle unfoldable group of fields.
  4958. *
  4959. * @since 1.6.8
  4960. *
  4961. * @param {object} e Event object.
  4962. */
  4963. toggleUnfoldableGroup: function( e ) {
  4964. e.preventDefault();
  4965. var $title = $( e.target ),
  4966. $group = $title.closest( '.wpforms-panel-fields-group' ),
  4967. $inner = $group.find( '.wpforms-panel-fields-group-inner' ),
  4968. cookieName = 'wpforms_fields_group_' + $group.data( 'group' );
  4969. if ( $group.hasClass( 'opened' ) ) {
  4970. wpCookies.remove( cookieName );
  4971. $group.removeClass( 'opened' );
  4972. $inner.stop().slideUp();
  4973. } else {
  4974. wpCookies.set( cookieName, 'true', 2592000 ); // 1 month.
  4975. $group.addClass( 'opened' );
  4976. $inner.stop().slideDown();
  4977. }
  4978. },
  4979. /**
  4980. * Hide field preview helper box.
  4981. *
  4982. * @since 1.7.1
  4983. *
  4984. * @param {object} e Event object.
  4985. */
  4986. hideFieldHelper: function( e ) {
  4987. e.preventDefault();
  4988. e.stopPropagation();
  4989. var $helpers = $( '.wpforms-field-helper' ),
  4990. cookieName = 'wpforms_field_helper_hide';
  4991. wpCookies.set( cookieName, 'true', 30 * 24 * 60 * 60 ); // 1 month.
  4992. $helpers.hide();
  4993. },
  4994. /**
  4995. * Smart Tag toggling.
  4996. *
  4997. * @since 1.0.1
  4998. * @since 1.6.9 Simplify method.
  4999. *
  5000. * @param {Event} e Event.
  5001. */
  5002. smartTagToggle: function( e ) {
  5003. e.preventDefault();
  5004. var $this = $( this ),
  5005. $wrapper = $this.closest( '.wpforms-panel-field,.wpforms-field-option-row' );
  5006. if ( $wrapper.hasClass( 'smart-tags-toggling' ) ) {
  5007. return;
  5008. }
  5009. $wrapper.addClass( 'smart-tags-toggling' );
  5010. if ( $this.hasClass( 'smart-tag-showing' ) ) {
  5011. app.removeSmartTagsList( $this );
  5012. return;
  5013. }
  5014. app.insertSmartTagsList( $this );
  5015. },
  5016. /**
  5017. * Remove Smart Tag list.
  5018. *
  5019. * @since 1.6.9
  5020. *
  5021. * @param {jQuery} $el Toggle element.
  5022. */
  5023. removeSmartTagsList: function( $el ) {
  5024. var $wrapper = $el.closest( '.wpforms-panel-field,.wpforms-field-option-row' ),
  5025. $list = $wrapper.find( '.smart-tags-list-display' );
  5026. $el.find( 'span' ).text( wpforms_builder.smart_tags_show );
  5027. $list.slideUp( '', function() {
  5028. $list.remove();
  5029. $el.removeClass( 'smart-tag-showing' );
  5030. $wrapper.removeClass( 'smart-tags-toggling' );
  5031. } );
  5032. },
  5033. /**
  5034. * Insert Smart Tag list.
  5035. *
  5036. * @since 1.6.9
  5037. *
  5038. * @param {jQuery} $el Toggle element.
  5039. */
  5040. insertSmartTagsList: function( $el ) {
  5041. var $wrapper = $el.closest( '.wpforms-panel-field,.wpforms-field-option-row' ),
  5042. $label = $el.closest( 'label' ),
  5043. insideLabel = true,
  5044. smartTagList;
  5045. if ( ! $label.length ) {
  5046. $label = $wrapper.find( 'label' );
  5047. insideLabel = false;
  5048. }
  5049. smartTagList = app.getSmartTagsList( $el, $label.attr( 'for' ).indexOf( 'wpforms-field-option-' ) !== -1 );
  5050. insideLabel ?
  5051. $label.after( smartTagList ) :
  5052. $el.after( smartTagList );
  5053. $el.find( 'span' ).text( wpforms_builder.smart_tags_hide );
  5054. $wrapper.find( '.smart-tags-list-display' ).slideDown( '', function() {
  5055. $el.addClass( 'smart-tag-showing' );
  5056. $wrapper.removeClass( 'smart-tags-toggling' );
  5057. } );
  5058. },
  5059. /**
  5060. * Get Smart Tag list markup.
  5061. *
  5062. * @since 1.6.9
  5063. *
  5064. * @param {jQuery} $el Toggle element.
  5065. * @param {boolean} isFieldOption Is a field option.
  5066. *
  5067. * @returns {string} Smart Tags list markup.
  5068. */
  5069. getSmartTagsList: function( $el, isFieldOption ) {
  5070. var smartTagList;
  5071. smartTagList = '<ul class="smart-tags-list-display unfoldable-cont">';
  5072. smartTagList += app.getSmartTagsListFieldsElements( $el );
  5073. smartTagList += app.getSmartTagsListOtherElements( $el, isFieldOption );
  5074. smartTagList += '</ul>';
  5075. return smartTagList;
  5076. },
  5077. /**
  5078. * Get Smart Tag fields elements markup.
  5079. *
  5080. * @since 1.6.9
  5081. *
  5082. * @param {jQuery} $el Toggle element.
  5083. *
  5084. * @returns {string} Smart Tags list elements markup.
  5085. */
  5086. getSmartTagsListFieldsElements: function( $el ) {
  5087. var type = $el.data( 'type' ),
  5088. fields = app.getSmartTagsFields( $el ),
  5089. smartTagListElements = '';
  5090. if ( ! [ 'fields', 'all' ].includes( type ) ) {
  5091. return '';
  5092. }
  5093. if ( ! fields ) {
  5094. return '<li class="heading">' + wpforms_builder.fields_unavailable + '</li>';
  5095. }
  5096. smartTagListElements += '<li class="heading">' + wpforms_builder.fields_available + '</li>';
  5097. for ( var fieldKey in wpf.orders.fields ) {
  5098. var fieldId = wpf.orders.fields[ fieldKey ];
  5099. if ( ! fields[ fieldId ] ) {
  5100. continue;
  5101. }
  5102. smartTagListElements += app.getSmartTagsListFieldsElement( fields[ fieldId ] );
  5103. }
  5104. return smartTagListElements;
  5105. },
  5106. /**
  5107. * Get fields that possible to create smart tag.
  5108. *
  5109. * @since 1.6.9
  5110. *
  5111. * @param {jQuery} $el Toggle element.
  5112. *
  5113. * @returns {Array} Fields for smart tags.
  5114. */
  5115. getSmartTagsFields: function( $el ) {
  5116. var allowed = $el.data( 'fields' );
  5117. return allowed && allowed.length ? wpf.getFields( allowed.split( ',' ), true ) : wpf.getFields( false, true );
  5118. },
  5119. /**
  5120. * Get field markup for the Smart Tags list.
  5121. *
  5122. * @since 1.6.9
  5123. *
  5124. * @param {object} field A field.
  5125. *
  5126. * @returns {string} Smart Tags field markup.
  5127. */
  5128. getSmartTagsListFieldsElement: function( field ) {
  5129. var label = field.label ?
  5130. wpf.encodeHTMLEntities( wpf.sanitizeHTML( field.label ) ) :
  5131. wpforms_builder.field + ' #' + field.id;
  5132. return '<li><a href="#" data-type="field" data-meta=\'' + field.id + '\'>' + label + '</a></li>';
  5133. },
  5134. /**
  5135. * Get Smart Tag other elements markup.
  5136. *
  5137. * @since 1.6.9
  5138. *
  5139. * @param {jQuery} $el Toggle element.
  5140. * @param {boolean} isFieldOption Is a field option.
  5141. *
  5142. * @returns {string} Smart Tags list elements markup.
  5143. */
  5144. getSmartTagsListOtherElements: function( $el, isFieldOption ) {
  5145. var type = $el.data( 'type' ),
  5146. smartTagListElements;
  5147. if ( type !== 'other' && type !== 'all' ) {
  5148. return '';
  5149. }
  5150. smartTagListElements = '<li class="heading">' + wpforms_builder.other + '</li>';
  5151. for ( var smartTagKey in wpforms_builder.smart_tags ) {
  5152. if ( isFieldOption && wpforms_builder.smart_tags_disabled_for_fields.indexOf( smartTagKey ) > -1 ) {
  5153. continue;
  5154. }
  5155. smartTagListElements += '<li><a href="#" data-type="other" data-meta=\'' + smartTagKey + '\'>' + wpforms_builder.smart_tags[ smartTagKey ] + '</a></li>';
  5156. }
  5157. return smartTagListElements;
  5158. },
  5159. /**
  5160. * Smart Tag insert.
  5161. *
  5162. * @since 1.0.1
  5163. * @since 1.6.9 TinyMCE compatibility.
  5164. *
  5165. * @param {Event} e Event.
  5166. */
  5167. smartTagInsert: function( e ) {
  5168. e.preventDefault();
  5169. var $this = $( this ),
  5170. $list = $this.closest( '.smart-tags-list-display' ),
  5171. $wrapper = $list.closest( '.wpforms-panel-field,.wpforms-field-option-row' ),
  5172. $toggle = $wrapper.find( '.toggle-smart-tag-display' ),
  5173. $input = $wrapper.find( 'input[type=text], textarea' ),
  5174. meta = $this.data( 'meta' ),
  5175. type = $this.data( 'type' ),
  5176. smartTag = type === 'field' ? '{field_id="' + meta + '"}' : '{' + meta + '}',
  5177. editor;
  5178. if ( typeof tinyMCE !== 'undefined' ) {
  5179. editor = tinyMCE.get( $input.prop( 'id' ) );
  5180. if ( editor && ! editor.hasFocus() ) {
  5181. editor.focus( true );
  5182. }
  5183. }
  5184. editor && ! editor.isHidden() ?
  5185. editor.insertContent( smartTag ) :
  5186. $input.insertAtCaret( smartTag );
  5187. // remove list, all done!
  5188. $list.slideUp( '', function() {
  5189. $list.remove();
  5190. } );
  5191. $toggle.find( 'span' ).text( wpforms_builder.smart_tags_show );
  5192. $wrapper.find( '.toggle-smart-tag-display' ).removeClass( 'smart-tag-showing' );
  5193. },
  5194. /**
  5195. * Field map table - Delete row.
  5196. *
  5197. * @since 1.2.0
  5198. * @since 1.6.1.2 Registered `wpformsFieldMapTableDeletedRow` trigger.
  5199. */
  5200. fieldMapTableDeleteRow: function( e, el ) {
  5201. var $this = $( el ),
  5202. $row = $this.closest( 'tr' ),
  5203. $table = $this.closest( 'table' ),
  5204. $block = $row.closest( '.wpforms-builder-settings-block' ),
  5205. total = $table.find( 'tr' ).length;
  5206. if ( total > '1' ) {
  5207. $row.remove();
  5208. $builder.trigger( 'wpformsFieldMapTableDeletedRow', [ $block ] );
  5209. }
  5210. },
  5211. /**
  5212. * Field map table - Add row.
  5213. *
  5214. * @since 1.2.0
  5215. * @since 1.6.1.2 Registered `wpformsFieldMapTableAddedRow` trigger.
  5216. */
  5217. fieldMapTableAddRow: function( e, el ) {
  5218. var $this = $( el ),
  5219. $row = $this.closest( 'tr' ),
  5220. $block = $row.closest( '.wpforms-builder-settings-block' ),
  5221. choice = $row.clone().insertAfter( $row );
  5222. choice.find( 'input' ).val( '' );
  5223. choice.find( 'select :selected' ).prop( 'selected', false );
  5224. choice.find( '.key-destination' ).attr( 'name', '' );
  5225. $builder.trigger( 'wpformsFieldMapTableAddedRow', [ $block, choice ] );
  5226. },
  5227. /**
  5228. * Update field mapped select items on form updates.
  5229. *
  5230. * @since 1.2.0
  5231. * @since 1.6.1.2 Registered `wpformsFieldSelectMapped` trigger.
  5232. */
  5233. fieldMapSelect: function( e, fields ) {
  5234. $( '.wpforms-field-map-select' ).each( function( index, el ) {
  5235. var $this = $( this ),
  5236. selected = $this.find( 'option:selected' ).val(),
  5237. allowedFields = $this.data( 'field-map-allowed' ),
  5238. placeholder = $this.data( 'field-map-placeholder' );
  5239. // Check if custom placeholder was provided.
  5240. if ( typeof placeholder === 'undefined' || ! placeholder ) {
  5241. placeholder = wpforms_builder.select_field;
  5242. }
  5243. // Reset select and add a placeholder option.
  5244. $this.empty().append( $( '<option>', { value: '', text : placeholder } ) );
  5245. // If allowed fields are not defined, bail.
  5246. if ( typeof allowedFields !== 'undefined' && allowedFields ) {
  5247. allowedFields = allowedFields.split( ' ' );
  5248. } else {
  5249. return;
  5250. }
  5251. // Loop through the current fields, if we have fields for the form.
  5252. if ( fields && ! $.isEmptyObject( fields ) ) {
  5253. for ( var key in wpf.orders.fields ) {
  5254. if ( ! Object.prototype.hasOwnProperty.call( wpf.orders.fields, key ) ) {
  5255. continue;
  5256. }
  5257. var fieldID = wpf.orders.fields[ key ],
  5258. label = '';
  5259. if ( ! fields[ fieldID ] ) {
  5260. continue;
  5261. }
  5262. // Prepare the label.
  5263. if ( typeof fields[ fieldID ].label !== 'undefined' && fields[ fieldID ].label.toString().trim() !== '' ) {
  5264. label = wpf.sanitizeHTML( fields[ fieldID ].label.toString().trim() );
  5265. } else {
  5266. label = wpforms_builder.field + ' #' + fieldID;
  5267. }
  5268. // Add to select if it is a field type allowed.
  5269. if ( $.inArray( fields[ fieldID ].type, allowedFields ) >= 0 || $.inArray( 'all-fields', allowedFields ) >= 0 ) {
  5270. $this.append( $( '<option>', { value: fields[ fieldID ].id, text : label } ) );
  5271. }
  5272. }
  5273. }
  5274. // Restore previous value if found.
  5275. if ( selected ) {
  5276. $this.find( 'option[value="' + selected + '"]' ).prop( 'selected', true );
  5277. }
  5278. // Add a "Custom Value" option, if it support.
  5279. var customValueSupport = $this.data( 'custom-value-support' );
  5280. if ( typeof customValueSupport === 'boolean' && customValueSupport ) {
  5281. $this.append(
  5282. $( '<option>', {
  5283. value: 'custom_value',
  5284. text: wpforms_builder.add_custom_value_label,
  5285. class: 'wpforms-field-map-option-custom-value',
  5286. } )
  5287. );
  5288. }
  5289. $builder.trigger( 'wpformsFieldSelectMapped', [ $this ] );
  5290. } );
  5291. },
  5292. /**
  5293. * Validate email smart tags in Notifications fields.
  5294. *
  5295. * @param {object} $el Input field to check the value for.
  5296. *
  5297. * @since 1.4.9
  5298. */
  5299. validateEmailSmartTags: function( $el ) {
  5300. var val = $el.val();
  5301. if ( ! val ) {
  5302. return;
  5303. }
  5304. // Turns '{email@domain.com}' into 'email@domain.com'.
  5305. // Email RegEx inspired by http://emailregex.com
  5306. val = val.replace( /{(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))}/g, function( x ) {
  5307. return x.slice( 1, -1 );
  5308. } );
  5309. $el.val( val );
  5310. },
  5311. //--------------------------------------------------------------------//
  5312. // Alerts (notices).
  5313. //--------------------------------------------------------------------//
  5314. /**
  5315. * Click on the Dismiss notice button.
  5316. *
  5317. * @since 1.6.7
  5318. */
  5319. dismissNotice: function() {
  5320. $builder.on( 'click', '.wpforms-alert-dismissible .wpforms-dismiss-button', function( e ) {
  5321. e.preventDefault();
  5322. var $button = $( this ),
  5323. $alert = $button.closest( '.wpforms-alert' ),
  5324. fieldId = $button.data( 'field-id' );
  5325. $alert.addClass( 'out' );
  5326. setTimeout( function() {
  5327. $alert.remove();
  5328. }, 250 );
  5329. if ( fieldId ) {
  5330. $( '#wpforms-field-option-' + fieldId ).remove();
  5331. }
  5332. } );
  5333. },
  5334. //--------------------------------------------------------------------//
  5335. // Other functions.
  5336. //--------------------------------------------------------------------//
  5337. /**
  5338. * Trim long form titles.
  5339. *
  5340. * @since 1.0.0
  5341. */
  5342. trimFormTitle: function() {
  5343. var $title = $( '.wpforms-center-form-name' );
  5344. if ( $title.text().length > 38 ) {
  5345. var shortTitle = $.trim( $title.text() ).substring( 0, 38 ).split( ' ' ).slice( 0, -1 ).join( ' ' ) + '...';
  5346. $title.text( shortTitle );
  5347. }
  5348. },
  5349. /**
  5350. * Load or refresh color picker.
  5351. *
  5352. * @since 1.2.1
  5353. */
  5354. loadColorPickers: function() {
  5355. $('.wpforms-color-picker').minicolors();
  5356. },
  5357. /**
  5358. * Hotkeys:
  5359. * Ctrl+H - Help.
  5360. * Ctrl+P - Preview.
  5361. * Ctrl+B - Embed.
  5362. * Ctrl+E - Entries.
  5363. * Ctrl+S - Save.
  5364. * Ctrl+Q - Exit.
  5365. * Ctrl+/ - Keyboard Shortcuts modal.
  5366. *
  5367. * @since 1.2.4
  5368. */
  5369. builderHotkeys: function() {
  5370. $( document ).keydown( function( e ) {
  5371. if ( ! e.ctrlKey ) {
  5372. return;
  5373. }
  5374. switch ( e.keyCode ) {
  5375. case 72: // Open Help screen on Ctrl+H.
  5376. $( '#wpforms-help', $builder ).click();
  5377. break;
  5378. case 80: // Open Form Preview tab on Ctrl+P.
  5379. window.open( wpforms_builder.preview_url );
  5380. break;
  5381. case 66: // Trigger the Embed modal on Ctrl+B.
  5382. $( '#wpforms-embed', $builder ).click();
  5383. break;
  5384. case 69: // Open Entries tab on Ctrl+E.
  5385. window.open( wpforms_builder.entries_url );
  5386. break;
  5387. case 83: // Trigger the Builder save on Ctrl+S.
  5388. $( '#wpforms-save', $builder ).click();
  5389. break;
  5390. case 81: // Trigger the Exit on Ctrl+Q.
  5391. $( '#wpforms-exit', $builder ).click();
  5392. break;
  5393. case 191: // Keyboard shortcuts modal on Ctrl+/.
  5394. app.openKeyboardShortcutsModal();
  5395. break;
  5396. default:
  5397. return;
  5398. }
  5399. return false;
  5400. } );
  5401. },
  5402. /**
  5403. * Open Keyboard Shortcuts modal.
  5404. *
  5405. * @since 1.6.9
  5406. */
  5407. openKeyboardShortcutsModal: function() {
  5408. // Close already opened instance.
  5409. if ( $( '.wpforms-builder-keyboard-shortcuts' ).length ) {
  5410. jconfirm.instances[ jconfirm.instances.length - 1 ].close();
  5411. return;
  5412. }
  5413. $.alert( {
  5414. title: wpforms_builder.shortcuts_modal_title,
  5415. content: wpforms_builder.shortcuts_modal_msg + wp.template( 'wpforms-builder-keyboard-shortcuts' )(),
  5416. icon: 'fa fa-keyboard-o',
  5417. type: 'blue',
  5418. boxWidth: '550px',
  5419. smoothContent: false,
  5420. buttons: {
  5421. confirm: {
  5422. text: wpforms_builder.close,
  5423. btnClass: 'btn-confirm',
  5424. keys: [ 'enter' ],
  5425. },
  5426. },
  5427. onOpenBefore: function() {
  5428. this.$body.addClass( 'wpforms-builder-keyboard-shortcuts' );
  5429. },
  5430. } );
  5431. },
  5432. /**
  5433. * Register JS templates for various elements.
  5434. *
  5435. * @since 1.4.8
  5436. */
  5437. registerTemplates: function () {
  5438. if (typeof WPForms === 'undefined') {
  5439. return;
  5440. }
  5441. WPForms.Admin.Builder.Templates.add([
  5442. 'wpforms-builder-confirmations-message-field',
  5443. 'wpforms-builder-conditional-logic-toggle-field'
  5444. ]);
  5445. },
  5446. /**
  5447. * Exit builder.
  5448. *
  5449. * @since 1.5.7
  5450. */
  5451. exitBack: function() {
  5452. if ( 1 < window.history.length && document.referrer ) {
  5453. window.history.back();
  5454. } else {
  5455. window.location.href = wpforms_builder.exit_url;
  5456. }
  5457. },
  5458. };
  5459. // Provide access to public functions/properties.
  5460. return app;
  5461. }( document, window, jQuery ) );
  5462. WPFormsBuilder.init();