fl-builder.js 313 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389739073917392739373947395739673977398739974007401740274037404740574067407740874097410741174127413741474157416741774187419742074217422742374247425742674277428742974307431743274337434743574367437743874397440744174427443744474457446744774487449745074517452745374547455745674577458745974607461746274637464746574667467746874697470747174727473747474757476747774787479748074817482748374847485748674877488748974907491749274937494749574967497749874997500750175027503750475057506750775087509751075117512751375147515751675177518751975207521752275237524752575267527752875297530753175327533753475357536753775387539754075417542754375447545754675477548754975507551755275537554755575567557755875597560756175627563756475657566756775687569757075717572757375747575757675777578757975807581758275837584758575867587758875897590759175927593759475957596759775987599760076017602760376047605760676077608760976107611761276137614761576167617761876197620762176227623762476257626762776287629763076317632763376347635763676377638763976407641764276437644764576467647764876497650765176527653765476557656765776587659766076617662766376647665766676677668766976707671767276737674767576767677767876797680768176827683768476857686768776887689769076917692769376947695769676977698769977007701770277037704770577067707770877097710771177127713771477157716771777187719772077217722772377247725772677277728772977307731773277337734773577367737773877397740774177427743774477457746774777487749775077517752775377547755775677577758775977607761776277637764776577667767776877697770777177727773777477757776777777787779778077817782778377847785778677877788778977907791779277937794779577967797779877997800780178027803780478057806780778087809781078117812781378147815781678177818781978207821782278237824782578267827782878297830783178327833783478357836783778387839784078417842784378447845784678477848784978507851785278537854785578567857785878597860786178627863786478657866786778687869787078717872787378747875787678777878787978807881788278837884788578867887788878897890789178927893789478957896789778987899790079017902790379047905790679077908790979107911791279137914791579167917791879197920792179227923792479257926792779287929793079317932793379347935793679377938793979407941794279437944794579467947794879497950795179527953795479557956795779587959796079617962796379647965796679677968796979707971797279737974797579767977797879797980798179827983798479857986798779887989799079917992799379947995799679977998799980008001800280038004800580068007800880098010801180128013801480158016801780188019802080218022802380248025802680278028802980308031803280338034803580368037803880398040804180428043804480458046804780488049805080518052805380548055805680578058805980608061806280638064806580668067806880698070807180728073807480758076807780788079808080818082808380848085808680878088808980908091809280938094809580968097809880998100810181028103810481058106810781088109811081118112811381148115811681178118811981208121812281238124812581268127812881298130813181328133813481358136813781388139814081418142814381448145814681478148814981508151815281538154815581568157815881598160816181628163816481658166816781688169817081718172817381748175817681778178817981808181818281838184818581868187818881898190819181928193819481958196819781988199820082018202820382048205820682078208820982108211821282138214821582168217821882198220822182228223822482258226822782288229823082318232823382348235823682378238823982408241824282438244824582468247824882498250825182528253825482558256825782588259826082618262826382648265826682678268826982708271827282738274827582768277827882798280828182828283828482858286828782888289829082918292829382948295829682978298829983008301830283038304830583068307830883098310831183128313831483158316831783188319832083218322832383248325832683278328832983308331833283338334833583368337833883398340834183428343834483458346834783488349835083518352835383548355835683578358835983608361836283638364836583668367836883698370837183728373837483758376837783788379838083818382838383848385838683878388838983908391839283938394839583968397839883998400840184028403840484058406840784088409841084118412841384148415841684178418841984208421842284238424842584268427842884298430843184328433843484358436843784388439844084418442844384448445844684478448844984508451845284538454845584568457845884598460846184628463846484658466846784688469847084718472847384748475847684778478847984808481848284838484848584868487848884898490849184928493849484958496849784988499850085018502850385048505850685078508850985108511851285138514851585168517851885198520852185228523852485258526852785288529853085318532853385348535853685378538853985408541854285438544854585468547854885498550855185528553855485558556855785588559856085618562856385648565856685678568856985708571857285738574857585768577857885798580858185828583858485858586858785888589859085918592859385948595859685978598859986008601860286038604860586068607860886098610861186128613861486158616861786188619862086218622862386248625862686278628862986308631863286338634863586368637863886398640864186428643864486458646864786488649865086518652865386548655865686578658865986608661866286638664866586668667866886698670867186728673867486758676867786788679868086818682868386848685868686878688868986908691869286938694869586968697869886998700870187028703870487058706870787088709871087118712871387148715871687178718871987208721872287238724872587268727872887298730873187328733873487358736873787388739874087418742874387448745874687478748874987508751875287538754875587568757875887598760876187628763876487658766876787688769877087718772877387748775877687778778877987808781878287838784878587868787878887898790879187928793879487958796879787988799880088018802880388048805880688078808880988108811881288138814881588168817881888198820882188228823882488258826882788288829883088318832883388348835883688378838883988408841884288438844884588468847884888498850885188528853885488558856885788588859886088618862886388648865886688678868886988708871887288738874887588768877887888798880888188828883888488858886888788888889889088918892889388948895889688978898889989008901890289038904890589068907890889098910891189128913891489158916891789188919892089218922892389248925892689278928892989308931893289338934893589368937893889398940894189428943894489458946894789488949895089518952895389548955895689578958895989608961896289638964896589668967896889698970897189728973897489758976897789788979898089818982898389848985898689878988898989908991899289938994899589968997899889999000900190029003900490059006900790089009901090119012901390149015901690179018901990209021902290239024902590269027902890299030903190329033903490359036903790389039904090419042904390449045904690479048904990509051905290539054905590569057905890599060906190629063906490659066906790689069907090719072907390749075907690779078907990809081908290839084908590869087908890899090909190929093909490959096909790989099910091019102910391049105910691079108910991109111911291139114911591169117911891199120912191229123912491259126912791289129913091319132913391349135913691379138913991409141914291439144914591469147914891499150915191529153915491559156915791589159916091619162916391649165916691679168916991709171917291739174917591769177917891799180918191829183918491859186918791889189919091919192919391949195919691979198919992009201920292039204920592069207920892099210921192129213921492159216921792189219922092219222922392249225922692279228922992309231923292339234923592369237923892399240924192429243924492459246924792489249925092519252925392549255925692579258925992609261926292639264926592669267926892699270927192729273927492759276927792789279928092819282928392849285928692879288928992909291929292939294929592969297929892999300930193029303930493059306930793089309931093119312931393149315931693179318931993209321932293239324932593269327932893299330933193329333933493359336933793389339934093419342934393449345934693479348934993509351935293539354935593569357935893599360936193629363936493659366936793689369937093719372937393749375937693779378937993809381938293839384938593869387938893899390939193929393939493959396939793989399940094019402940394049405940694079408940994109411941294139414941594169417941894199420942194229423942494259426942794289429943094319432943394349435943694379438943994409441944294439444944594469447944894499450945194529453945494559456945794589459946094619462946394649465946694679468946994709471947294739474947594769477947894799480948194829483948494859486948794889489949094919492949394949495949694979498949995009501950295039504950595069507950895099510951195129513951495159516951795189519952095219522952395249525952695279528952995309531953295339534953595369537953895399540954195429543954495459546954795489549955095519552955395549555955695579558955995609561956295639564956595669567956895699570957195729573957495759576957795789579958095819582958395849585958695879588958995909591959295939594959595969597959895999600960196029603960496059606960796089609961096119612961396149615961696179618961996209621962296239624962596269627962896299630963196329633963496359636963796389639964096419642964396449645964696479648964996509651965296539654965596569657965896599660966196629663966496659666966796689669967096719672967396749675967696779678967996809681968296839684968596869687968896899690969196929693969496959696969796989699970097019702970397049705970697079708970997109711971297139714971597169717971897199720972197229723972497259726972797289729973097319732973397349735973697379738973997409741974297439744974597469747974897499750975197529753975497559756975797589759976097619762976397649765976697679768976997709771977297739774977597769777977897799780978197829783978497859786978797889789979097919792979397949795979697979798979998009801980298039804980598069807980898099810981198129813981498159816981798189819982098219822982398249825982698279828982998309831983298339834983598369837983898399840984198429843984498459846984798489849985098519852985398549855985698579858985998609861986298639864986598669867986898699870987198729873987498759876987798789879988098819882988398849885988698879888988998909891989298939894989598969897989898999900990199029903990499059906990799089909991099119912991399149915991699179918991999209921992299239924992599269927992899299930993199329933993499359936993799389939994099419942994399449945994699479948994999509951995299539954995599569957995899599960996199629963996499659966996799689969997099719972997399749975997699779978997999809981998299839984998599869987998899899990999199929993999499959996999799989999100001000110002100031000410005100061000710008100091001010011100121001310014100151001610017100181001910020100211002210023100241002510026100271002810029100301003110032100331003410035100361003710038100391004010041100421004310044100451004610047100481004910050100511005210053100541005510056100571005810059100601006110062100631006410065100661006710068100691007010071100721007310074100751007610077100781007910080100811008210083100841008510086100871008810089100901009110092100931009410095100961009710098100991010010101101021010310104101051010610107101081010910110101111011210113101141011510116101171011810119101201012110122101231012410125101261012710128101291013010131101321013310134101351013610137101381013910140101411014210143101441014510146101471014810149101501015110152101531015410155101561015710158101591016010161101621016310164101651016610167101681016910170101711017210173101741017510176101771017810179101801018110182101831018410185101861018710188101891019010191101921019310194101951019610197101981019910200102011020210203102041020510206102071020810209102101021110212102131021410215102161021710218102191022010221102221022310224102251022610227102281022910230102311023210233102341023510236102371023810239102401024110242102431024410245102461024710248102491025010251102521025310254102551025610257102581025910260102611026210263102641026510266102671026810269102701027110272102731027410275102761027710278102791028010281102821028310284102851028610287102881028910290102911029210293102941029510296102971029810299103001030110302103031030410305103061030710308103091031010311103121031310314103151031610317103181031910320103211032210323103241032510326103271032810329103301033110332103331033410335103361033710338103391034010341103421034310344103451034610347103481034910350103511035210353103541035510356103571035810359103601036110362103631036410365103661036710368103691037010371103721037310374103751037610377103781037910380103811038210383103841038510386103871038810389103901039110392103931039410395103961039710398103991040010401104021040310404104051040610407104081040910410104111041210413104141041510416104171041810419104201042110422104231042410425104261042710428104291043010431104321043310434104351043610437104381043910440104411044210443104441044510446104471044810449104501045110452104531045410455104561045710458104591046010461104621046310464104651046610467104681046910470104711047210473104741047510476104771047810479104801048110482104831048410485104861048710488104891049010491104921049310494104951049610497104981049910500105011050210503105041050510506105071050810509105101051110512105131051410515105161051710518105191052010521105221052310524105251052610527105281052910530105311053210533105341053510536105371053810539105401054110542105431054410545105461054710548105491055010551105521055310554105551055610557105581055910560105611056210563105641056510566105671056810569105701057110572105731057410575105761057710578105791058010581105821058310584105851058610587105881058910590105911059210593105941059510596105971059810599106001060110602106031060410605106061060710608106091061010611106121061310614106151061610617106181061910620106211062210623106241062510626106271062810629106301063110632106331063410635106361063710638106391064010641106421064310644106451064610647106481064910650106511065210653106541065510656106571065810659106601066110662106631066410665106661066710668106691067010671106721067310674106751067610677106781067910680106811068210683106841068510686106871068810689106901069110692106931069410695106961069710698106991070010701107021070310704107051070610707107081070910710107111071210713107141071510716107171071810719107201072110722107231072410725107261072710728107291073010731107321073310734107351073610737107381073910740107411074210743107441074510746107471074810749107501075110752107531075410755107561075710758107591076010761107621076310764107651076610767107681076910770107711077210773107741077510776107771077810779107801078110782107831078410785107861078710788107891079010791107921079310794107951079610797107981079910800108011080210803108041080510806108071080810809108101081110812108131081410815108161081710818108191082010821108221082310824108251082610827108281082910830108311083210833108341083510836108371083810839108401084110842108431084410845108461084710848108491085010851108521085310854108551085610857108581085910860108611086210863108641086510866108671086810869108701087110872108731087410875108761087710878108791088010881108821088310884108851088610887108881088910890108911089210893108941089510896108971089810899109001090110902109031090410905109061090710908109091091010911109121091310914109151091610917109181091910920109211092210923109241092510926109271092810929109301093110932109331093410935109361093710938109391094010941109421094310944109451094610947109481094910950109511095210953109541095510956
  1. (function($){
  2. /**
  3. * The main builder interface class.
  4. *
  5. * @since 1.0
  6. * @class FLBuilder
  7. */
  8. FLBuilder = {
  9. /**
  10. * An instance of FLBuilderPreview for working
  11. * with the current live preview.
  12. *
  13. * @since 1.3.3
  14. * @property {FLBuilderPreview} preview
  15. */
  16. preview : null,
  17. /**
  18. * An instance of FLLightbox for displaying a list
  19. * of actions a user can take such as publish or cancel.
  20. *
  21. * @since 1.0
  22. * @access private
  23. * @property {FLLightbox} _actionsLightbox
  24. */
  25. _actionsLightbox : null,
  26. /**
  27. * An array of AJAX data that needs to be requested
  28. * after the current request has finished.
  29. *
  30. * @since 2.2
  31. * @property {Array} _ajaxQueue
  32. */
  33. _ajaxQueue : [],
  34. /**
  35. * A reference to the current AJAX request object.
  36. *
  37. * @since 2.2
  38. * @property {Object} _ajaxRequest
  39. */
  40. _ajaxRequest : null,
  41. /**
  42. * An object that holds data for column resizing.
  43. *
  44. * @since 1.6.4
  45. * @access private
  46. * @property {Object} _colResizeData
  47. */
  48. _colResizeData : null,
  49. /**
  50. * A flag for whether a column is being resized or not.
  51. *
  52. * @since 1.6.4
  53. * @access private
  54. * @property {Boolean} _colResizing
  55. */
  56. _colResizing : false,
  57. /**
  58. * The CSS class of the main content wrapper for the
  59. * current layout that is being worked on.
  60. *
  61. * @since 1.0
  62. * @access private
  63. * @property {String} _contentClass
  64. */
  65. _contentClass : false,
  66. /**
  67. * Whether dragging has been enabled or not.
  68. *
  69. * @since 1.0
  70. * @access private
  71. * @property {Boolean} _dragEnabled
  72. */
  73. _dragEnabled : false,
  74. /**
  75. * Whether an element is currently being dragged or not.
  76. *
  77. * @since 1.0
  78. * @access private
  79. * @property {Boolean} _dragging
  80. */
  81. _dragging : false,
  82. /**
  83. * The initial scroll top of the window when a drag starts.
  84. * Used to reset the scroll top when a drag is cancelled.
  85. *
  86. * @since 1.0
  87. * @access private
  88. * @property {Boolean} _dragging
  89. */
  90. _dragInitialScrollTop : 0,
  91. /**
  92. * The URL to redirect to when a user leaves the builder.
  93. *
  94. * @since 1.0
  95. * @access private
  96. * @property {String} _exitUrl
  97. */
  98. _exitUrl : null,
  99. /**
  100. * An instance of FLBuilderAJAXLayout for rendering
  101. * the layout via AJAX.
  102. *
  103. * @since 1.7
  104. * @property {FLBuilderAJAXLayout} _layout
  105. */
  106. _layout : null,
  107. /**
  108. * An array of layout data that needs to be rendered
  109. * after the current rendered is finished.
  110. *
  111. * @since 2.2
  112. * @property {Array} _layoutQueue
  113. */
  114. _layoutQueue : [],
  115. /**
  116. * A cached copy of custom layout CSS that is used to
  117. * revert changes if the cancel button is clicked.
  118. *
  119. * @since 1.7
  120. * @property {String} _layoutSettingsCSSCache
  121. */
  122. _layoutSettingsCSSCache : null,
  123. /**
  124. * A timeout for throttling custom layout CSS changes.
  125. *
  126. * @since 1.7
  127. * @property {Object} _layoutSettingsCSSTimeout
  128. */
  129. _layoutSettingsCSSTimeout : null,
  130. /**
  131. * An instance of FLLightbox for displaying settings.
  132. *
  133. * @since 1.0
  134. * @access private
  135. * @property {FLLightbox} _lightbox
  136. */
  137. _lightbox : null,
  138. /**
  139. * A timeout for refreshing the height of lightbox scrollbars
  140. * in case the content changes from dynamic settings.
  141. *
  142. * @since 1.0
  143. * @access private
  144. * @property {Object} _lightboxScrollbarTimeout
  145. */
  146. _lightboxScrollbarTimeout : null,
  147. /**
  148. * An array that's used to cache which module settings
  149. * CSS and JS assets have already been loaded so they
  150. * are only loaded once.
  151. *
  152. * @since 1.0
  153. * @access private
  154. * @property {Array} _loadedModuleAssets
  155. */
  156. _loadedModuleAssets : [],
  157. /**
  158. * An object used to store module settings helpers.
  159. *
  160. * @since 1.0
  161. * @access private
  162. * @property {Object} _moduleHelpers
  163. */
  164. _moduleHelpers : {},
  165. /**
  166. * An instance of wp.media used to select multiple photos.
  167. *
  168. * @since 1.0
  169. * @access private
  170. * @property {Object} _multiplePhotoSelector
  171. */
  172. _multiplePhotoSelector : null,
  173. /**
  174. * A jQuery reference to a group that a new column
  175. * should be added to once it's finished rendering.
  176. *
  177. * @since 2.0
  178. * @access private
  179. * @property {Object} _newColParent
  180. */
  181. _newColParent : null,
  182. /**
  183. * The position a column should be added to within
  184. * a group once it finishes rendering.
  185. *
  186. * @since 2.0
  187. * @access private
  188. * @property {Number} _newColPosition
  189. */
  190. _newColPosition : 0,
  191. /**
  192. * A jQuery reference to a row that a new column group
  193. * should be added to once it's finished rendering.
  194. *
  195. * @since 1.0
  196. * @access private
  197. * @property {Object} _newColGroupParent
  198. */
  199. _newColGroupParent : null,
  200. /**
  201. * The position a column group should be added to within
  202. * a row once it finishes rendering.
  203. *
  204. * @since 1.0
  205. * @access private
  206. * @property {Number} _newColGroupPosition
  207. */
  208. _newColGroupPosition : 0,
  209. /**
  210. * A jQuery reference to a new module's parent.
  211. *
  212. * @since 1.7
  213. * @access private
  214. * @property {Object} _newModuleParent
  215. */
  216. _newModuleParent : null,
  217. /**
  218. * The position a new module should be added at once
  219. * it finishes rendering.
  220. *
  221. * @since 1.7
  222. * @access private
  223. * @property {Number} _newModulePosition
  224. */
  225. _newModulePosition : 0,
  226. /**
  227. * The position a row should be added to within
  228. * the layout once it finishes rendering.
  229. *
  230. * @since 1.0
  231. * @access private
  232. * @property {Number} _newRowPosition
  233. */
  234. _newRowPosition : 0,
  235. /**
  236. * The ID of a template that the user has selected.
  237. *
  238. * @since 1.0
  239. * @access private
  240. * @property {Number} _selectedTemplateId
  241. */
  242. _selectedTemplateId : null,
  243. /**
  244. * The type of template that the user has selected.
  245. * Possible values are "core" or "user".
  246. *
  247. * @since 1.0
  248. * @access private
  249. * @property {String} _selectedTemplateType
  250. */
  251. _selectedTemplateType : null,
  252. /**
  253. * An instance of wp.media used to select a single photo.
  254. *
  255. * @since 1.0
  256. * @access private
  257. * @property {Object} _singlePhotoSelector
  258. */
  259. _singlePhotoSelector : null,
  260. /**
  261. * An instance of wp.media used to select a single video.
  262. *
  263. * @since 1.0
  264. * @access private
  265. * @property {Object} _singleVideoSelector
  266. */
  267. _singleVideoSelector : null,
  268. /**
  269. * An instance of wp.media used to select a multiple audio.
  270. *
  271. * @since 1.0
  272. * @access private
  273. * @property {Object} _multipleAudiosSelector
  274. */
  275. _multipleAudiosSelector : null,
  276. /**
  277. * @since 2.2.5
  278. */
  279. _codeDisabled: false,
  280. /**
  281. * Misc data container
  282. * @since 2.6
  283. * @access private
  284. */
  285. _sandbox: {},
  286. /**
  287. * Flag whether to clear preview or not
  288. * @access private
  289. */
  290. _publishAndRemain: false,
  291. _shapesEdited: false,
  292. /**
  293. * Initializes the builder interface.
  294. *
  295. * @since 1.0
  296. * @access private
  297. * @method _init
  298. */
  299. _init: function()
  300. {
  301. FLBuilder.UIIFrame.init();
  302. FLBuilder._initJQueryReadyFix();
  303. FLBuilder._initGlobalErrorHandling();
  304. FLBuilder._initPostLock();
  305. FLBuilder._initClassNames();
  306. FLBuilder._initMediaUploader();
  307. FLBuilder._initOverflowFix();
  308. FLBuilder._initScrollbars();
  309. FLBuilder._initLightboxes();
  310. FLBuilder._initDropTargets();
  311. FLBuilder._initSortables();
  312. FLBuilder._initStrings();
  313. FLBuilder._initSanityChecks();
  314. FLBuilder._initTipTips();
  315. FLBuilder._initTinyMCE();
  316. FLBuilder._bindEvents();
  317. FLBuilder._bindOverlayEvents();
  318. FLBuilder._setupEmptyLayout();
  319. FLBuilder._highlightEmptyCols();
  320. FLBuilder._checkEnv();
  321. FLBuilder._initColorScheme();
  322. FLBuilder.addHook('didInitUI', FLBuilder._showTourOrTemplates.bind(FLBuilder) );
  323. FLBuilder.addHook('endEditingSession', FLBuilder._doStats.bind(this) );
  324. FLBuilder.triggerHook('init');
  325. },
  326. /**
  327. * Prevent errors thrown in jQuery's ready function
  328. * from breaking subsequent ready calls.
  329. *
  330. * @since 1.4.6
  331. * @access private
  332. * @method _initJQueryReadyFix
  333. */
  334. _initJQueryReadyFix: function()
  335. {
  336. if ( FLBuilderConfig.debug ) {
  337. return;
  338. }
  339. jQuery.fn.oldReady = jQuery.fn.ready;
  340. jQuery.fn.ready = function( fn ) {
  341. return jQuery.fn.oldReady( function() {
  342. try {
  343. if ( 'function' == typeof fn ) {
  344. fn( $ );
  345. }
  346. }
  347. catch ( e ){
  348. FLBuilder.logError( e );
  349. }
  350. });
  351. };
  352. },
  353. _initSanityChecks: function() {
  354. if ( FLBuilderConfig.uploadPath && typeof FLBuilderLayout === 'undefined' ) {
  355. url = '<a href="' + FLBuilderConfig.uploadUrl + '">wp-admin -> Settings -> Media</a>';
  356. FLBuilder.alert( '<strong>Critcal Error</strong><p style="font-size:15px;">Please go to ' + url + ' and make sure uploads folder settings is blank</p>');
  357. $('.fl-builder-alert-close', window.parent.document).hide()
  358. }
  359. },
  360. /**
  361. * Try to prevent errors from third party plugins
  362. * from breaking the builder.
  363. *
  364. * @since 1.4.6
  365. * @access private
  366. * @method _initGlobalErrorHandling
  367. */
  368. _initGlobalErrorHandling: function()
  369. {
  370. if ( FLBuilderConfig.debug ) {
  371. return;
  372. }
  373. window.onerror = function( message, file, line, col, error ) {
  374. FLBuilder.logGlobalError( message, file, line, col, error );
  375. return true;
  376. };
  377. },
  378. /**
  379. * Send a wp.heartbeat request to lock editing of this
  380. * post so it can only be edited by the current user.
  381. *
  382. * @since 1.0.6
  383. * @access private
  384. * @method _initPostLock
  385. */
  386. _initPostLock: function()
  387. {
  388. if(typeof wp.heartbeat != 'undefined') {
  389. wp.heartbeat.interval(120);
  390. wp.heartbeat.enqueue('fl_builder_post_lock', {
  391. post_id: FLBuilderConfig.postId
  392. });
  393. }
  394. },
  395. /**
  396. * Initializes html and body classes as well as the
  397. * builder content class for this post.
  398. *
  399. * @since 1.0
  400. * @access private
  401. * @method _initClassNames
  402. */
  403. _initClassNames: function()
  404. {
  405. var html = $( 'html' ).add( 'html', window.parent.document ),
  406. body = $( 'body' ).add( 'body', window.parent.document );
  407. html.addClass( 'fl-builder-edit' );
  408. body.addClass( 'fl-builder' );
  409. if ( FLBuilderConfig.simpleUi ) {
  410. body.addClass( 'fl-builder-simple' );
  411. }
  412. FLBuilder._contentClass = '.fl-builder-content-' + FLBuilderConfig.postId;
  413. $( FLBuilder._contentClass ).addClass( 'fl-builder-content-editing' );
  414. },
  415. /**
  416. * Initializes the WordPress media uploader so any files
  417. * uploaded will be attached to the current post.
  418. *
  419. * @since 1.2.2
  420. * @access private
  421. * @method _initMediaUploader
  422. */
  423. _initMediaUploader: function()
  424. {
  425. wp.media.model.settings.post.id = FLBuilderConfig.postId;
  426. },
  427. /**
  428. * Third party themes that set their content wrappers to
  429. * overflow:hidden break builder overlays. We set them
  430. * to overflow:visible while editing.
  431. *
  432. * @since 1.0
  433. * @access private
  434. * @method _initOverflowFix
  435. */
  436. _initOverflowFix: function()
  437. {
  438. $(FLBuilder._contentClass).parents().css('overflow', 'visible');
  439. },
  440. /**
  441. * Initializes Nano Scroller scrollbars for the
  442. * builder interface.
  443. *
  444. * @since 1.0
  445. * @access private
  446. * @method _initScrollbars
  447. */
  448. _initScrollbars: function()
  449. {
  450. var scrollers = $('.fl-nanoscroller', window.parent.document).nanoScroller({
  451. documentContext: window.parent.document,
  452. alwaysVisible: false,
  453. preventPageScrolling: true,
  454. paneClass: 'fl-nanoscroller-pane',
  455. sliderClass: 'fl-nanoscroller-slider',
  456. contentClass: 'fl-nanoscroller-content'
  457. }),
  458. settingsScroller = scrollers.filter('.fl-builder-settings-fields'),
  459. pane = settingsScroller.find('.fl-nanoscroller-pane');
  460. if ( pane.length ) {
  461. var display = pane.get(0).style.display;
  462. var content = settingsScroller.find('.fl-nanoscroller-content');
  463. if ( display === "none" ) {
  464. content.removeClass('has-scrollbar');
  465. } else {
  466. content.addClass('has-scrollbar');
  467. }
  468. }
  469. },
  470. /**
  471. * Initializes jQuery sortables for drag and drop.
  472. *
  473. * @since 1.0
  474. * @access private
  475. * @method _initSortables
  476. */
  477. _initSortables: function()
  478. {
  479. var defaults = {
  480. frame: null,
  481. appendTo: FLBuilder._contentClass,
  482. scroll: true,
  483. cursor: 'move',
  484. cursorAt: {
  485. left: 85,
  486. top: 20
  487. },
  488. distance: 1,
  489. helper: FLBuilder._blockDragHelper,
  490. start : FLBuilder._blockDragStart,
  491. sort: FLBuilder._blockDragSort,
  492. change: FLBuilder._blockDragChange,
  493. stop: FLBuilder._blockDragStop,
  494. placeholder: 'fl-builder-drop-zone',
  495. tolerance: 'intersect'
  496. },
  497. rowConnections = '',
  498. columnConnections = '',
  499. moduleConnections = '';
  500. // Adjust defaults for the iFrame UI.
  501. if ( FLBuilder.UIIFrame.isEnabled() ) {
  502. defaults.frame = $( '#fl-builder-ui-iframe', window.parent.document );
  503. defaults.appendTo = $( 'body', window.parent.document );
  504. defaults.scroll = false;
  505. }
  506. // Module Connections.
  507. if ( 'row' == FLBuilderConfig.userTemplateType ) {
  508. moduleConnections = FLBuilder._contentClass + ' .fl-row:not(.fl-builder-node-loading) .fl-col-group-drop-target, ' +
  509. FLBuilder._contentClass + ' .fl-row:not(.fl-builder-node-loading) .fl-col-drop-target, ' +
  510. FLBuilder._contentClass + ' .fl-row:not(.fl-builder-node-loading) .fl-col-content, ' +
  511. FLBuilder._contentClass + ' .fl-row:not(.fl-builder-node-loading) .fl-module[data-accepts]:not(:has(> .fl-module-content)), ' +
  512. FLBuilder._contentClass + ' .fl-row:not(.fl-builder-node-loading) .fl-module[data-accepts] > .fl-module-content';
  513. }
  514. else if ( 'column' == FLBuilderConfig.userTemplateType ) {
  515. moduleConnections = FLBuilder._contentClass + ' .fl-col-group-drop-target, ' +
  516. FLBuilder._contentClass + ' .fl-col-drop-target, ' +
  517. FLBuilder._contentClass + ' .fl-col-content, ' +
  518. FLBuilder._contentClass + ' .fl-module[data-accepts]:not(:has(> .fl-module-content)), ' +
  519. FLBuilder._contentClass + ' .fl-module[data-accepts] > .fl-module-content';
  520. }
  521. else if ( 'module' == FLBuilderConfig.userTemplateType ) {
  522. moduleConnections = FLBuilder._contentClass + ' .fl-module[data-accepts]:not(:has(> .fl-module-content)), ' +
  523. FLBuilder._contentClass + ' .fl-module[data-accepts] > .fl-module-content';
  524. }
  525. else {
  526. moduleConnections = FLBuilder._contentClass + ' .fl-row-drop-target, ' +
  527. FLBuilder._contentClass + ' .fl-row:not(.fl-builder-node-loading) .fl-col-group-drop-target, ' +
  528. FLBuilder._contentClass + ' .fl-row:not(.fl-builder-node-loading) .fl-col-drop-target, ' +
  529. FLBuilder._contentClass + ' .fl-row:not(.fl-builder-node-loading) .fl-col:not(.fl-builder-node-loading):not(.fl-node-global) .fl-col-content, ' +
  530. FLBuilder._contentClass + ' .fl-row:not(.fl-builder-node-loading) .fl-module[data-accepts]:not(:has(> .fl-module-content)):not(.fl-node-global), ' +
  531. FLBuilder._contentClass + ' .fl-row:not(.fl-builder-node-loading) .fl-module[data-accepts]:not(.fl-node-global) > .fl-module-content';
  532. }
  533. // Column Connections.
  534. if ( 'row' == FLBuilderConfig.userTemplateType ) {
  535. columnConnections = FLBuilder._contentClass + ' .fl-row:not(.fl-builder-node-loading) .fl-col-group-drop-target, ' +
  536. FLBuilder._contentClass + ' .fl-row:not(.fl-builder-node-loading) .fl-col-drop-target';
  537. }
  538. else {
  539. columnConnections = FLBuilder._contentClass + ' .fl-row-drop-target, ' +
  540. FLBuilder._contentClass + ' .fl-row:not(.fl-builder-node-loading) .fl-col-group-drop-target, ' +
  541. FLBuilder._contentClass + ' .fl-row:not(.fl-builder-node-loading) .fl-col-drop-target';
  542. }
  543. // Row Connections.
  544. if ( FLBuilderConfig.nestedColumns ) {
  545. rowConnections = moduleConnections;
  546. }
  547. else if ( 'row' == FLBuilderConfig.userTemplateType ) {
  548. rowConnections = FLBuilder._contentClass + ' .fl-row:not(.fl-builder-node-loading) .fl-col-group-drop-target, ' +
  549. FLBuilder._contentClass + ' .fl-row:not(.fl-builder-node-loading) .fl-col-drop-target';
  550. }
  551. else {
  552. rowConnections = FLBuilder._contentClass + ' .fl-row-drop-target, ' +
  553. FLBuilder._contentClass + ' .fl-row:not(.fl-builder-node-loading) .fl-col-group-drop-target, ' +
  554. FLBuilder._contentClass + ' .fl-row:not(.fl-builder-node-loading) .fl-col-drop-target';
  555. }
  556. // Row layouts from the builder panel.
  557. $('.fl-builder-rows', window.parent.document).sortable($.extend({}, defaults, {
  558. connectWith: rowConnections,
  559. items: '.fl-builder-block-row',
  560. stop: FLBuilder._rowDragStop
  561. }));
  562. // Row templates from the builder panel.
  563. $('.fl-builder-row-templates', window.parent.document).sortable($.extend({}, defaults, {
  564. connectWith: FLBuilder._contentClass + ' .fl-row-drop-target',
  565. items: '.fl-builder-block-row-template:not(.fl-builder-block-disabled)',
  566. stop: FLBuilder._nodeTemplateDragStop
  567. }));
  568. // Saved rows from the builder panel.
  569. $('.fl-builder-saved-rows', window.parent.document).sortable($.extend({}, defaults, {
  570. cancel: '.fl-builder-node-template-actions, .fl-builder-node-template-edit, .fl-builder-node-template-delete',
  571. connectWith: FLBuilder._contentClass + ' .fl-row-drop-target',
  572. items: '.fl-builder-block-saved-row',
  573. stop: FLBuilder._nodeTemplateDragStop
  574. }));
  575. // Saved columns from the builder panel.
  576. $('.fl-builder-saved-columns', window.parent.document).sortable($.extend({}, defaults, {
  577. cancel: '.fl-builder-node-template-actions, .fl-builder-node-template-edit, .fl-builder-node-template-delete',
  578. connectWith: columnConnections,
  579. items: '.fl-builder-block-saved-column',
  580. stop: FLBuilder._nodeTemplateDragStop
  581. }));
  582. // Modules from the builder panel.
  583. $('.fl-builder-modules, .fl-builder-widgets', window.parent.document).sortable($.extend({}, defaults, {
  584. connectWith: moduleConnections,
  585. items: '.fl-builder-block-module:not(.fl-builder-block-disabled)',
  586. stop: FLBuilder._moduleDragStop
  587. }));
  588. // Module templates from the builder panel.
  589. $('.fl-builder-module-templates', window.parent.document).sortable($.extend({}, defaults, {
  590. connectWith: moduleConnections,
  591. items: '.fl-builder-block-module-template',
  592. stop: FLBuilder._nodeTemplateDragStop
  593. }));
  594. // Saved modules from the builder panel.
  595. $('.fl-builder-saved-modules', window.parent.document).sortable($.extend({}, defaults, {
  596. cancel: '.fl-builder-node-template-actions, .fl-builder-node-template-edit, .fl-builder-node-template-delete',
  597. connectWith: moduleConnections,
  598. items: '.fl-builder-block-saved-module',
  599. stop: FLBuilder._nodeTemplateDragStop
  600. }));
  601. // Rows
  602. $('.fl-row-sortable-proxy', window.parent.document).sortable($.extend({}, defaults, {
  603. connectWith: FLBuilder._contentClass + ' .fl-row-drop-target',
  604. helper: FLBuilder._rowDragHelper,
  605. start: FLBuilder._rowDragStart,
  606. stop: FLBuilder._rowDragStop
  607. }));
  608. // Columns
  609. $('.fl-col-sortable-proxy', window.parent.document).sortable($.extend({}, defaults, {
  610. connectWith: moduleConnections,
  611. helper: FLBuilder._colDragHelper,
  612. start: FLBuilder._colDragStart,
  613. stop: FLBuilder._colDragStop
  614. }));
  615. // Modules
  616. $('.fl-module-sortable-proxy', window.parent.document).sortable($.extend({}, defaults, {
  617. connectWith: moduleConnections,
  618. helper: FLBuilder._moduleDragHelper,
  619. start: FLBuilder._moduleDragStart,
  620. stop: FLBuilder._moduleDragStop
  621. }));
  622. // Modules and groups in columns.
  623. $(FLBuilder._contentClass + ' .fl-col-content').sortable($.extend({}, defaults, {
  624. cancel: '.fl-module, .fl-col-group',
  625. handle: '.fl-module-sortable-proxy',
  626. }));
  627. // Modules and groups in container modules WITHOUT a wrapper.
  628. $(FLBuilder._contentClass + ' .fl-module[data-accepts]:not(:has(> .fl-module-content))').sortable($.extend({}, defaults, {
  629. cancel: '.fl-module, .fl-col-group',
  630. handle: '.fl-module-sortable-proxy',
  631. }));
  632. // Modules and groups in container modules WITH a wrapper.
  633. $(FLBuilder._contentClass + ' .fl-module[data-accepts] > .fl-module-content').sortable($.extend({}, defaults, {
  634. cancel: '.fl-module, .fl-col-group',
  635. handle: '.fl-module-sortable-proxy',
  636. }));
  637. // Drop targets
  638. $(FLBuilder._contentClass + ' .fl-row-drop-target').sortable( defaults );
  639. $(FLBuilder._contentClass + ' .fl-col-group-drop-target').sortable( defaults );
  640. $(FLBuilder._contentClass + ' .fl-col-drop-target').sortable( defaults );
  641. },
  642. /**
  643. * Refreshes the items for all jQuery sortables so any
  644. * new items will be recognized.
  645. *
  646. * @since 2.2
  647. * @access private
  648. * @method _refreshSortables
  649. */
  650. _refreshSortables: function()
  651. {
  652. var sortables = $( '.ui-sortable' ).add( '.ui-sortable', window.parent.document );
  653. sortables.sortable( 'refresh' );
  654. sortables.sortable( 'refreshPositions' );
  655. },
  656. /**
  657. * Initializes text translation
  658. *
  659. * @since 1.0
  660. * @access private
  661. * @method _initStrings
  662. */
  663. _initStrings: function()
  664. {
  665. $.validator.messages.required = FLBuilderStrings.validateRequiredMessage;
  666. },
  667. /**
  668. * Binds most of the events for the builder interface.
  669. *
  670. * @since 1.0
  671. * @access private
  672. * @method _bindEvents
  673. */
  674. _bindEvents: function()
  675. {
  676. /* Links */
  677. $excludedLinks = $('.fl-builder-bar a, .fl-builder--content-library-panel a, .fl-page-nav .nav a'); // links in ui shouldn't be disabled.
  678. $('a').not($excludedLinks).on('click', FLBuilder._preventDefault);
  679. $('.fl-page-nav .nav a').on('click', FLBuilder._headerLinkClicked);
  680. $('body').on( 'click', '.fl-builder-content a', FLBuilder._preventDefault);
  681. $('body').on( 'mouseup', 'button.fl-builder-button', this._buttonMouseUp.bind(this) );
  682. /* Heartbeat */
  683. $(document).on('heartbeat-tick', FLBuilder._initPostLock);
  684. /* Unload Warning */
  685. $(window.parent).on('beforeunload', FLBuilder._warnBeforeUnload);
  686. /* Lite Version */
  687. $('body', window.parent.document).on( 'click', '.fl-builder-blocks-pro-expand', FLBuilder._toggleProModules);
  688. $('body', window.parent.document).on( 'click', '.fl-builder-upgrade-button', FLBuilder._upgradeClicked);
  689. /* Panel */
  690. $('.fl-builder-panel-actions .fl-builder-panel-close', window.parent.document).on('click', FLBuilder._closePanel);
  691. $('body', window.parent.document).on( 'mousedown', '.fl-builder-node-template-actions', FLBuilder._stopPropagation);
  692. $('body', window.parent.document).on( 'mousedown', '.fl-builder-node-template-edit', FLBuilder._stopPropagation);
  693. $('body', window.parent.document).on( 'mousedown', '.fl-builder-node-template-delete', FLBuilder._stopPropagation);
  694. $('body', window.parent.document).on( 'click', '.fl-builder-node-template-edit', FLBuilder._editNodeTemplateClicked);
  695. $('body', window.parent.document).on( 'click', '.fl-builder-node-template-delete', FLBuilder._deleteNodeTemplateClicked);
  696. $('body', window.parent.document).on( 'mousedown', '.fl-builder-block:not(.fl-builder-block-disabled)', FLBuilder._blockDragInit );
  697. $('body', window.parent.document).on('mouseup', FLBuilder._blockDragCancel);
  698. /* Actions Lightbox */
  699. $('body', window.parent.document).on( 'click', '.fl-builder-actions .fl-builder-cancel-button', FLBuilder._cancelButtonClicked);
  700. /* Layout and Global Settings */
  701. $('body', window.parent.document).on( 'click', '.fl-builder-layout-settings .fl-builder-settings-save', FLBuilder._saveLayoutSettingsClicked);
  702. $('body', window.parent.document).on( 'click', '.fl-builder-layout-settings .fl-builder-settings-cancel', FLBuilder._cancelLayoutSettingsClicked);
  703. $('body', window.parent.document).on( 'click', '.fl-builder-global-settings .fl-builder-settings-save', FLBuilder._saveGlobalSettingsClicked);
  704. $('body', window.parent.document).on( 'click', '.fl-builder-global-settings .fl-builder-settings-cancel', FLBuilder._cancelLayoutSettingsClicked);
  705. /* Template Panel Tab */
  706. $('body', window.parent.document).on( 'click', '.fl-user-template', FLBuilder._userTemplateClicked);
  707. $('body', window.parent.document).on( 'click', '.fl-user-template-edit', FLBuilder._editUserTemplateClicked);
  708. $('body', window.parent.document).on( 'click', '.fl-user-template-delete', FLBuilder._deleteUserTemplateClicked);
  709. $('body', window.parent.document).on( 'click', '.fl-builder-template-replace-button', FLBuilder._templateReplaceClicked);
  710. $('body', window.parent.document).on( 'click', '.fl-builder-template-append-button', FLBuilder._templateAppendClicked);
  711. $('body', window.parent.document).on( 'click', '.fl-builder-template-actions .fl-builder-cancel-button', FLBuilder._templateCancelClicked);
  712. /* User Template Settings */
  713. $('body', window.parent.document).on( 'click', '.fl-builder-user-template-settings .fl-builder-settings-save', FLBuilder._saveUserTemplateSettings);
  714. /* Welcome Actions */
  715. $('body', window.parent.document).on( 'click', '.fl-builder-no-tour-button', FLBuilder._noTourButtonClicked);
  716. $('body', window.parent.document).on( 'click', '.fl-builder-yes-tour-button', FLBuilder._yesTourButtonClicked);
  717. /* Alert Lightbox */
  718. $('body', window.parent.document).on( 'click', '.fl-builder-alert-close', FLBuilder._alertClose);
  719. /* General Overlays */
  720. FLBuilder._bindGeneralOverlayEvents();
  721. /* Node Templates */
  722. $('body', window.parent.document).on( 'click', '.fl-builder-settings-save-as', FLBuilder._showNodeTemplateSettings);
  723. $('body', window.parent.document).on( 'click', '.fl-builder-node-template-settings .fl-builder-settings-save', FLBuilder._saveNodeTemplate);
  724. /* Settings */
  725. $('body', window.parent.document).on( 'click', '.fl-builder-settings-tabs a', FLBuilder._settingsTabClicked);
  726. $('body', window.parent.document).on( 'show', '.fl-builder-settings-tabs a', FLBuilder._calculateSettingsTabsOverflow);
  727. $('body', window.parent.document).on( 'hide', '.fl-builder-settings-tabs a', FLBuilder._calculateSettingsTabsOverflow);
  728. $('body', window.parent.document).on( 'click', '.fl-builder-settings-cancel', FLBuilder._settingsCancelClicked);
  729. /* Settings Tabs Overflow menu */
  730. $('body', window.parent.document).on( 'click', '.fl-builder-settings-tabs-overflow-menu > a', FLBuilder._settingsTabsToOverflowMenuItemClicked.bind(this));
  731. $('body', window.parent.document).on( 'click', '.fl-builder-settings-tabs-more', FLBuilder._toggleTabsOverflowMenu.bind(this) );
  732. $('body', window.parent.document).on( 'click', '.fl-builder-settings-tabs-overflow-click-mask', FLBuilder._hideTabsOverflowMenu.bind(this));
  733. /* Tooltips */
  734. $('body', window.parent.document).on( 'mouseover', '.fl-help-tooltip-icon', FLBuilder._showHelpTooltip);
  735. $('body', window.parent.document).on( 'mouseout', '.fl-help-tooltip-icon', FLBuilder._hideHelpTooltip);
  736. /* Multiple Fields */
  737. $('body', window.parent.document).on( 'click', '.fl-builder-field-add', FLBuilder._addFieldClicked);
  738. $('body', window.parent.document).on( 'click', '.fl-builder-field-copy', FLBuilder._copyFieldClicked);
  739. $('body', window.parent.document).on( 'click', '.fl-builder-field-delete', FLBuilder._deleteFieldClicked);
  740. /* Photo Fields */
  741. $('body', window.parent.document).on( 'click', '.fl-photo-field .fl-photo-select', FLBuilder._selectSinglePhoto);
  742. $('body', window.parent.document).on( 'click', '.fl-photo-field .fl-photo-edit', FLBuilder._selectSinglePhoto);
  743. $('body', window.parent.document).on( 'click', '.fl-photo-field .fl-photo-replace', FLBuilder._selectSinglePhoto);
  744. $('body', window.parent.document).on( 'click', '.fl-photo-field .fl-photo-remove', FLBuilder._singlePhotoRemoved);
  745. /* Multiple Photo Fields */
  746. $('body', window.parent.document).on( 'click', '.fl-multiple-photos-field .fl-multiple-photos-select', FLBuilder._selectMultiplePhotos);
  747. $('body', window.parent.document).on( 'click', '.fl-multiple-photos-field .fl-multiple-photos-edit', FLBuilder._selectMultiplePhotos);
  748. $('body', window.parent.document).on( 'click', '.fl-multiple-photos-field .fl-multiple-photos-add', FLBuilder._selectMultiplePhotos);
  749. /* Video Fields */
  750. $('body', window.parent.document).on( 'click', '.fl-video-field .fl-video-select', FLBuilder._selectSingleVideo);
  751. $('body', window.parent.document).on( 'click', '.fl-video-field .fl-video-replace', FLBuilder._selectSingleVideo);
  752. $('body', window.parent.document).on( 'click', '.fl-video-field .fl-video-remove', FLBuilder._singleVideoRemoved);
  753. /* Multiple Audio Fields */
  754. $('body', window.parent.document).on( 'click', '.fl-multiple-audios-field .fl-multiple-audios-select', FLBuilder._selectMultipleAudios);
  755. $('body', window.parent.document).on( 'click', '.fl-multiple-audios-field .fl-multiple-audios-edit', FLBuilder._selectMultipleAudios);
  756. $('body', window.parent.document).on( 'click', '.fl-multiple-audios-field .fl-multiple-audios-add', FLBuilder._selectMultipleAudios);
  757. /* Icon Fields */
  758. $('body', window.parent.document).on( 'click', '.fl-icon-field .fl-icon-select', FLBuilder._selectIcon);
  759. $('body', window.parent.document).on( 'click', '.fl-icon-field .fl-icon-replace', FLBuilder._selectIcon);
  760. $('body', window.parent.document).on( 'click', '.fl-icon-field .fl-icon-remove', FLBuilder._removeIcon);
  761. /* Settings Form Fields */
  762. $('body', window.parent.document).on( 'click', '.fl-form-field .fl-form-field-edit', FLBuilder._formFieldClicked);
  763. $('body', window.parent.document).on( 'click', '.fl-form-field-settings .fl-builder-settings-save', FLBuilder._saveFormFieldClicked);
  764. /* Layout Fields */
  765. $('body', window.parent.document).on( 'click', '.fl-layout-field-option', FLBuilder._layoutFieldClicked);
  766. /* Links Fields */
  767. $('body', window.parent.document).on( 'click', '.fl-link-field-select', FLBuilder._linkFieldSelectClicked);
  768. $('body', window.parent.document).on( 'click', '.fl-link-field-search-cancel', FLBuilder._linkFieldSelectCancelClicked);
  769. /* Loop Settings Fields */
  770. $('body', window.parent.document).on( 'change', '.fl-loop-data-source-select select[name=data_source]', FLBuilder._loopDataSourceChange);
  771. $('body', window.parent.document).on( 'change', '.fl-custom-query select[name=post_type]', FLBuilder._customQueryPostTypeChange);
  772. $('body', window.parent.document).on( 'change', '.fl-custom-query select[name="post_type[]"]', FLBuilder._customQueryPostTypesChange);
  773. /* Text Fields - Add Predefined Value Selector */
  774. $('body', window.parent.document).on( 'change', '.fl-text-field-add-value', FLBuilder._textFieldAddValueSelectChange);
  775. /* Number Fields */
  776. $('body', window.parent.document).on( 'focus', '.fl-field input[type=number]', FLBuilder._onNumberFieldFocus );
  777. $('body', window.parent.document).on( 'blur', '.fl-field input[type=number]', FLBuilder._onNumberFieldBlur );
  778. // Live Preview
  779. FLBuilder.addHook( 'didCompleteAJAX', FLBuilder._refreshSettingsPreviewReference );
  780. FLBuilder.addHook( 'didRenderLayoutComplete', FLBuilder._refreshSettingsPreviewReference );
  781. },
  782. /**
  783. * Remove events when ending the edit session
  784. * @since 2.0
  785. * @access private
  786. */
  787. _unbindEvents: function() {
  788. $('a').off('click', FLBuilder._preventDefault);
  789. $('.fl-page-nav .nav a').off('click', FLBuilder._headerLinkClicked);
  790. $('body').undelegate('.fl-builder-content a', 'click', FLBuilder._preventDefault);
  791. },
  792. /**
  793. * Rebind events when restarting the edit session
  794. * @since 2.1.2.3
  795. * @access private
  796. */
  797. _rebindEvents: function() {
  798. $('a').on('click', FLBuilder._preventDefault);
  799. $('.fl-page-nav .nav a').on('click', FLBuilder._headerLinkClicked);
  800. $('body').on( 'click', '.fl-builder-content a', FLBuilder._preventDefault);
  801. },
  802. /**
  803. * Prevents the default action for an event.
  804. *
  805. * @since 1.6.3
  806. * @access private
  807. * @method _preventDefault
  808. * @param {Object} e The event object.
  809. */
  810. _preventDefault: function( e )
  811. {
  812. e.preventDefault();
  813. },
  814. /**
  815. * Prevents propagation of an event.
  816. *
  817. * @since 1.6.3
  818. * @access private
  819. * @method _stopPropagation
  820. * @param {Object} e The event object.
  821. */
  822. _stopPropagation: function( e )
  823. {
  824. e.stopPropagation();
  825. },
  826. /**
  827. * Launches the builder for another page if a link in the
  828. * builder theme header is clicked.
  829. *
  830. * @since 1.3.9
  831. * @access private
  832. * @method _headerLinkClicked
  833. * @param {Object} e The event object.
  834. */
  835. _headerLinkClicked: function(e)
  836. {
  837. var link = $(this),
  838. href = link.attr('href');
  839. // ignore links with a #hash
  840. if( this.hash ) {
  841. return;
  842. }
  843. e.preventDefault();
  844. if ( FLBuilderConfig.isUserTemplate ) {
  845. return;
  846. }
  847. FLBuilder._exitUrl = href.indexOf('?') > -1 ? href : href + '?fl_builder';
  848. FLBuilder.triggerHook('triggerDone');
  849. },
  850. /**
  851. * Warns the user that their changes won't be saved if
  852. * they leave the page while editing settings.
  853. *
  854. * @since 1.0.6
  855. * @access private
  856. * @method _warnBeforeUnload
  857. * @return {String} The warning message.
  858. */
  859. _warnBeforeUnload: function()
  860. {
  861. var rowSettings = $('.fl-builder-row-settings', window.parent.document).length > 0,
  862. colSettings = $('.fl-builder-col-settings', window.parent.document).length > 0,
  863. moduleSettings = $('.fl-builder-module-settings', window.parent.document).length > 0;
  864. if(rowSettings || colSettings || moduleSettings) {
  865. return FLBuilderStrings.unloadWarning;
  866. }
  867. },
  868. /* Lite Version
  869. ----------------------------------------------------------*/
  870. /**
  871. * Opens a new window with the upgrade URL when the
  872. * upgrade button is clicked.
  873. *
  874. * @since 1.0
  875. * @access private
  876. * @method _upgradeClicked
  877. */
  878. _upgradeClicked: function()
  879. {
  880. window.parent.open(FLBuilderConfig.upgradeUrl);
  881. },
  882. /**
  883. * Toggles the pro module section in lite.
  884. *
  885. * @since 2.4
  886. */
  887. _toggleProModules: function()
  888. {
  889. var button = $( '.fl-builder-blocks-pro-expand', window.parent.document ),
  890. closed = $( '.fl-builder-blocks-pro-closed', window.parent.document ),
  891. open = $( '.fl-builder-blocks-pro-open', window.parent.document );
  892. button.toggleClass( 'fl-builder-blocks-pro-expand-rotate' );
  893. if ( closed.length ) {
  894. closed.removeClass( 'fl-builder-blocks-pro-closed' );
  895. closed.addClass( 'fl-builder-blocks-pro-open' );
  896. } else {
  897. open.removeClass( 'fl-builder-blocks-pro-open' );
  898. open.addClass( 'fl-builder-blocks-pro-closed' );
  899. }
  900. },
  901. /**
  902. * Shows the the pro message lightbox.
  903. *
  904. * @since 2.4
  905. */
  906. _showProMessage: function( feature )
  907. {
  908. if ( ! FLBuilderConfig.lite ) {
  909. return
  910. }
  911. var alert = new FLLightbox({
  912. className: 'fl-builder-pro-lightbox',
  913. destroyOnClose: true
  914. }),
  915. template = wp.template( 'fl-pro-lightbox' );
  916. alert.open( template( { feature : feature } ) );
  917. },
  918. /* TipTips
  919. ----------------------------------------------------------*/
  920. /**
  921. * Initializes tooltip help messages.
  922. *
  923. * @since 1.1.9
  924. * @access private
  925. * @method _initTipTips
  926. */
  927. _initTipTips: function()
  928. {
  929. var classname = '.fl-tip:not(.fl-has-tip)',
  930. tips = $( classname ).add( classname, window.parent.document );
  931. tips.each( function(){
  932. var ele = $( this );
  933. ele.addClass( 'fl-has-tip' );
  934. if ( undefined == ele.attr( 'data-title' ) ) {
  935. ele.attr( 'data-title', ele.attr( 'title' ) );
  936. }
  937. } )
  938. if ( ! FLBuilderLayout._isTouch() ) {
  939. tips.tipTip( {
  940. defaultPosition : 'top',
  941. delay : 300,
  942. maxWidth : 'auto'
  943. } );
  944. }
  945. },
  946. /**
  947. * Removes all tooltip help messages from the screen.
  948. *
  949. * @since 1.1.9
  950. * @access private
  951. * @method _hideTipTips
  952. */
  953. _hideTipTips: function()
  954. {
  955. $('#tiptip_holder').stop().hide();
  956. $('#tiptip_holder', window.parent.document).stop().hide();
  957. },
  958. /* Submenus
  959. ----------------------------------------------------------*/
  960. /**
  961. * Callback for when the parent of a submenu is clicked.
  962. *
  963. * @since 1.6.4
  964. * @access private
  965. * @method _submenuParentClicked
  966. * @param {Object} e The event object.
  967. */
  968. _submenuParentClicked: function( e )
  969. {
  970. var body = $( 'body' ),
  971. parent = $( this ),
  972. submenu = parent.find( '.fl-builder-submenu' );
  973. if ( parent.hasClass( 'fl-builder-submenu-open' ) ) {
  974. body.removeClass( 'fl-builder-submenu-open' );
  975. parent.removeClass( 'fl-builder-submenu-open' );
  976. parent.removeClass( 'fl-builder-submenu-right' );
  977. } else {
  978. if( parent.offset().left + submenu.width() > $( window ).width() ) {
  979. parent.addClass( 'fl-builder-submenu-right' );
  980. }
  981. body.addClass( 'fl-builder-submenu-open' );
  982. parent.addClass( 'fl-builder-submenu-open' );
  983. }
  984. submenu.closest('.fl-row-overlay').addClass('fl-row-menu-active');
  985. FLBuilder._hideTipTips();
  986. e.preventDefault();
  987. e.stopPropagation();
  988. },
  989. /**
  990. * Callback for when the parent of a submenu is hovered.
  991. *
  992. * @since 2.6
  993. * @access private
  994. * @method _hoverMenuParentMouseEnter
  995. * @param {Object} e The event object.
  996. */
  997. _hoverMenuParentMouseEnter: function (e) {
  998. e.stopPropagation();
  999. var body = $('body'),
  1000. parent = $(this),
  1001. submenu = parent.find('.fl-builder-submenu');
  1002. // remove classes first
  1003. $('.fl-builder-submenu-right').removeClass('fl-builder-submenu-right');
  1004. $('.fl-builder-submenu-open').removeClass('fl-builder-submenu-open');
  1005. $('.fl-row-menu-active').removeClass('fl-row-menu-active');
  1006. // determine align
  1007. if (parent.offset().left + submenu.width() > $(window).width()) {
  1008. parent.addClass('fl-builder-submenu-right');
  1009. }
  1010. // add classes
  1011. parent.closest('.fl-row-overlay').addClass('fl-row-menu-active');
  1012. body.addClass('fl-builder-submenu-open');
  1013. parent.addClass('fl-builder-submenu-open');
  1014. },
  1015. /**
  1016. * Callback for when the parent of a submenu is unhovered.
  1017. *
  1018. * @since 2.6
  1019. * @access private
  1020. * @method _hoverMenuParentMouseLeave
  1021. * @param {Object} e The event object.
  1022. */
  1023. _hoverMenuParentMouseLeave: function (e) {
  1024. // remove classes
  1025. $('.fl-builder-submenu-right').removeClass('fl-builder-submenu-right');
  1026. $('.fl-builder-submenu-open').removeClass('fl-builder-submenu-open');
  1027. $('.fl-row-menu-active').removeClass('fl-row-menu-active');
  1028. },
  1029. /**
  1030. * Callback for when the child of a submenu is clicked.
  1031. *
  1032. * @since 1.6.4
  1033. * @access private
  1034. * @method _submenuChildClicked
  1035. * @param {Object} e The event object.
  1036. */
  1037. _submenuChildClicked: function( e )
  1038. {
  1039. var body = $( 'body' ),
  1040. parent = $( this ).parents( '.fl-builder-has-submenu' );
  1041. if ( ! parent.parents( '.fl-builder-has-submenu' ).length ) {
  1042. body.removeClass( 'fl-builder-submenu-open' );
  1043. parent.removeClass( 'fl-builder-submenu-open' );
  1044. }
  1045. },
  1046. /**
  1047. * Callback for when the mouse enters a submenu.
  1048. *
  1049. * @since 1.6.4
  1050. * @access private
  1051. * @method _submenuMouseenter
  1052. * @param {Object} e The event object.
  1053. */
  1054. _submenuMouseenter: function( e )
  1055. {
  1056. if ($(this).parent().hasClass('fl-builder-submenu-hover')) {
  1057. return;
  1058. }
  1059. var menu = $( this ),
  1060. timeout = menu.data( 'timeout' );
  1061. if ( 'undefined' != typeof timeout ) {
  1062. clearTimeout( timeout );
  1063. }
  1064. },
  1065. /**
  1066. * Callback for when the mouse leaves a submenu.
  1067. *
  1068. * @since 1.6.4
  1069. * @access private
  1070. * @method _submenuMouseleave
  1071. * @param {Object} e The event object.
  1072. */
  1073. _submenuMouseleave: function( e )
  1074. {
  1075. if ($(this).parent().hasClass('fl-builder-submenu-hover')) {
  1076. return;
  1077. }
  1078. var body = $( 'body' ),
  1079. menu = $( this ),
  1080. timeout = setTimeout( function() {
  1081. body.removeClass( 'fl-builder-submenu-open' );
  1082. menu.closest( '.fl-builder-has-submenu' ).removeClass( 'fl-builder-submenu-open' );
  1083. }, 500 );
  1084. menu.closest('.fl-row-overlay').removeClass('fl-row-menu-active');
  1085. menu.data( 'timeout', timeout );
  1086. },
  1087. /**
  1088. * Callback for when the mouse enters the parent
  1089. * of a nested submenu.
  1090. *
  1091. * @since 1.9
  1092. * @access private
  1093. * @method _submenuNestedParentMouseenter
  1094. * @param {Object} e The event object.
  1095. */
  1096. _submenuNestedParentMouseenter: function( e )
  1097. {
  1098. var parent = $( this );
  1099. var submenu = parent.find( '> .fl-builder-submenu' );
  1100. var winWidth = $( window ).width();
  1101. var leftSpace = parent.offset().left;
  1102. var rightSpace = winWidth - ( parent.width() + leftSpace );
  1103. var menusWidth = parent.width() + leftSpace + submenu.width();
  1104. if ( menusWidth > winWidth && submenu.width() < leftSpace ) {
  1105. parent.addClass( 'fl-builder-submenu-right' );
  1106. } else if ( submenu.width() > rightSpace ) {
  1107. var left = parent.width() - ( submenu.width() - rightSpace );
  1108. submenu.css( 'left', left );
  1109. }
  1110. },
  1111. /**
  1112. * Closes all open submenus.
  1113. *
  1114. * @since 1.9
  1115. * @access private
  1116. * @method _closeAllSubmenus
  1117. */
  1118. _closeAllSubmenus: function()
  1119. {
  1120. $( '.fl-builder-submenu-open' ).removeClass( 'fl-builder-submenu-open' );
  1121. },
  1122. /* Bar
  1123. ----------------------------------------------------------*/
  1124. /**
  1125. * Fires blur on mouse up to avoid focus ring when clicked with mouse.
  1126. *
  1127. * @since 2.0
  1128. * @access private
  1129. * @method _buttonMouseUp
  1130. * @param {Event} e
  1131. * @return void
  1132. */
  1133. _buttonMouseUp: function(e) {
  1134. $(e.currentTarget).blur();
  1135. },
  1136. /* Panel
  1137. ----------------------------------------------------------*/
  1138. /**
  1139. * Closes the builder's content panel.
  1140. *
  1141. * @since 1.0
  1142. * @access private
  1143. * @method _closePanel
  1144. */
  1145. _closePanel: function()
  1146. {
  1147. FLBuilder.triggerHook('hideContentPanel');
  1148. },
  1149. /**
  1150. * Opens the builder's content panel.
  1151. *
  1152. * @since 1.0
  1153. * @access private
  1154. * @method _showPanel
  1155. */
  1156. _showPanel: function()
  1157. {
  1158. FLBuilder.triggerHook('showContentPanel');
  1159. },
  1160. /**
  1161. * Toggle the panel open or closed.
  1162. *
  1163. * @since 2.0
  1164. * @access private
  1165. * @method _togglePanel
  1166. */
  1167. _togglePanel: function()
  1168. {
  1169. FLBuilder.triggerHook('toggleContentPanel');
  1170. },
  1171. /* Save Actions
  1172. ----------------------------------------------------------*/
  1173. /**
  1174. * Publish the current layout
  1175. *
  1176. * @since 2.0
  1177. * @access private
  1178. * @method _publishLayout
  1179. * @param {Boolean} shouldExit Whether or not builder should exit after publish
  1180. * @param {Boolean} openLightbox Whether or not to keep the lightboxes open.
  1181. * @return void
  1182. */
  1183. _publishLayout: function( shouldExit, openLightbox ) {
  1184. // Save existing settings first if any exist. Don't proceed if it fails.
  1185. if ( ! FLBuilder._triggerSettingsSave( openLightbox, true ) ) {
  1186. return;
  1187. }
  1188. if ( _.isUndefined( shouldExit ) ) {
  1189. var shouldExit = true;
  1190. }
  1191. const actions = FL.Builder.data.getLayoutActions()
  1192. const callback = FLBuilder._onPublishComplete.bind( FLBuilder, shouldExit )
  1193. actions.saveLayout( true, shouldExit, callback )
  1194. },
  1195. /**
  1196. * Publishes the layout when the publish button is clicked.
  1197. *
  1198. * @since 1.0
  1199. * @access private
  1200. * @param bool whether or not builder should exit after publish
  1201. * @method _publishButtonClicked
  1202. */
  1203. _publishButtonClicked: function( shouldExit )
  1204. {
  1205. FLBuilder._publishLayout( shouldExit );
  1206. },
  1207. /**
  1208. * Fires on successful ajax publish.
  1209. *
  1210. * @since 2.0
  1211. * @access private
  1212. * @param bool whether or not builder should exit after publish
  1213. * @return void
  1214. */
  1215. _onPublishComplete: function( shouldExit ) {
  1216. if ( shouldExit ) {
  1217. if ( FLBuilderConfig.shouldRefreshOnPublish ) {
  1218. FLBuilder._exit();
  1219. } else {
  1220. FLBuilder._exitWithoutRefresh();
  1221. }
  1222. }
  1223. // Change the admin bar status dot to green if it isn't already
  1224. $('#wp-admin-bar-fl-builder-frontend-edit-link .fl-builder-admin-bar-status-dot').css('color', '#6bc373');
  1225. FLBuilder.triggerHook( 'didPublishLayout', {
  1226. shouldExit: shouldExit,
  1227. } );
  1228. },
  1229. /**
  1230. * Exits the builder when the save draft button is clicked.
  1231. *
  1232. * @since 1.0
  1233. * @access private
  1234. * @method _draftButtonClicked
  1235. */
  1236. _draftButtonClicked: function()
  1237. {
  1238. FLBuilder.showAjaxLoader();
  1239. const actions = FL.Builder.data.getLayoutActions()
  1240. actions.saveDraft()
  1241. },
  1242. /**
  1243. * Clears changes to the layout when the discard draft button
  1244. * is clicked.
  1245. *
  1246. * @since 1.0
  1247. * @access private
  1248. * @method _discardButtonClicked
  1249. */
  1250. _discardButtonClicked: function()
  1251. {
  1252. var result = confirm(FLBuilderStrings.discardMessage);
  1253. if(result) {
  1254. FLBuilder.showAjaxLoader();
  1255. const actions = FL.Builder.data.getLayoutActions()
  1256. actions.discardDraft()
  1257. } else {
  1258. FLBuilder.triggerHook('didCancelDiscard');
  1259. }
  1260. },
  1261. /**
  1262. * Closes the actions lightbox when the cancel button is clicked.
  1263. *
  1264. * @since 1.0
  1265. * @access private
  1266. * @method _cancelButtonClicked
  1267. */
  1268. _cancelButtonClicked: function()
  1269. {
  1270. FLBuilder._exitUrl = null;
  1271. FLBuilder._actionsLightbox.close();
  1272. },
  1273. /**
  1274. * Redirects the user to the _exitUrl if defined, otherwise
  1275. * it redirects the user to the current post without the
  1276. * builder active.
  1277. *
  1278. * @since 1.0
  1279. * @since 1.5.7 Closes the window if we're in a child window.
  1280. * @access private
  1281. * @method _exit
  1282. */
  1283. _exit: function()
  1284. {
  1285. var href = window.parent.location.href;
  1286. try {
  1287. var flbuilder = typeof window.parent.opener.FLBuilder != 'undefined'
  1288. }
  1289. catch(err) {
  1290. var flbuilder = false
  1291. }
  1292. if ( FLBuilderConfig.isUserTemplate && typeof window.parent.opener != 'undefined' && window.parent.opener ) {
  1293. if ( flbuilder ) {
  1294. if ( 'undefined' === typeof FLBuilderGlobalNodeId ) {
  1295. window.parent.opener.FLBuilder._updateLayout();
  1296. } else {
  1297. window.parent.opener.FLBuilder._updateNode( FLBuilderGlobalNodeId );
  1298. }
  1299. }
  1300. window.parent.close();
  1301. }
  1302. else {
  1303. if ( FLBuilder._exitUrl ) {
  1304. href = FLBuilder._exitUrl;
  1305. }
  1306. else {
  1307. href = href.replace( '&fl_builder_ui', '' );
  1308. href = href.replace( '?fl_builder&', '?' );
  1309. href = href.replace( '?fl_builder', '' );
  1310. href = href.replace( '&fl_builder', '' );
  1311. }
  1312. if ( window.parent.location.host === 'playground.wordpress.net' ) {
  1313. const meta = '<meta http-equiv="refresh" content="0; URL=\'' + href + '\'" />';
  1314. $( 'head', window.parent.document ).append( meta );
  1315. } else {
  1316. window.parent.location.href = href;
  1317. }
  1318. }
  1319. },
  1320. /**
  1321. * Allow the editing session to end but don't redirect to any url.
  1322. *
  1323. * @since 2.0
  1324. * @return void
  1325. */
  1326. _exitWithoutRefresh: function() {
  1327. var href = window.parent.location.href;
  1328. try {
  1329. var flbuilder = typeof window.parent.opener.FLBuilder != 'undefined'
  1330. }
  1331. catch(err) {
  1332. var flbuilder = false
  1333. }
  1334. if ( FLBuilderConfig.isUserTemplate && flbuilder && window.opener ) {
  1335. if ( flbuilder ) {
  1336. if ( 'undefined' === typeof FLBuilderGlobalNodeId ) {
  1337. window.parent.opener.FLBuilder._updateLayout();
  1338. } else {
  1339. window.parent.opener.FLBuilder._updateNode( FLBuilderGlobalNodeId );
  1340. }
  1341. }
  1342. window.parent.close();
  1343. }
  1344. else {
  1345. FLBuilder.triggerHook('endEditingSession');
  1346. }
  1347. },
  1348. /* Tools Actions
  1349. ----------------------------------------------------------*/
  1350. /**
  1351. * Duplicates the current post and builder layout.
  1352. *
  1353. * @since 1.0
  1354. * @access private
  1355. * @method _duplicateLayoutClicked
  1356. */
  1357. _duplicateLayoutClicked: function()
  1358. {
  1359. FLBuilder.showAjaxLoader();
  1360. FLBuilder.ajax({
  1361. action: 'duplicate_post'
  1362. }, FLBuilder._duplicateLayoutComplete);
  1363. },
  1364. /**
  1365. * Redirects the user to the post edit screen of a
  1366. * duplicated post when duplication is complete.
  1367. *
  1368. * @since 1.0
  1369. * @access private
  1370. * @method _duplicatePageComplete
  1371. * @param {Number} The ID of the duplicated post.
  1372. */
  1373. _duplicateLayoutComplete: function(response)
  1374. {
  1375. var adminUrl = FLBuilderConfig.adminUrl;
  1376. window.parent.location.href = adminUrl + 'post.php?post='+ response +'&action=edit';
  1377. },
  1378. /* Layout Settings
  1379. ----------------------------------------------------------*/
  1380. /**
  1381. * Shows the layout settings lightbox when the layout
  1382. * settings button is clicked.
  1383. *
  1384. * @since 1.7
  1385. * @access private
  1386. * @method _layoutSettingsClicked
  1387. */
  1388. _layoutSettingsClicked: function()
  1389. {
  1390. FLBuilderSettingsForms.render( {
  1391. id : 'layout',
  1392. className : 'fl-builder-layout-settings',
  1393. settings : FLBuilderSettingsConfig.settings.layout
  1394. }, function() {
  1395. FLBuilder._layoutSettingsInitCSS();
  1396. } );
  1397. },
  1398. /**
  1399. * Initializes custom layout CSS for live preview.
  1400. *
  1401. * @since 1.7
  1402. * @access private
  1403. * @method _layoutSettingsInitCSS
  1404. */
  1405. _layoutSettingsInitCSS: function()
  1406. {
  1407. var css = $( '.fl-builder-settings #fl-field-css textarea:not(.ace_text-input)', window.parent.document );
  1408. css.on( 'change', FLBuilder._layoutSettingsCSSChanged );
  1409. FLBuilder._layoutSettingsCSSCache = css.val();
  1410. },
  1411. /**
  1412. * Sets a timeout for throttling custom layout CSS changes.
  1413. *
  1414. * @since 1.7
  1415. * @access private
  1416. * @method _layoutSettingsCSSChanged
  1417. */
  1418. _layoutSettingsCSSChanged: function()
  1419. {
  1420. if ( FLBuilder._layoutSettingsCSSTimeout ) {
  1421. clearTimeout( FLBuilder._layoutSettingsCSSTimeout );
  1422. }
  1423. FLBuilder._layoutSettingsCSSTimeout = setTimeout( $.proxy( FLBuilder._layoutSettingsCSSDoChange, this ), 600 );
  1424. },
  1425. /**
  1426. * Updates the custom layout CSS when changes are made in the editor.
  1427. *
  1428. * @since 1.7
  1429. * @access private
  1430. * @method _layoutSettingsCSSDoChange
  1431. */
  1432. _layoutSettingsCSSDoChange: function()
  1433. {
  1434. var form = $( '.fl-builder-settings', window.parent.document ),
  1435. textarea = $( this ),
  1436. field = textarea.parents( '#fl-field-css' );
  1437. if ( field.find( '.ace_error' ).length > 0 ) {
  1438. return;
  1439. }
  1440. else if ( form.hasClass( 'fl-builder-layout-settings' ) ) {
  1441. $( '#fl-builder-layout-css' ).html( textarea.val() );
  1442. }
  1443. else {
  1444. $( '#fl-builder-global-css' ).html( textarea.val() );
  1445. }
  1446. FLBuilder._layoutSettingsCSSTimeout = null;
  1447. },
  1448. /**
  1449. * Saves the layout settings when the save button is clicked.
  1450. *
  1451. * @since 1.7
  1452. * @access private
  1453. * @method _saveLayoutSettingsClicked
  1454. */
  1455. _saveLayoutSettingsClicked: function()
  1456. {
  1457. var form = $( this ).closest( '.fl-builder-settings' ),
  1458. data = form.serializeArray(),
  1459. settings = {},
  1460. i = 0;
  1461. for( ; i < data.length; i++) {
  1462. settings[ data[ i ].name ] = data[ i ].value;
  1463. }
  1464. FLBuilder.showAjaxLoader();
  1465. FLBuilder._lightbox.close();
  1466. FLBuilder._layoutSettingsCSSCache = null;
  1467. const actions = FL.Builder.data.getLayoutActions()
  1468. actions.saveLayoutSettings( settings )
  1469. },
  1470. /**
  1471. * Reverts changes made when the cancel button for the layout
  1472. * settings has been clicked.
  1473. *
  1474. * @since 1.7
  1475. * @access private
  1476. * @method _cancelLayoutSettingsClicked
  1477. */
  1478. _cancelLayoutSettingsClicked: function()
  1479. {
  1480. var form = $( '.fl-builder-settings', window.parent.document );
  1481. if ( form.hasClass( 'fl-builder-layout-settings' ) ) {
  1482. $( '#fl-builder-layout-css' ).html( FLBuilder._layoutSettingsCSSCache );
  1483. }
  1484. else {
  1485. $( '#fl-builder-global-css' ).html( FLBuilder._layoutSettingsCSSCache );
  1486. }
  1487. FLBuilder._layoutSettingsCSSCache = null;
  1488. },
  1489. /**
  1490. * Completes the ajax call for saving layout settings.
  1491. *
  1492. * @since 2.5
  1493. * @access private
  1494. * @method _saveLayoutSettingsComplete
  1495. * @param {Object} the settings object
  1496. */
  1497. _saveLayoutSettingsComplete: function( settings )
  1498. {
  1499. FLBuilder.triggerHook( 'didSaveLayoutSettingsComplete', settings )
  1500. FLBuilder._updateLayout()
  1501. },
  1502. /* Global Settings
  1503. ----------------------------------------------------------*/
  1504. /**
  1505. * Shows the global builder settings lightbox when the global
  1506. * settings button is clicked.
  1507. *
  1508. * @since 1.0
  1509. * @access private
  1510. * @method _globalSettingsClicked
  1511. */
  1512. _globalSettingsClicked: function()
  1513. {
  1514. const settings = FLBuilderSettingsConfig.settings.global
  1515. settings.color_scheme = FL.Builder.data.getSystemState().colorScheme
  1516. FLBuilderSettingsForms.render( {
  1517. id : 'global',
  1518. className : 'fl-builder-global-settings',
  1519. settings : settings
  1520. }, function() {
  1521. FLBuilder._layoutSettingsInitCSS();
  1522. FLBuilder.original_shapes = FLBuilderSettingsConfig.settings.global.shape_form;
  1523. } );
  1524. },
  1525. /**
  1526. * Saves the global settings when the save button is clicked.
  1527. *
  1528. * @since 1.0
  1529. * @access private
  1530. * @method _saveGlobalSettingsClicked
  1531. */
  1532. _saveGlobalSettingsClicked: function()
  1533. {
  1534. var form = $(this).closest('.fl-builder-settings'),
  1535. valid = form.validate().form(),
  1536. settings = FLBuilder._getSettings( form );
  1537. if(valid) {
  1538. FLBuilder.showAjaxLoader();
  1539. FLBuilder._layoutSettingsCSSCache = null;
  1540. const actions = FL.Builder.data.getLayoutActions()
  1541. actions.saveGlobalSettings( settings )
  1542. FLBuilder._lightbox.close();
  1543. // if number of global shapes has changed then refresh.
  1544. if ( 'undefined' != typeof FLBuilder.original_shapes && FLBuilder.original_shapes.length !== settings.shape_form?.length ) {
  1545. FLBuilder._shapesEdited = true;
  1546. }
  1547. }
  1548. },
  1549. /**
  1550. * Saves the global settings when the save button is clicked.
  1551. *
  1552. * @since 1.0
  1553. * @access private
  1554. * @method _saveGlobalSettingsComplete
  1555. * @param {String} response
  1556. */
  1557. _saveGlobalSettingsComplete: function( response )
  1558. {
  1559. FLBuilder.triggerHook( 'didSaveGlobalSettingsComplete', FLBuilder._jsonParse( response ) );
  1560. FLBuilder._updateLayout();
  1561. if ( FLBuilder._shapesEdited === true ) {
  1562. window.parent.location.reload(true);
  1563. }
  1564. },
  1565. /* Global Styles - See extensions for global styles code.
  1566. ----------------------------------------------------------*/
  1567. /**
  1568. * Shows the global builder styles lightbox when the global
  1569. * styles button is clicked.
  1570. *
  1571. * @since 2.8
  1572. */
  1573. _globalStylesClicked: function()
  1574. {
  1575. if ( FLBuilderConfig.lite ) {
  1576. FLBuilder._showProMessage( 'Global Styles' );
  1577. } else if ( 'undefined' !== typeof FLBuilderGlobalStyles ) {
  1578. FLBuilderGlobalStyles._showPanel();
  1579. }
  1580. },
  1581. /* Template Selector
  1582. ----------------------------------------------------------*/
  1583. /**
  1584. * Shows the template selector when the builder is launched
  1585. * if the current layout is empty.
  1586. *
  1587. * @since 1.0
  1588. * @access private
  1589. * @method _initTemplateSelector
  1590. */
  1591. _initTemplateSelector: function()
  1592. {
  1593. var rows = $(FLBuilder._contentClass).find('.fl-row'),
  1594. layoutHasContent = ( rows.length > 0 );
  1595. if( ! layoutHasContent ) {
  1596. FLBuilder.ContentPanel.show('modules');
  1597. }
  1598. },
  1599. /**
  1600. * Show options for inserting or appending a template when a template is selected.
  1601. * This logic was moved from `_templateClicked` to unbind it from the specific event.
  1602. *
  1603. * @since 2.0
  1604. * @access private
  1605. * @method _requestTemplateInsert
  1606. */
  1607. _requestTemplateInsert: function(index, type) {
  1608. // if there are existing rows in the layout
  1609. if( FLBuilder.layoutHasContent() ) {
  1610. // If the template is blank, no need to ask
  1611. if(index == 0) {
  1612. if( confirm( FLBuilderStrings.changeTemplateMessage ) ) {
  1613. FLBuilder._lightbox._node.hide();
  1614. FLBuilder._applyTemplate(0, false, type);
  1615. }
  1616. }
  1617. // present options Replace or Append
  1618. else {
  1619. FLBuilder._selectedTemplateId = index;
  1620. FLBuilder._selectedTemplateType = type;
  1621. FLBuilder._showTemplateActions();
  1622. FLBuilder._lightbox._node.hide();
  1623. }
  1624. }
  1625. // if there are no rows, just insert the template.
  1626. else {
  1627. FLBuilder._applyTemplate(index, false, type);
  1628. }
  1629. },
  1630. /**
  1631. * Shows the actions lightbox for replacing and appending templates.
  1632. *
  1633. * @since 1.1.9
  1634. * @access private
  1635. * @method _showTemplateActions
  1636. */
  1637. _showTemplateActions: function()
  1638. {
  1639. var buttons = [];
  1640. buttons[ 10 ] = {
  1641. 'key': 'template-replace',
  1642. 'label': FLBuilderStrings.templateReplace
  1643. };
  1644. buttons[ 20 ] = {
  1645. 'key': 'template-append',
  1646. 'label': FLBuilderStrings.templateAppend
  1647. };
  1648. FLBuilder._showActionsLightbox({
  1649. 'className': 'fl-builder-template-actions',
  1650. 'title': FLBuilderStrings.actionsLightboxTitle,
  1651. 'buttons': buttons
  1652. });
  1653. },
  1654. /**
  1655. * Replaces the current layout with a template when the replace
  1656. * button is clicked.
  1657. *
  1658. * @since 1.1.9
  1659. * @access private
  1660. * @method _templateReplaceClicked
  1661. */
  1662. _templateReplaceClicked: function()
  1663. {
  1664. if(confirm(FLBuilderStrings.changeTemplateMessage)) {
  1665. FLBuilder._actionsLightbox.close();
  1666. FLBuilder._applyTemplate(FLBuilder._selectedTemplateId, false, FLBuilder._selectedTemplateType);
  1667. }
  1668. },
  1669. /**
  1670. * Append a template to the current layout when the append
  1671. * button is clicked.
  1672. *
  1673. * @since 1.1.9
  1674. * @access private
  1675. * @method _templateAppendClicked
  1676. */
  1677. _templateAppendClicked: function()
  1678. {
  1679. FLBuilder._actionsLightbox.close();
  1680. FLBuilder._applyTemplate(FLBuilder._selectedTemplateId, true, FLBuilder._selectedTemplateType);
  1681. },
  1682. /**
  1683. * Shows the template selector when the cancel button of
  1684. * the template actions lightbox is clicked.
  1685. *
  1686. * @since 1.1.9
  1687. * @access private
  1688. * @method _templateCancelClicked
  1689. */
  1690. _templateCancelClicked: function()
  1691. {
  1692. FLBuilder.triggerHook( 'showContentPanel' );
  1693. },
  1694. /**
  1695. * Applys a template to the current layout by either appending
  1696. * it or replacing the current layout with it.
  1697. *
  1698. * @since 1.1.9
  1699. * @access private
  1700. * @method _applyTemplate
  1701. * @param {Number} id The template id.
  1702. * @param {Boolean} append Whether the new template should be appended or not.
  1703. * @param {String} type The type of template. Either core or user.
  1704. */
  1705. _applyTemplate: function( id, append, type )
  1706. {
  1707. append = typeof append === 'undefined' || ! append ? '0' : '1'
  1708. type = typeof type === 'undefined' ? 'core' : type
  1709. FLBuilder._lightbox.close();
  1710. FLBuilder.showAjaxLoader();
  1711. const actions = FL.Builder.data.getLayoutActions()
  1712. actions.applyTemplate( id, append, type )
  1713. FLBuilder.triggerHook('didApplyTemplate');
  1714. },
  1715. /**
  1716. * Callback for when applying a template completes.
  1717. * @since 2.0
  1718. * @access private
  1719. * @method _applyTemplateComplete
  1720. * @param {String} response
  1721. */
  1722. _applyTemplateComplete: function( response )
  1723. {
  1724. var data = FLBuilder._jsonParse( response );
  1725. FLBuilder._renderLayout( data.layout );
  1726. FLBuilder.triggerHook( 'didApplyTemplateComplete', data.config );
  1727. },
  1728. /**
  1729. * Callback for when applying a user template completes.
  1730. * @since 1.9.5
  1731. * @access private
  1732. * @method _applyUserTemplateComplete
  1733. * @param {string} response
  1734. */
  1735. _applyUserTemplateComplete: function( response )
  1736. {
  1737. var data = FLBuilder._jsonParse( response );
  1738. if ( null !== data.layout_css ) {
  1739. $( '#fl-builder-layout-css' ).html( data.layout_css );
  1740. }
  1741. FLBuilder._renderLayout( data.layout );
  1742. FLBuilder.triggerHook( 'didApplyTemplateComplete', data.config );
  1743. },
  1744. /* User Template Settings
  1745. ----------------------------------------------------------*/
  1746. /**
  1747. * Shows the settings for saving a user defined template
  1748. * when the save template button is clicked.
  1749. *
  1750. * @since 1.1.3
  1751. * @access private
  1752. * @method _saveUserTemplateClicked
  1753. */
  1754. _saveUserTemplateClicked: function()
  1755. {
  1756. if ( FLBuilderConfig.lite ) {
  1757. FLBuilder._showProMessage( 'Saving Templates' );
  1758. return;
  1759. }
  1760. FLBuilderSettingsForms.render( {
  1761. id : 'user_template',
  1762. className : 'fl-builder-user-template-settings',
  1763. rules : {
  1764. name: {
  1765. required: true
  1766. }
  1767. }
  1768. } );
  1769. },
  1770. /**
  1771. * Saves user template settings when the save button is clicked.
  1772. *
  1773. * @since 1.1.9
  1774. * @access private
  1775. * @method _saveUserTemplateSettings
  1776. */
  1777. _saveUserTemplateSettings: function()
  1778. {
  1779. var form = $(this).closest('.fl-builder-settings'),
  1780. valid = form.validate().form(),
  1781. settings = FLBuilder._getSettings(form);
  1782. if(valid) {
  1783. const actions = FL.Builder.data.getLayoutActions()
  1784. actions.saveUserTemplateSettings( settings )
  1785. FLBuilder._lightbox.close();
  1786. }
  1787. },
  1788. /**
  1789. * Shows a success alert when user template settings have saved.
  1790. *
  1791. * @since 1.1.9
  1792. * @access private
  1793. * @method _saveUserTemplateSettingsComplete
  1794. */
  1795. _saveUserTemplateSettingsComplete: function(data)
  1796. {
  1797. if ( !data ) return;
  1798. var data = FLBuilder._jsonParse(data);
  1799. FLBuilderConfig.contentItems.template.push(data);
  1800. FLBuilder.triggerHook('contentItemsChanged');
  1801. },
  1802. /**
  1803. * Callback for when a user clicks a user defined template in
  1804. * the template selector.
  1805. *
  1806. * @since 1.1.3
  1807. * @access private
  1808. * @method _userTemplateClicked
  1809. */
  1810. _userTemplateClicked: function()
  1811. {
  1812. var id = $(this).attr('data-id');
  1813. if($(FLBuilder._contentClass).children('.fl-row').length > 0) {
  1814. if(id == 'blank') {
  1815. if(confirm(FLBuilderStrings.changeTemplateMessage)) {
  1816. FLBuilder._lightbox._node.hide();
  1817. FLBuilder._applyTemplate('blank', false, 'user');
  1818. }
  1819. }
  1820. else {
  1821. FLBuilder._selectedTemplateId = id;
  1822. FLBuilder._selectedTemplateType = 'user';
  1823. FLBuilder._showTemplateActions();
  1824. FLBuilder._lightbox._node.hide();
  1825. }
  1826. }
  1827. else {
  1828. FLBuilder._applyTemplate(id, false, 'user');
  1829. }
  1830. },
  1831. /**
  1832. * Launches the builder in a new tab to edit a user
  1833. * defined template when the edit link is clicked.
  1834. *
  1835. * @since 1.1.3
  1836. * @access private
  1837. * @method _editUserTemplateClicked
  1838. * @param {Object} e The event object.
  1839. */
  1840. _editUserTemplateClicked: function(e)
  1841. {
  1842. e.preventDefault();
  1843. e.stopPropagation();
  1844. window.parent.open($(this).attr('href'));
  1845. },
  1846. /**
  1847. * Deletes a user defined template when the delete link is clicked.
  1848. *
  1849. * @since 1.1.3
  1850. * @access private
  1851. * @method _deleteUserTemplateClicked
  1852. * @param {Object} e The event object.
  1853. */
  1854. _deleteUserTemplateClicked: function(e)
  1855. {
  1856. var template = $( this ).closest( '.fl-user-template' ),
  1857. id = template.attr( 'data-id' ),
  1858. all = $( '.fl-user-template[data-id=' + id + ']', window.parent.document ),
  1859. parent = null,
  1860. index = null,
  1861. i = null,
  1862. item = null;
  1863. if ( confirm( FLBuilderStrings.deleteTemplate ) ) {
  1864. const actions = FL.Builder.data.getLayoutActions()
  1865. actions.deleteUserTemplate( id )
  1866. // Remove the item from library
  1867. for(i in FLBuilderConfig.contentItems.template) {
  1868. item = FLBuilderConfig.contentItems.template[i];
  1869. if (item.postId == id) {
  1870. index = i;
  1871. }
  1872. }
  1873. if (!_.isNull(index)) {
  1874. FLBuilderConfig.contentItems.template.splice(index, 1);
  1875. FLBuilder.triggerHook('contentItemsChanged');
  1876. }
  1877. }
  1878. e.stopPropagation();
  1879. },
  1880. /* Help Tour
  1881. ----------------------------------------------------------*/
  1882. /**
  1883. * Shows the help tour or template selector when the builder
  1884. * is launched.
  1885. *
  1886. * @since 1.4.9
  1887. * @access private
  1888. * @method _showTourOrTemplates
  1889. */
  1890. _showTourOrTemplates: function()
  1891. {
  1892. if ( ! FLBuilderConfig.simpleUi && ! FLBuilderConfig.isUserTemplate ) {
  1893. if ( FLBuilderConfig.help.tour && FLBuilderConfig.newUser ) {
  1894. FLBuilder._showTourLightbox();
  1895. }
  1896. else {
  1897. FLBuilder._initTemplateSelector();
  1898. }
  1899. }
  1900. },
  1901. /**
  1902. * Save browser stats when builder is loaded.
  1903. * @since 2.1.6
  1904. */
  1905. _doStats: function() {
  1906. if( 1 == FLBuilderConfig.statsEnabled ) {
  1907. args = {
  1908. 'screen-width': screen.width,
  1909. 'screen-height': screen.height,
  1910. 'pixel-ratio': window.devicePixelRatio,
  1911. 'user-agent': window.navigator.userAgent,
  1912. 'isrtl': FLBuilderConfig.isRtl
  1913. }
  1914. FLBuilder.ajax({
  1915. action: 'save_browser_stats',
  1916. browser_data: args
  1917. });
  1918. }
  1919. },
  1920. /**
  1921. * Shows the actions lightbox with a welcome message for new
  1922. * users asking if they would like to take the tour.
  1923. *
  1924. * @since 1.4.9
  1925. * @access private
  1926. * @method _showTourLightbox
  1927. */
  1928. _showTourLightbox: function()
  1929. {
  1930. var template = wp.template( 'fl-tour-lightbox' );
  1931. FLBuilder._actionsLightbox.open( template() );
  1932. },
  1933. /**
  1934. * Closes the actions lightbox and shows the template selector
  1935. * if a new user declines the tour.
  1936. *
  1937. * @since 1.4.9
  1938. * @access private
  1939. * @method _noTourButtonClicked
  1940. */
  1941. _noTourButtonClicked: function()
  1942. {
  1943. FLBuilder._actionsLightbox.close();
  1944. FLBuilder._initTemplateSelector();
  1945. },
  1946. /**
  1947. * Closes the actions lightbox and starts the tour when a new user
  1948. * decides to take the tour.
  1949. *
  1950. * @since 1.4.9
  1951. * @access private
  1952. * @method _yesTourButtonClicked
  1953. */
  1954. _yesTourButtonClicked: function()
  1955. {
  1956. FLBuilder._actionsLightbox.close();
  1957. FLBuilderTour.start();
  1958. },
  1959. /**
  1960. * Starts the help tour.
  1961. *
  1962. * @since 1.4.9
  1963. * @access private
  1964. * @method _startHelpTour
  1965. */
  1966. _startHelpTour: function()
  1967. {
  1968. FLBuilder._actionsLightbox.close();
  1969. FLBuilderTour.start();
  1970. },
  1971. /* Layout
  1972. ----------------------------------------------------------*/
  1973. /**
  1974. * Shows a message to drop a row or module to get started
  1975. * if the layout is empty.
  1976. *
  1977. * @since 1.0
  1978. * @access private
  1979. * @method _setupEmptyLayout
  1980. */
  1981. _setupEmptyLayout: function()
  1982. {
  1983. var content = $(FLBuilder._contentClass);
  1984. if ( FLBuilderConfig.isUserTemplate && 'module' == FLBuilderConfig.userTemplateType ) {
  1985. return;
  1986. }
  1987. else if ( FLBuilderConfig.isUserTemplate && 'column' == FLBuilderConfig.userTemplateType ) {
  1988. return;
  1989. }
  1990. else {
  1991. content.removeClass('fl-builder-empty');
  1992. content.find('.fl-builder-empty-message').remove();
  1993. if ( ! content.find( '.fl-row, .fl-builder-block' ).length ) {
  1994. content.addClass('fl-builder-empty');
  1995. content.append('<span class="fl-builder-empty-message">'+ FLBuilderStrings.emptyMessage +'</span>');
  1996. FLBuilder._initSortables();
  1997. }
  1998. }
  1999. },
  2000. /**
  2001. * Sends an AJAX request to re-render a single node.
  2002. *
  2003. * @since 2.0
  2004. * @access private
  2005. * @method _updateNode
  2006. * @param {String} nodeId
  2007. * @param {Function} callback
  2008. */
  2009. _updateNode: function( nodeId, callback )
  2010. {
  2011. if ( ! $( '.fl-node-' + nodeId ).length ) {
  2012. return;
  2013. }
  2014. FLBuilder._showNodeLoading( nodeId );
  2015. const actions = FL.Builder.data.getLayoutActions()
  2016. actions.renderNode( nodeId, callback )
  2017. },
  2018. /**
  2019. * Sends an AJAX request to render the layout and is typically
  2020. * used as a callback to many of the builder's save operations.
  2021. *
  2022. * @since 1.0
  2023. * @access private
  2024. * @method _updateLayout
  2025. */
  2026. _updateLayout: function()
  2027. {
  2028. FLBuilder.showAjaxLoader();
  2029. const actions = FL.Builder.data.getLayoutActions()
  2030. actions.renderLayout()
  2031. // Refresh Node Data
  2032. actions.fetchLayout()
  2033. },
  2034. /**
  2035. * Removes the current layout and renders a new layout using
  2036. * the provided data. Will render a node instead of the layout
  2037. * if data.partial is true.
  2038. *
  2039. * @since 1.0
  2040. * @access private
  2041. * @method _renderLayout
  2042. * @param {Object} data The layout data. May also be a JSON encoded string.
  2043. * @param {Function} callback A function to call when the layout has finished rendering.
  2044. */
  2045. _renderLayout: function( data, callback )
  2046. {
  2047. if ( FLBuilder._layout ) {
  2048. FLBuilder._layoutQueue.push( {
  2049. data: data,
  2050. callback: callback,
  2051. } );
  2052. } else {
  2053. FLBuilder._layout = new FLBuilderAJAXLayout( data, callback );
  2054. }
  2055. },
  2056. /**
  2057. * Called by the layout's JavaScript file once it's loaded
  2058. * to finish rendering the layout.
  2059. *
  2060. * @since 1.0
  2061. * @access private
  2062. * @method _renderLayoutComplete
  2063. */
  2064. _renderLayoutComplete: function()
  2065. {
  2066. if ( FLBuilder._layout ) {
  2067. FLBuilder._layout._complete();
  2068. FLBuilder._layout = null;
  2069. }
  2070. if ( FLBuilder._layoutQueue.length ) {
  2071. var item = FLBuilder._layoutQueue.shift();
  2072. FLBuilder._layout = new FLBuilderAJAXLayout( item.data, item.callback );
  2073. }
  2074. },
  2075. /**
  2076. * Trigger the resize event on the window so elements
  2077. * in the layout that rely on JavaScript know to resize.
  2078. *
  2079. * @since 1.0
  2080. * @access private
  2081. * @method _resizeLayout
  2082. */
  2083. _resizeLayout: function()
  2084. {
  2085. $(window).trigger('resize');
  2086. if(typeof YUI !== 'undefined') {
  2087. YUI().use('node-event-simulate', function(Y) {
  2088. Y.one(window).simulate("resize");
  2089. });
  2090. }
  2091. },
  2092. /**
  2093. * Checks to see if any rows exist in the layout, or if it is blank.
  2094. *
  2095. * @since 2.0
  2096. * @method layoutHasContent
  2097. * @return {Boolean}
  2098. */
  2099. layoutHasContent: function()
  2100. {
  2101. if( $(FLBuilder._contentClass).children('.fl-row').length > 0) {
  2102. return true;
  2103. } else {
  2104. return false;
  2105. }
  2106. },
  2107. /**
  2108. * Initializes MediaElements.js audio and video players.
  2109. *
  2110. * @since 1.0
  2111. * @access private
  2112. * @method _initMediaElements
  2113. */
  2114. _initMediaElements: function()
  2115. {
  2116. var settings = {};
  2117. if(typeof $.fn.mediaelementplayer != 'undefined') {
  2118. if(typeof _wpmejsSettings !== 'undefined') {
  2119. settings.pluginPath = _wpmejsSettings.pluginPath;
  2120. }
  2121. $('.wp-audio-shortcode, .wp-video-shortcode').not('.mejs-container').mediaelementplayer(settings);
  2122. }
  2123. },
  2124. /* Generic Drag and Drop
  2125. ----------------------------------------------------------*/
  2126. /**
  2127. * Inserts drop targets for nodes such as rows, columns
  2128. * and column groups since making those all sortables
  2129. * makes sorting really jumpy.
  2130. *
  2131. * @since 1.9
  2132. * @access private
  2133. * @method _initDropTargets
  2134. */
  2135. _initDropTargets: function()
  2136. {
  2137. var notGlobal = 'row' == FLBuilderConfig.userTemplateType ? '' : ':not(.fl-node-global)',
  2138. rows = $( FLBuilder._contentClass + ' .fl-row' ),
  2139. row = null,
  2140. groups = $( FLBuilder._contentClass + ' .fl-row' + notGlobal ).find( '.fl-col-group' ),
  2141. group = null,
  2142. cols = null,
  2143. rootCol = 'column' == FLBuilderConfig.userTemplateType ? $( FLBuilder._contentClass + '> .fl-col' ).eq(0) : null,
  2144. modules = $( FLBuilder._contentClass + ' .fl-row' + notGlobal ).find( '.fl-row-content > .fl-module' ),
  2145. i = 0;
  2146. // Remove old drop targets.
  2147. $( '.fl-col-drop-target' ).remove();
  2148. $( '.fl-col-group-drop-target' ).remove();
  2149. $( '.fl-row-drop-target' ).remove();
  2150. // Row drop targets.
  2151. $( FLBuilder._contentClass ).append( '<div class="fl-drop-target fl-row-drop-target"></div>' );
  2152. rows.prepend( '<div class="fl-drop-target fl-row-drop-target"></div>' );
  2153. rows.append( '<div class="fl-drop-target fl-drop-target-last fl-row-drop-target fl-row-drop-target-last"></div>' );
  2154. // Add drop targets to empty rows.
  2155. rows.find( '.fl-row-content' ).prepend( '<div class="fl-drop-target fl-col-group-drop-target fl-col-group-drop-target-empty"></div>' );
  2156. FLBuilder._initEmptyRowDropTargets();
  2157. // Add drop targets to container modules.
  2158. modules.append( '<div class="fl-drop-target fl-col-group-drop-target"></div>' );
  2159. modules.append( '<div class="fl-drop-target fl-drop-target-last fl-col-group-drop-target fl-col-group-drop-target-last"></div>' );
  2160. // Add drop targets to column groups and columns.
  2161. for ( i = 0; i < groups.length; i++ ) {
  2162. group = groups.eq( i );
  2163. cols = group.find( '> .fl-col' );
  2164. // Column group drop targets.
  2165. if ( ! group.hasClass( 'fl-col-group-nested' ) ) {
  2166. group.append( '<div class="fl-drop-target fl-col-group-drop-target"></div>' );
  2167. group.append( '<div class="fl-drop-target fl-drop-target-last fl-col-group-drop-target fl-col-group-drop-target-last"></div>' );
  2168. }
  2169. // Column drop targets.
  2170. cols.append( '<div class="fl-drop-target fl-col-drop-target"></div>' );
  2171. cols.append( '<div class="fl-drop-target fl-drop-target-last fl-col-drop-target fl-col-drop-target-last"></div>' );
  2172. }
  2173. // Add drop targets to root column of a column template.
  2174. if ( rootCol && 0 === groups.length ) {
  2175. groups = rootCol.find( '.fl-col-group' );
  2176. rootCol.append( '<div class="fl-drop-target fl-col-drop-target"></div>' );
  2177. rootCol.append( '<div class="fl-drop-target fl-drop-target-last fl-col-drop-target fl-col-drop-target-last"></div>' );
  2178. }
  2179. },
  2180. /**
  2181. * Shows drop targets in empty rows with no content.
  2182. *
  2183. * @method _initEmptyRowDropTargets
  2184. * @return void
  2185. */
  2186. _initEmptyRowDropTargets: function()
  2187. {
  2188. var emptyRows = $( FLBuilder._contentClass + ' .fl-row-content:not(:has(*:visible))' );
  2189. $( '.fl-row-content-empty' ).removeClass( '.fl-row-content-empty' );
  2190. if ( emptyRows.length ) {
  2191. emptyRows.addClass( 'fl-row-content-empty' );
  2192. }
  2193. },
  2194. /**
  2195. * Returns a helper element for a drag operation.
  2196. *
  2197. * @since 1.0
  2198. * @access private
  2199. * @method _blockDragHelper
  2200. * @param {Object} e The event object.
  2201. * @param {Object} item The item being dragged.
  2202. * @return {Object} The helper element.
  2203. */
  2204. _blockDragHelper: function (e, item)
  2205. {
  2206. var helper = item.clone();
  2207. item.clone().insertAfter(item);
  2208. helper.addClass('fl-builder-block-drag-helper');
  2209. return helper;
  2210. },
  2211. /**
  2212. * Initializes a drag operation.
  2213. *
  2214. * @since 1.0
  2215. * @access private
  2216. * @method _blockDragInit
  2217. * @param {Object} e The event object.
  2218. */
  2219. _blockDragInit: function( e )
  2220. {
  2221. var target = $( e.currentTarget ),
  2222. node = null,
  2223. scrollTop = $( window ).scrollTop(),
  2224. initialPos = 0;
  2225. // Set the _dragEnabled flag.
  2226. FLBuilder._dragEnabled = true;
  2227. // Save the initial scroll position.
  2228. FLBuilder._dragInitialScrollTop = scrollTop;
  2229. // Get the node to scroll to once the node highlights have affected the body height.
  2230. if ( target.closest( '[data-node]' ).length > 0 ) {
  2231. // Set the node to a node instance being dragged.
  2232. node = target.closest( '[data-node]' );
  2233. // Mark this node as initialized for dragging.
  2234. node.addClass( 'fl-node-drag-init' );
  2235. }
  2236. else if ( target.hasClass( 'fl-builder-block' ) ) {
  2237. // Set the node to the first visible row instance.
  2238. $( '.fl-row' ).each( function() {
  2239. if ( node === null && $( this ).offset().top - scrollTop > 0 ) {
  2240. node = $( this );
  2241. }
  2242. } );
  2243. }
  2244. // Get the initial scroll position of the node.
  2245. if ( node !== null ) {
  2246. initialPos = node.offset().top - scrollTop;
  2247. }
  2248. // Setup the UI for dragging.
  2249. FLBuilder._highlightRowsAndColsForDrag( target );
  2250. FLBuilder._adjustColHeightsForDrag();
  2251. FLBuilder._disableGlobalNodes();
  2252. FLBuilder._destroyOverlayEvents();
  2253. FLBuilder._initSortables();
  2254. $( 'body' ).addClass( 'fl-builder-dragging' );
  2255. $( 'body', window.parent.document ).addClass( 'fl-builder-dragging' );
  2256. $( '.fl-builder-empty-message' ).hide();
  2257. $( '.fl-sortable-disabled' ).removeClass( 'fl-sortable-disabled' );
  2258. // Remove all action overlays if this isn't a touch for a proxy item.
  2259. if ( 'touchstart' !== e.type && ! $( e.target ).hasClass( 'fl-sortable-proxy-item ' ) ) {
  2260. FLBuilder._removeAllOverlays();
  2261. }
  2262. // Scroll to the node that is dragging.
  2263. if ( initialPos > 0 ) {
  2264. scrollTo( 0, node.offset().top - initialPos );
  2265. }
  2266. FLBuilder.triggerHook('didInitDrag');
  2267. },
  2268. /**
  2269. * Callback that fires when dragging starts.
  2270. *
  2271. * @since 1.0
  2272. * @access private
  2273. * @method _blockDragStart
  2274. * @param {Object} e The event object.
  2275. * @param {Object} ui An object with additional info for the drag.
  2276. */
  2277. _blockDragStart: function(e, ui)
  2278. {
  2279. // Let the builder know dragging has started.
  2280. FLBuilder._dragging = true;
  2281. // Removed the drag init class as we're done with that.
  2282. $( '.fl-node-drag-init' ).removeClass( 'fl-node-drag-init' );
  2283. FLBuilder.triggerHook('didStartDrag');
  2284. },
  2285. /**
  2286. * Callback that fires when an element that is being
  2287. * dragged is sorted.
  2288. *
  2289. * @since 1.0
  2290. * @access private
  2291. * @method _blockDragSort
  2292. * @param {Object} e The event object.
  2293. * @param {Object} ui An object with additional info for the drag.
  2294. */
  2295. _blockDragSort: function(e, ui)
  2296. {
  2297. var parent = ui.placeholder.parent(),
  2298. title = FLBuilderStrings.insert;
  2299. // Prevent sorting?
  2300. if ( FLBuilder._blockPreventSort( ui.item, parent ) ) {
  2301. return;
  2302. }
  2303. // Reset flex items with a fixed width.
  2304. $( '.fl-sortable-fixed-width' ).css( 'max-width', '' ).removeClass( 'fl-sortable-fixed-width' );
  2305. // Find the placeholder title.
  2306. if(parent.hasClass('fl-col-content') || parent.hasClass('fl-module') || parent.hasClass('fl-module-content')) {
  2307. // Make flex items fixed width while sorting.
  2308. if ((/flex/).test(parent.css('display'))) {
  2309. ui.placeholder.hide();
  2310. parent.css('max-width', '');
  2311. parent.css('max-width', parent.outerWidth());
  2312. parent.addClass( 'fl-sortable-fixed-width' );
  2313. ui.placeholder.show();
  2314. }
  2315. if ((/flex/).test(parent.css('display')) && (/row|row-reverse/).test(parent.css( "flex-direction" ))) {
  2316. title = '';
  2317. }
  2318. else if(ui.item.hasClass('fl-builder-block-row')) {
  2319. title = ui.item.find('.fl-builder-block-title').text();
  2320. }
  2321. else if(ui.item.hasClass('fl-col-sortable-proxy-item')) {
  2322. title = FLBuilderStrings.column;
  2323. }
  2324. else if(ui.item.hasClass('fl-builder-block-module')) {
  2325. title = ui.item.find('.fl-builder-block-title').text();
  2326. }
  2327. else if(ui.item.hasClass('fl-builder-block-saved-module') || ui.item.hasClass('fl-builder-block-module-template')) {
  2328. title = ui.item.find('.fl-builder-block-title').text();
  2329. }
  2330. else {
  2331. title = ui.item.data('name');
  2332. }
  2333. }
  2334. else if(parent.hasClass('fl-col-drop-target')) {
  2335. title = '';
  2336. }
  2337. else if (parent.hasClass('fl-col-group-drop-target')) {
  2338. title = '';
  2339. }
  2340. else if(parent.hasClass('fl-row-drop-target')) {
  2341. if(ui.item.hasClass('fl-builder-block-row')) {
  2342. title = ui.item.find('.fl-builder-block-title').text();
  2343. }
  2344. else if(ui.item.hasClass('fl-builder-block-saved-row')) {
  2345. title = ui.item.find('.fl-builder-block-title').text();
  2346. }
  2347. else if(ui.item.hasClass('fl-builder-block-saved-column')) {
  2348. title = ui.item.find('.fl-builder-block-title').text();
  2349. }
  2350. else if(ui.item.hasClass('fl-row-sortable-proxy-item')) {
  2351. title = FLBuilderStrings.row;
  2352. }
  2353. else {
  2354. title = FLBuilderStrings.newRow;
  2355. }
  2356. }
  2357. // Set the placeholder title.
  2358. ui.placeholder.html(title);
  2359. // Add the global class?
  2360. if ( ui.item.hasClass( 'fl-node-global' ) ||
  2361. ui.item.hasClass( 'fl-builder-block-global' ) ||
  2362. $( '.fl-node-dragging' ).hasClass( 'fl-node-global' )
  2363. ) {
  2364. ui.placeholder.addClass( 'fl-builder-drop-zone-global' );
  2365. }
  2366. else {
  2367. ui.placeholder.removeClass( 'fl-builder-drop-zone-global' );
  2368. }
  2369. },
  2370. /**
  2371. * Callback that fires when an element that is being
  2372. * dragged position changes.
  2373. *
  2374. * What we're doing here keeps it from appearing jumpy when draging
  2375. * between columns. Without this you'd see the placeholder jump into
  2376. * a column position briefly when you didn't intend for it to.
  2377. *
  2378. * @since 1.9
  2379. * @access private
  2380. * @method _blockDragChange
  2381. * @param {Object} e The event object.
  2382. * @param {Object} ui An object with additional info for the drag.
  2383. */
  2384. _blockDragChange: function( e, ui )
  2385. {
  2386. FLBuilder._initEmptyRowDropTargets();
  2387. ui.placeholder.css( 'opacity', '0' );
  2388. ui.placeholder.animate( { 'opacity': '1' }, 100 );
  2389. },
  2390. /**
  2391. * Prevents sorting of items that shouldn't be sorted into
  2392. * specific areas.
  2393. *
  2394. * @since 1.9
  2395. * @access private
  2396. * @method _blockPreventSort
  2397. * @param {Object} item The item being sorted.
  2398. * @param {Object} parent The new parent.
  2399. */
  2400. _blockPreventSort: function( item, parent )
  2401. {
  2402. var prevent = false,
  2403. isRowBlock = item.hasClass( 'fl-builder-block-row' ),
  2404. isCol = item.hasClass( 'fl-col-sortable-proxy-item' ),
  2405. isModule = item.hasClass( 'fl-builder-block-module' ) || item.hasClass( 'fl-module-sortable-proxy-item' ),
  2406. isContainerModule = parent.data( 'accepts' ),
  2407. isParentColGlobal = parent.closest('.fl-col[data-template-url]').hasClass('fl-node-global'),
  2408. isParentCol = parent.hasClass( 'fl-col-content' ),
  2409. isColTarget = parent.hasClass( 'fl-col-drop-target' ),
  2410. group = parent.parents( '.fl-col-group:not(.fl-col-group-nested)' ),
  2411. nestedGroup = parent.parents( '.fl-col-group-nested' ),
  2412. parentType = parent.data( 'type' ),
  2413. itemType = item.data( 'type' );
  2414. // Handle container modules.
  2415. if ( isContainerModule ) {
  2416. const { accepts } = FLBuilderConfig.contentItems.module.filter( config => parentType === config.slug ).pop();
  2417. // Prevent rows and columns in container modules.
  2418. if ( isRowBlock || isCol ) {
  2419. prevent = true;
  2420. }
  2421. // Prevent unaccepted nodes in container modules.
  2422. else if ( 'object' === typeof accepts && accepts.length && ! accepts.includes( itemType ) ) {
  2423. prevent = true;
  2424. }
  2425. }
  2426. // Prevent modules in unaccepted parents.
  2427. // if ( isModule && 'widget' !== itemType ) {
  2428. // const { parents } = FLBuilderConfig.contentItems.module.filter( config => itemType === config.slug ).pop();
  2429. //
  2430. // if ( 'object' === typeof parents && ! parents.includes( parentType ) ) {
  2431. // prevent = true;
  2432. // }
  2433. // }
  2434. // Prevent columns in nested columns.
  2435. if ( ( isRowBlock || isCol ) && isParentCol && nestedGroup.length > 0 ) {
  2436. prevent = true;
  2437. }
  2438. // Prevent 1 column from being nested in an empty column.
  2439. if ( isParentCol && ! parent.find( '.fl-module, .fl-col' ).length ) {
  2440. if ( isRowBlock && '1-col' == item.data( 'cols' ) ) {
  2441. prevent = true;
  2442. }
  2443. else if ( isCol ) {
  2444. prevent = true;
  2445. }
  2446. }
  2447. // Prevent 5 or 6 columns from being nested.
  2448. if ( isRowBlock && isParentCol && $.inArray( item.data( 'cols' ), [ '5-cols', '6-cols' ] ) > -1 ) {
  2449. prevent = true;
  2450. }
  2451. // Prevent columns with nested columns from being dropped in nested columns.
  2452. if ( isCol && $( '.fl-node-dragging' ).find( '.fl-col-group-nested' ).length > 0 ) {
  2453. if ( isParentCol || ( isColTarget && nestedGroup.length > 0 ) ) {
  2454. prevent = true;
  2455. }
  2456. }
  2457. // Prevent more than 12 columns.
  2458. if ( isColTarget && group.length > 0 && 0 === nestedGroup.length && group.find( '> .fl-col:visible' ).length > 11 ) {
  2459. prevent = true;
  2460. }
  2461. // Prevent more than 4 nested columns.
  2462. if ( isColTarget && nestedGroup.length > 0 && nestedGroup.find( '.fl-col:visible' ).length > 3 ) {
  2463. prevent = true;
  2464. }
  2465. // Prevent module from being dropped to a Global Col except when editing a saved column.
  2466. if ( isModule && isParentColGlobal && 'column' !== FLBuilderConfig.userTemplateType ) {
  2467. prevent = true;
  2468. }
  2469. // Prevent dropping into layouts rendered via shortcode.
  2470. if ( parent.closest( '.fl-builder-shortcode-mask-wrap' ).length ) {
  2471. prevent = true;
  2472. }
  2473. // Add the disabled class if we are preventing a sort.
  2474. if ( prevent ) {
  2475. parent.addClass( 'fl-sortable-disabled' );
  2476. }
  2477. return prevent;
  2478. },
  2479. /**
  2480. * Cleans up when a drag operation has stopped.
  2481. *
  2482. * @since 1.0
  2483. * @access private
  2484. * @method _blockDragStop
  2485. * @param {Object} e The event object.
  2486. * @param {Object} ui An object with additional info for the drag.
  2487. */
  2488. _blockDragStop: function( e, ui )
  2489. {
  2490. var scrollTop = $( window ).scrollTop(),
  2491. parent = ui.item.parent(),
  2492. initialPos = null;
  2493. // Get the node to scroll to once removing the node highlights affects the body height.
  2494. if ( parent.hasClass( 'fl-drop-target' ) && parent.closest( '[data-node]' ).length ) {
  2495. parent = parent.closest( '[data-node]' );
  2496. initialPos = parent.offset().top - scrollTop;
  2497. }
  2498. else {
  2499. initialPos = parent.offset().top - scrollTop;
  2500. }
  2501. // Show the panel if a block was dropped back in.
  2502. if ( parent.hasClass( 'fl-builder-blocks-section-content' ) ) {
  2503. FLBuilder._showPanel();
  2504. }
  2505. // Finish dragging.
  2506. FLBuilder._dragEnabled = false;
  2507. FLBuilder._dragging = false;
  2508. FLBuilder._bindOverlayEvents();
  2509. FLBuilder._removeEmptyRowAndColHighlights();
  2510. FLBuilder._highlightEmptyCols();
  2511. FLBuilder._enableGlobalNodes();
  2512. FLBuilder._setupEmptyLayout();
  2513. $( 'body' ).removeClass( 'fl-builder-dragging' );
  2514. $( 'body', window.parent.document ).removeClass( 'fl-builder-dragging' );
  2515. // Scroll the page back to where it was.
  2516. scrollTo( 0, parent.offset().top - initialPos );
  2517. FLBuilder.triggerHook('didStopDrag');
  2518. },
  2519. /**
  2520. * Cleans up when a drag operation has canceled.
  2521. *
  2522. * @since 1.0
  2523. * @access private
  2524. * @method _blockDragCancel
  2525. */
  2526. _blockDragCancel: function()
  2527. {
  2528. if ( FLBuilder._dragEnabled && ! FLBuilder._dragging ) {
  2529. FLBuilder._dragEnabled = false;
  2530. FLBuilder._dragging = false;
  2531. FLBuilder._bindOverlayEvents();
  2532. FLBuilder._removeEmptyRowAndColHighlights();
  2533. FLBuilder._highlightEmptyCols();
  2534. FLBuilder._enableGlobalNodes();
  2535. FLBuilder._setupEmptyLayout();
  2536. $( 'body' ).removeClass( 'fl-builder-dragging' );
  2537. $( 'body', window.parent.document ).removeClass( 'fl-builder-dragging' );
  2538. $( '.fl-node-drag-init' ).removeClass( 'fl-node-drag-init' );
  2539. $( '.fl-node-dragging' ).removeClass( 'fl-node-dragging' );
  2540. scrollTo( 0, FLBuilder._dragInitialScrollTop );
  2541. FLBuilder.triggerHook('didCancelDrag');
  2542. }
  2543. },
  2544. /**
  2545. * Reorders a node within its parent.
  2546. *
  2547. * @since 1.9
  2548. * @access private
  2549. * @method _reorderNode
  2550. * @param {String} nodeId The node ID of the node.
  2551. * @param {Number} position The new position.
  2552. */
  2553. _reorderNode: function( nodeId, position )
  2554. {
  2555. const actions = FL.Builder.getActions()
  2556. actions.moveNode( nodeId, position )
  2557. },
  2558. /**
  2559. * Handle completion after reorder ajax.
  2560. *
  2561. * @since 2.5
  2562. * @access private
  2563. * @method _reorderNodeComplete
  2564. * @param Object ajax response
  2565. */
  2566. _reorderNodeComplete: function( response ) {
  2567. var data = FLBuilder._jsonParse( response );
  2568. var hook = 'didMove' + data.nodeType.charAt(0).toUpperCase() + data.nodeType.slice(1);
  2569. FLBuilder.triggerHook( 'didMoveNode', data );
  2570. FLBuilder.triggerHook( hook, data );
  2571. },
  2572. /**
  2573. * Moves a node up a position within its parent.
  2574. *
  2575. * @since 2.8
  2576. */
  2577. _moveNodeUpClicked: function( e )
  2578. {
  2579. var node = $( e.target ).closest( '[data-node]' );
  2580. var nodeId = node.attr( 'data-node' );
  2581. var data = FL.Builder.data.getNode( nodeId );
  2582. var position = data.position - 1;
  2583. var upBtn = node.find( '.fl-block-move-up' ).first();
  2584. var downBtn = node.find( '.fl-block-move-down' ).first();
  2585. if ( position <= 0 ) {
  2586. upBtn.addClass( 'fl-builder-submenu-disabled' );
  2587. } else {
  2588. upBtn.removeClass( 'fl-builder-submenu-disabled' );
  2589. }
  2590. downBtn.removeClass( 'fl-builder-submenu-disabled' );
  2591. FLBuilder._reorderNode( nodeId, position );
  2592. FLBuilder._selectNodeOverlay( node, false );
  2593. FL.Builder.data.updateNode( nodeId, { ...data, position } );
  2594. },
  2595. /**
  2596. * Moves a node down a position within its parent.
  2597. *
  2598. * @since 2.8
  2599. */
  2600. _moveNodeDownClicked: function( e )
  2601. {
  2602. var node = $( e.target ).closest( '[data-node]' );
  2603. var nodeId = node.attr( 'data-node' );
  2604. var data = FL.Builder.data.getNode( nodeId );
  2605. var position = data.position + 1;
  2606. var upBtn = node.find( '.fl-block-move-up' ).first();
  2607. var downBtn = node.find( '.fl-block-move-down' ).first();
  2608. var parentChildren = node.parent().find( '> [data-node]' );
  2609. if ( position >= parentChildren.length - 1 ) {
  2610. downBtn.addClass( 'fl-builder-submenu-disabled' );
  2611. } else {
  2612. downBtn.removeClass( 'fl-builder-submenu-disabled' );
  2613. }
  2614. upBtn.removeClass( 'fl-builder-submenu-disabled' );
  2615. FLBuilder._reorderNode( nodeId, position );
  2616. FLBuilder._selectNodeOverlay( node, false );
  2617. FL.Builder.data.updateNode( nodeId, { ...data, position } );
  2618. },
  2619. /**
  2620. * Moves a node to a new parent.
  2621. *
  2622. * @since 1.9
  2623. * @access private
  2624. * @method _moveNode
  2625. * @param {String} newParent The node ID of the new parent.
  2626. * @param {String} nodeId The node ID of the node.
  2627. * @param {Number} position The new position.
  2628. */
  2629. _moveNode: function( newParent, nodeId, position )
  2630. {
  2631. const actions = FL.Builder.getActions()
  2632. actions.moveNode( nodeId, position, newParent )
  2633. },
  2634. /**
  2635. * Finishes a move node operation.
  2636. *
  2637. * @since 2.5
  2638. * @access private
  2639. * @method _moveNodeComplete
  2640. * @param Object ajax response
  2641. */
  2642. _moveNodeComplete: function( response )
  2643. {
  2644. const data = FLBuilder._jsonParse( response );
  2645. const hook = 'didMove' + data.nodeType.charAt( 0 ).toUpperCase() + data.nodeType.slice( 1 );
  2646. FLBuilder.triggerHook( 'didMoveNode', data );
  2647. FLBuilder.triggerHook( hook, data );
  2648. },
  2649. /* Generic Node Methods
  2650. ----------------------------------------------------------*/
  2651. /**
  2652. * Returns a JQuery reference to the HTMLElement for particular node.
  2653. *
  2654. * @since 2.5
  2655. * @access private
  2656. * @method _getjQueryElement
  2657. * @return {JQuery} dom reference.
  2658. */
  2659. _getJQueryElement: function( id ) {
  2660. return $( FLBuilder._contentClass ).find( '[data-node="' + id + '"]' )
  2661. },
  2662. /**
  2663. * Returns whether a node lays its children out
  2664. * horizontally, vertically, or layered.
  2665. *
  2666. * @since 2.8
  2667. * @param {Object} node
  2668. */
  2669. _getNodeLayoutDirection: function( node )
  2670. {
  2671. var isFlex = ( /flex/ ).test( node.parent().css( 'display' ) );
  2672. var isFlexRow = ( /row|row-reverse/ ).test( node.parent().css( 'flex-direction' ) );
  2673. var isGrid = ( /grid/ ).test( node.parent().css( 'display' ) );
  2674. var isLayeredGrid = isGrid && '1 / 1 / -1 / -1' === node.css( 'grid-area' );
  2675. var isSingleColumnGrid = 1 === window.getComputedStyle( node.parent()[0] ).getPropertyValue( 'grid-template-columns' ).split( ' ' ).length;
  2676. if ( isFlex && isFlexRow ) {
  2677. return 'horizontal';
  2678. } else if ( isGrid ) {
  2679. if ( isLayeredGrid ) {
  2680. return 'layered';
  2681. } else if ( ! isSingleColumnGrid ) {
  2682. return 'horizontal';
  2683. }
  2684. }
  2685. return 'vertical';
  2686. },
  2687. /**
  2688. * Checks if a node requires a confirmation message before deleting.
  2689. *
  2690. * @since 2.5
  2691. * @access private
  2692. * @method _needsDeleteConfirmation
  2693. * @return bool
  2694. */
  2695. _needsDeleteConfirmation: function( node ) {
  2696. if ( 'module' === node.type ) {
  2697. return true
  2698. }
  2699. // Otherwise check if the container has modules
  2700. const el = FLBuilder._getJQueryElement( node.node )
  2701. return el.find( '.fl-module' ).length > 0
  2702. },
  2703. /**
  2704. * Disables global nodes during drag.
  2705. *
  2706. * @since 2.8
  2707. */
  2708. _disableGlobalNodes: function()
  2709. {
  2710. if ( 'row' !== FLBuilderConfig.userTemplateType ) {
  2711. $( '.fl-row.fl-node-global' ).addClass( 'fl-node-disabled' );
  2712. }
  2713. if ( 'column' !== FLBuilderConfig.userTemplateType ) {
  2714. $( '.fl-row:not(.fl-node-global) .fl-col.fl-node-global' ).addClass( 'fl-node-disabled' );
  2715. }
  2716. if ( 'module' !== FLBuilderConfig.userTemplateType ) {
  2717. $( '.fl-row:not(.fl-node-global) .fl-module.fl-node-global[data-accepts]' ).addClass( 'fl-node-disabled' );
  2718. }
  2719. },
  2720. /**
  2721. * Remove disabled global node class after drag.
  2722. *
  2723. * @since 2.8
  2724. */
  2725. _enableGlobalNodes: function()
  2726. {
  2727. $( '.fl-node-disabled' ).removeClass( 'fl-node-disabled' );
  2728. },
  2729. /**
  2730. * Called when the node settings overlay action is clicked.
  2731. *
  2732. * @since 2.8
  2733. */
  2734. _nodeSettingsClicked: function( e )
  2735. {
  2736. var button = $( this );
  2737. var nodeId = button.attr( 'data-target-node' );
  2738. var actions = FL.Builder.getActions();
  2739. if ( ! nodeId ) {
  2740. nodeId = button.parents( '[data-node]' ).attr( 'data-node' );
  2741. }
  2742. actions.openSettings( nodeId );
  2743. e.stopPropagation();
  2744. },
  2745. /**
  2746. * Called when the node duplicate overlay action is clicked.
  2747. *
  2748. * @since 2.8
  2749. */
  2750. _nodeDuplicateClicked: function( e )
  2751. {
  2752. var button = $( this );
  2753. var nodeId = button.attr( 'data-target-node' );
  2754. var actions = FL.Builder.getActions();
  2755. if ( ! nodeId ) {
  2756. nodeId = button.parents( '[data-node]' ).attr( 'data-node' );
  2757. }
  2758. actions.copyNode( nodeId );
  2759. e.stopPropagation();
  2760. },
  2761. /**
  2762. * Called when the node remove overlay action is clicked.
  2763. *
  2764. * @since 2.8
  2765. */
  2766. _nodeRemoveClicked: function( e )
  2767. {
  2768. var button = $( this );
  2769. var nodeId = button.attr( 'data-target-node' );
  2770. var actions = FL.Builder.getActions();
  2771. if ( ! nodeId ) {
  2772. nodeId = button.parents( '[data-node]' ).attr( 'data-node' );
  2773. }
  2774. actions.deleteNode( nodeId );
  2775. e.stopPropagation();
  2776. },
  2777. /* Rows
  2778. ----------------------------------------------------------*/
  2779. /**
  2780. * Returns a helper element for row drag operations.
  2781. *
  2782. * @since 1.0
  2783. * @access private
  2784. * @method _rowDragHelper
  2785. * @return {Object} The helper element.
  2786. */
  2787. _rowDragHelper: function()
  2788. {
  2789. return $('<div class="fl-builder-block-drag-helper">' + FLBuilderStrings.row + '</div>');
  2790. },
  2791. /**
  2792. * Initializes dragging for row. Rows themselves aren't sortables
  2793. * as nesting that many sortables breaks down quickly and draggable by
  2794. * itself is slow. Instead, we are programmatically triggering the drag
  2795. * of our helper div that isn't a nested sortable but connected to the
  2796. * sortables in the main layout.
  2797. *
  2798. * @since 1.9
  2799. * @access private
  2800. * @method _rowDragInit
  2801. * @param {Object} e The event object.
  2802. */
  2803. _rowDragInit: function( e )
  2804. {
  2805. var handle = $( e.target ),
  2806. helper = $( '.fl-row-sortable-proxy-item', window.parent.document ),
  2807. row = handle.closest( '.fl-row' );
  2808. if ( handle.closest( '.fl-block-move-menu' ).length ) {
  2809. return;
  2810. }
  2811. row.addClass( 'fl-node-dragging' );
  2812. FLBuilder._blockDragInit( e );
  2813. e.target = helper[ 0 ];
  2814. helper.trigger( e );
  2815. },
  2816. /**
  2817. * @method _rowDragInitTouch
  2818. * @param {Object} e The event object.
  2819. */
  2820. _rowDragInitTouch: function( startEvent )
  2821. {
  2822. var handle = $( startEvent.target ),
  2823. helper = $( '.fl-row-sortable-proxy-item', window.parent.document ),
  2824. row = handle.closest( '.fl-row' ),
  2825. moved = false;
  2826. handle.on( 'touchmove', function( moveEvent ) {
  2827. if ( ! moved ) {
  2828. startEvent.currentTarget = row[0];
  2829. FLBuilder._rowDragInit( startEvent );
  2830. moved = true;
  2831. }
  2832. moveEvent.target = helper[0];
  2833. helper.trigger( moveEvent );
  2834. } );
  2835. handle.on( 'touchend', function( endEvent ) {
  2836. endEvent.target = helper[0];
  2837. helper.trigger( endEvent );
  2838. endEvent.stopPropagation();
  2839. } );
  2840. },
  2841. /**
  2842. * Callback that fires when dragging starts for a row.
  2843. *
  2844. * @since 1.9
  2845. * @access private
  2846. * @method _rowDragStart
  2847. * @param {Object} e The event object.
  2848. * @param {Object} ui An object with additional info for the drag.
  2849. */
  2850. _rowDragStart: function( e, ui )
  2851. {
  2852. var rows = $( FLBuilder._contentClass + ' .fl-row' ),
  2853. row = $( '.fl-node-dragging' );
  2854. if ( 1 === rows.length ) {
  2855. $( FLBuilder._contentClass ).addClass( 'fl-builder-empty' );
  2856. }
  2857. row.hide();
  2858. FLBuilder._blockDragStart( e, ui );
  2859. },
  2860. /**
  2861. * Callback for when a row drag operation completes.
  2862. *
  2863. * @since 1.0
  2864. * @access private
  2865. * @method _rowDragStop
  2866. * @param {Object} e The event object.
  2867. * @param {Object} ui An object with additional info for the drag.
  2868. */
  2869. _rowDragStop: function( e, ui )
  2870. {
  2871. var item = ui.item,
  2872. parent = item.parent(),
  2873. row = null,
  2874. group = null,
  2875. position = 0;
  2876. FLBuilder._blockDragStop( e, ui );
  2877. // A row was dropped back into the row list.
  2878. if ( parent.hasClass( 'fl-builder-rows' ) ) {
  2879. item.remove();
  2880. return;
  2881. }
  2882. // A row was dropped back into the sortable proxy.
  2883. else if ( parent.hasClass( 'fl-row-sortable-proxy' ) ) {
  2884. $( '.fl-node-dragging' ).removeClass( 'fl-node-dragging' ).show();
  2885. return;
  2886. }
  2887. // Add a new row.
  2888. else if ( item.hasClass( 'fl-builder-block' ) ) {
  2889. // Cancel the drop if the sortable is disabled?
  2890. if ( parent.hasClass( 'fl-sortable-disabled' ) ) {
  2891. item.remove();
  2892. FLBuilder._showPanel();
  2893. return;
  2894. }
  2895. // A new row was dropped into column.
  2896. else if ( parent.hasClass( 'fl-col-content' ) ) {
  2897. FLBuilder._addColGroup(
  2898. item.closest( '.fl-col' ).attr( 'data-node' ),
  2899. item.attr( 'data-cols' ),
  2900. parent.find( '> .fl-module, .fl-col-group, .fl-builder-block' ).index( item )
  2901. );
  2902. }
  2903. // A new row was dropped next to a column.
  2904. else if ( parent.hasClass( 'fl-col-drop-target' ) ) {
  2905. FLBuilder._addCols(
  2906. parent.closest( '.fl-col' ),
  2907. parent.hasClass( 'fl-col-drop-target-last' ) ? 'after' : 'before',
  2908. item.attr( 'data-cols' ),
  2909. parent.closest( '.fl-col-group-nested' ).length > 0
  2910. );
  2911. }
  2912. // A new row was dropped into a column group position.
  2913. else if ( parent.hasClass( 'fl-col-group-drop-target' ) ) {
  2914. group = item.closest( '.fl-col-group, .fl-module' );
  2915. position = item.closest( '.fl-row' ).find( '.fl-row-content' ).find( '> .fl-col-group, > .fl-module' ).index( group );
  2916. FLBuilder._addColGroup(
  2917. item.closest( '.fl-row' ).attr( 'data-node' ),
  2918. item.attr( 'data-cols' ),
  2919. parent.hasClass( 'fl-drop-target-last' ) ? position + 1 : position
  2920. );
  2921. }
  2922. // A row was dropped into a row position.
  2923. else {
  2924. row = item.closest( '.fl-row' );
  2925. position = ! row.length ? 0 : $( FLBuilder._contentClass + ' > .fl-row' ).index( row );
  2926. FLBuilder._addRow(
  2927. item.attr('data-cols'),
  2928. parent.hasClass( 'fl-drop-target-last' ) ? position + 1 : position
  2929. );
  2930. }
  2931. // Remove the helper.
  2932. item.remove();
  2933. // Show the builder panel.
  2934. FLBuilder._showPanel();
  2935. }
  2936. // Reorder a row.
  2937. else {
  2938. row = $( '.fl-node-dragging' ).removeClass( 'fl-node-dragging' ).show();
  2939. // Make sure a single row wasn't dropped back into the main layout.
  2940. if ( ! parent.parent().hasClass( 'fl-builder-content' ) ) {
  2941. // Move the row in the UI.
  2942. if ( parent.hasClass( 'fl-drop-target-last' ) ) {
  2943. parent.parent().after( row );
  2944. }
  2945. else {
  2946. parent.parent().before( row );
  2947. }
  2948. // Reorder the row.
  2949. FLBuilder._reorderNode(
  2950. row.attr('data-node'),
  2951. row.index()
  2952. );
  2953. }
  2954. // Revert the proxy to its parent.
  2955. $( '.fl-row-sortable-proxy', window.parent.document ).append( ui.item );
  2956. }
  2957. },
  2958. /**
  2959. * Adds a new row to the layout.
  2960. *
  2961. * @since 1.0
  2962. * @access private
  2963. * @method _addRow
  2964. * @param {String} cols The type of column layout to use.
  2965. * @param {Number} position The position of the new row.
  2966. * @param {String} module Optional. The node ID of an existing module to move to this row.
  2967. */
  2968. _addRow: function(cols, position, module)
  2969. {
  2970. FLBuilder._showNodeLoadingPlaceholder( $( FLBuilder._contentClass ), position );
  2971. FLBuilder._newRowPosition = position;
  2972. const actions = FL.Builder.data.getLayoutActions()
  2973. actions.addRow( cols, position, module )
  2974. },
  2975. /**
  2976. * Adds the HTML for a new row to the layout when the AJAX
  2977. * add operation is complete.
  2978. *
  2979. * @since 1.0
  2980. * @access private
  2981. * @method _addRowComplete
  2982. * @param {String} response The JSON response with the HTML for the new row.
  2983. */
  2984. _addRowComplete: function(response)
  2985. {
  2986. var data = 'object' === typeof response ? response : FLBuilder._jsonParse(response),
  2987. content = $(FLBuilder._contentClass),
  2988. rowId = $(data.html).data('node');
  2989. // Add new row info to the data.
  2990. data.nodeParent = content;
  2991. data.nodePosition = FLBuilder._newRowPosition;
  2992. // Render the layout.
  2993. FLBuilder._renderLayout( data, function(){
  2994. FLBuilder._removeNodeLoadingPlaceholder( $( '.fl-node-' + rowId ) );
  2995. FLBuilder.triggerHook( 'didAddRow', rowId );
  2996. } );
  2997. },
  2998. /**
  2999. * Callback for when the delete row button is clicked.
  3000. *
  3001. * @since 1.0
  3002. * @access private
  3003. * @method _deleteRowClicked
  3004. * @param {Object} e The event object.
  3005. */
  3006. _deleteRowClicked: function( e )
  3007. {
  3008. var id = $( e.target ).closest( '.fl-row' ).data( 'node' );
  3009. var actions = FL.Builder.getActions()
  3010. actions.deleteNode( id )
  3011. e.stopPropagation();
  3012. },
  3013. /**
  3014. * Deletes a row.
  3015. *
  3016. * @since 1.0
  3017. * @access private
  3018. * @method _deleteRow
  3019. * @param {Object} row A jQuery reference of the row to delete.
  3020. */
  3021. _deleteRow: function(row)
  3022. {
  3023. var nodeId = row.attr('data-node');
  3024. const actions = FL.Builder.data.getLayoutActions()
  3025. actions.deleteNode( nodeId )
  3026. row.empty();
  3027. row.remove();
  3028. FLBuilder._setupEmptyLayout();
  3029. FLBuilder._removeRowOverlays();
  3030. FLBuilder.triggerHook( 'didDeleteRow', nodeId );
  3031. },
  3032. /**
  3033. * Listen for duplicate click.
  3034. *
  3035. * @since 1.3.8
  3036. * @access private
  3037. * @method _rowCopyClicked
  3038. * @param {Object} e The event object.
  3039. */
  3040. _rowCopyClicked: function(e)
  3041. {
  3042. var id = $( this ).closest( '.fl-row' ).attr( 'data-node' );
  3043. FLBuilder._copyRow( id );
  3044. e.stopPropagation();
  3045. },
  3046. /**
  3047. * Copy settings of a row.
  3048. *
  3049. * @since 2.6
  3050. * @access private
  3051. * @method _rowCopySettingsClicked
  3052. */
  3053. _rowCopySettingsClicked: function () {
  3054. const menuEl = $(this);
  3055. const nodeId = menuEl.closest('.fl-row').data('node');
  3056. // bind copy to the el
  3057. FLBuilderSettingsCopyPaste._bindCopyToElement(menuEl, 'row', nodeId, true);
  3058. },
  3059. /**
  3060. * Paste settings of a row.
  3061. *
  3062. * @since 2.6
  3063. * @access private
  3064. * @method _rowPasteSettingsClicked
  3065. */
  3066. _rowPasteSettingsClicked: function () {
  3067. const menuEl = $(this);
  3068. const menuText = menuEl.text();
  3069. const nodeId = menuEl.closest('.fl-row').data('node');
  3070. const success = FLBuilderSettingsCopyPaste._importFromClipboard('row', nodeId);
  3071. if (!success) {
  3072. // set button text
  3073. menuEl.text(FLBuilderStrings.module_import.error);
  3074. // restore button text
  3075. setTimeout(() => {
  3076. menuEl.text(menuText)
  3077. }, 1000);
  3078. }
  3079. },
  3080. /**
  3081. * Duplicate a row.
  3082. *
  3083. * @since 2.5
  3084. * @access private
  3085. * @method _copyRow
  3086. * @param String id of node
  3087. * @return void
  3088. */
  3089. _copyRow: function( nodeId ) {
  3090. var row = FLBuilder._getJQueryElement( nodeId ),
  3091. clone = row.clone(),
  3092. form = $( '.fl-builder-settings[data-node]', window.parent.document ),
  3093. formNodeId = form.attr( 'data-node' ),
  3094. formNode = ( formNodeId === nodeId ) ? row : row.find( '[data-node="' + formNodeId + '"]' ),
  3095. settings = null;
  3096. if ( form.length && formNode.length ) {
  3097. settings = FLBuilder._getSettings( form );
  3098. FLBuilderSettingsConfig.nodes[ formNodeId ] = settings;
  3099. }
  3100. clone.addClass( 'fl-node-' + nodeId + '-clone fl-builder-node-clone' );
  3101. clone.find( '.fl-block-overlay' ).remove();
  3102. clone.removeAttr( 'data-node' );
  3103. row.after( clone );
  3104. FLBuilder._showNodeLoading( nodeId + '-clone' );
  3105. // Animate scroll to new element
  3106. const el = clone.get(0);
  3107. el.scrollIntoView( {
  3108. behavior: 'smooth',
  3109. block: 'center',
  3110. } );
  3111. const actions = FL.Builder.data.getLayoutActions()
  3112. const callback = function( response ) {
  3113. var data = FLBuilder._jsonParse( response );
  3114. data.nodeParent = $( FLBuilder._contentClass );
  3115. data.nodePosition = $( FLBuilder._contentClass + ' > .fl-row' ).index( clone );
  3116. data.duplicatedRow = nodeId;
  3117. data.onAddNewHTML = function() { clone.remove() };
  3118. FLBuilder._rowCopyComplete( data );
  3119. }
  3120. actions.copyRow( nodeId, settings, formNodeId, callback )
  3121. },
  3122. /**
  3123. * Callback for when a row has been duplicated.
  3124. *
  3125. * @since 1.7
  3126. * @access private
  3127. * @method _rowCopyComplete
  3128. * @param {Object} data
  3129. */
  3130. _rowCopyComplete: function( data )
  3131. {
  3132. FLBuilder._renderLayout( data, function() {
  3133. FLBuilder.triggerHook( 'didDuplicateRow', {
  3134. newNodeId : data.nodeId,
  3135. oldNodeId : data.duplicatedRow
  3136. } );
  3137. } );
  3138. },
  3139. /**
  3140. * Shows the settings lightbox and loads the row settings
  3141. * when the row settings button is clicked.
  3142. *
  3143. * @since 1.0
  3144. * @access private
  3145. * @method _rowSettingsClicked
  3146. */
  3147. _rowSettingsClicked: function( e )
  3148. {
  3149. var button = $( this),
  3150. nodeId = $( this ).closest( '.fl-row' ).attr( 'data-node' ),
  3151. global = button.closest( '.fl-block-overlay-global' ).length > 0;
  3152. const actions = FL.Builder.getActions()
  3153. actions.openSettings( nodeId )
  3154. e.stopPropagation()
  3155. },
  3156. /**
  3157. * Show settings for a row node
  3158. *
  3159. * @since 2.?
  3160. * @access private
  3161. * @method _showRowSettings
  3162. */
  3163. _showRowSettings: function( nodeId, global )
  3164. {
  3165. let win = null;
  3166. // If we're on a global row template page
  3167. if ( global && 'row' != FLBuilderConfig.userTemplateType ) {
  3168. if ( FLBuilderConfig.userCanEditGlobalTemplates ) {
  3169. win = window.parent.open( $( '.fl-row[data-node="' + nodeId + '"]' ).attr( 'data-template-url' ) );
  3170. win.FLBuilderGlobalNodeId = nodeId;
  3171. }
  3172. } else {
  3173. FLBuilderSettingsForms.render( {
  3174. id : 'row',
  3175. nodeId : nodeId,
  3176. className : 'fl-builder-row-settings',
  3177. attrs : 'data-node="' + nodeId + '"',
  3178. buttons : ! global && ! FLBuilderConfig.lite && ! FLBuilderConfig.simpleUi ? ['save-as'] : [],
  3179. badges : global ? [ FLBuilderStrings.global ] : [],
  3180. settings : FLBuilderSettingsConfig.nodes[ nodeId ],
  3181. preview : {
  3182. type: 'row'
  3183. }
  3184. }, function() {
  3185. $( '#fl-field-width select', window.parent.document ).on( 'change', FLBuilder._rowWidthChanged );
  3186. $( '#fl-field-content_width select', window.parent.document ).on( 'change', FLBuilder._rowWidthChanged );
  3187. } );
  3188. }
  3189. },
  3190. /**
  3191. * Shows or hides the row max-width setting when the
  3192. * row or row content width is changed.
  3193. *
  3194. * @since 2.0
  3195. * @access private
  3196. * @method _rowWidthChanged
  3197. */
  3198. _rowWidthChanged: function()
  3199. {
  3200. var rowWidth = $( '#fl-field-width select', window.parent.document ).val(),
  3201. contentWidth = $( '#fl-field-content_width select', window.parent.document ).val(),
  3202. maxWidth = $( '#fl-field-max_content_width', window.parent.document );
  3203. if ( 'fixed' == rowWidth || ( 'full' == rowWidth && 'fixed' == contentWidth ) ) {
  3204. maxWidth.show();
  3205. } else {
  3206. maxWidth.hide();
  3207. }
  3208. },
  3209. /**
  3210. * Resets the max-width of a row.
  3211. *
  3212. * @since 2.0
  3213. * @access private
  3214. * @method _resetRowWidthClicked
  3215. */
  3216. _resetRowWidthClicked: function( e )
  3217. {
  3218. var button = $( this ),
  3219. row = button.closest( '.fl-row' ),
  3220. nodeId = row.attr( 'data-node' ),
  3221. content = row.find( '.fl-row-content' ),
  3222. width = FLBuilderConfig.global.row_width + 'px',
  3223. settings = $( '.fl-builder-row-settings', window.parent.document );
  3224. if ( row.hasClass( 'fl-row-fixed-width' ) ) {
  3225. row.css( 'max-width', width );
  3226. }
  3227. content.css( 'max-width', width );
  3228. if ( settings.length ) {
  3229. settings.find( '[name=max_content_width]' ).val( '' );
  3230. }
  3231. const actions = FL.Builder.data.getLayoutActions()
  3232. actions.resetRowWidth( nodeId )
  3233. FLBuilder._closeAllSubmenus();
  3234. FLBuilder.triggerHook( 'didResetRowWidth', nodeId );
  3235. e.stopPropagation();
  3236. },
  3237. /* Columns
  3238. ----------------------------------------------------------*/
  3239. /**
  3240. * Adds a dashed border to empty columns.
  3241. *
  3242. * @since 1.0
  3243. * @access private
  3244. * @method _highlightEmptyCols
  3245. */
  3246. _highlightEmptyCols: function()
  3247. {
  3248. var notGlobal = FLBuilderConfig.userTemplateType ? '' : ':not(.fl-node-global)',
  3249. cols = $(FLBuilder._contentClass + ' .fl-col' + notGlobal),
  3250. modules = $( FLBuilder._contentClass + ' .fl-module[data-accepts]' + notGlobal );
  3251. cols.removeClass('fl-col-highlight').find('.fl-col-content').css( 'min-height', '' );
  3252. modules.removeClass('fl-module-highlight');
  3253. cols.each(function(){
  3254. var col = $(this);
  3255. if(!col.find('.fl-module, .fl-col').length && !col.closest('.fl-builder-shortcode-mask-wrap').length) {
  3256. col.addClass('fl-col-highlight');
  3257. }
  3258. });
  3259. modules.each(function(){
  3260. var module = $(this);
  3261. if(!module.find('.fl-module').length && !module.closest('.fl-builder-shortcode-mask-wrap').length) {
  3262. module.addClass('fl-module-highlight');
  3263. }
  3264. });
  3265. },
  3266. /**
  3267. * Sets up dashed borders to show where things can
  3268. * be dropped in rows and columns.
  3269. *
  3270. * @since 1.9
  3271. * @access private
  3272. * @method _highlightRowsAndColsForDrag
  3273. * @param {Object} target The event target for the drag.
  3274. */
  3275. _highlightRowsAndColsForDrag: function( target )
  3276. {
  3277. var notGlobal = 'row' == FLBuilderConfig.userTemplateType ? '' : ':not(.fl-node-global)';
  3278. // Do not highlight root parent column.
  3279. if ( 'column' == FLBuilderConfig.userTemplateType ) {
  3280. notGlobal = ':not(:first)';
  3281. }
  3282. // Highlight rows.
  3283. $( FLBuilder._contentClass + ' > .fl-row' ).addClass( 'fl-row-highlight' );
  3284. // Highlight columns.
  3285. if ( ! target || ! target.closest( '.fl-row-overlay' ).length ) {
  3286. $( FLBuilder._contentClass + ' .fl-col' + notGlobal ).each( function() {
  3287. if ( ! $( this ).closest( '.fl-builder-shortcode-mask-wrap' ).length ) {
  3288. $( this ).addClass( 'fl-col-highlight' );
  3289. }
  3290. } );
  3291. }
  3292. // Highlight container modules.
  3293. $( FLBuilder._contentClass + ' .fl-module[data-accepts]' ).each( function() {
  3294. if ( ! $( this ).closest( '.fl-builder-shortcode-mask-wrap' ).length ) {
  3295. $( this ).addClass( 'fl-module-highlight' );
  3296. }
  3297. } );
  3298. },
  3299. /**
  3300. * Remove any column highlights
  3301. *
  3302. * @since 2.0
  3303. * @access private
  3304. * @method _removeEmptyRowAndColHighlights
  3305. */
  3306. _removeEmptyRowAndColHighlights: function() {
  3307. $( '.fl-row-highlight' ).removeClass('fl-row-highlight');
  3308. $( '.fl-col-highlight' ).removeClass('fl-col-highlight');
  3309. $( '.fl-module-highlight' ).removeClass('fl-module-highlight');
  3310. $( '.fl-sortable-fixed-width' ).css( 'max-width', '' ).removeClass( 'fl-sortable-fixed-width' );
  3311. },
  3312. /**
  3313. * Adjust the height of columns with modules in them
  3314. * to account for the drop zone and keep the layout
  3315. * from jumping around.
  3316. *
  3317. * @since 1.9
  3318. * @access private
  3319. * @method _adjustColHeightsForDrag
  3320. */
  3321. _adjustColHeightsForDrag: function()
  3322. {
  3323. var notGlobalRow = 'row' == FLBuilderConfig.userTemplateType ? '' : '.fl-row:not(.fl-node-global) ',
  3324. notGlobalCol = 'column' == FLBuilderConfig.userTemplateType ? '' : '.fl-col:not(.fl-node-global) ',
  3325. content = $( FLBuilder._contentClass ),
  3326. notNested = content.find( notGlobalRow + '.fl-col-group:not(.fl-col-group-nested) > ' + notGlobalCol + '> .fl-col-content' ),
  3327. nested = content.find( notGlobalRow + '.fl-col-group-nested ' + notGlobalCol + '.fl-col-content' ),
  3328. col = null,
  3329. i = 0;
  3330. $( '.fl-node-drag-init' ).hide();
  3331. for ( ; i < nested.length; i++ ) {
  3332. if ( ! nested.eq( i ).closest( '.fl-builder-shortcode-mask-wrap' ).length ) {
  3333. FLBuilder._adjustColHeightForDrag( nested.eq( i ) );
  3334. }
  3335. }
  3336. for ( i = 0; i < notNested.length; i++ ) {
  3337. if ( ! notNested.eq( i ).closest( '.fl-builder-shortcode-mask-wrap' ).length ) {
  3338. FLBuilder._adjustColHeightForDrag( notNested.eq( i ) );
  3339. }
  3340. }
  3341. $( '.fl-node-drag-init' ).show();
  3342. },
  3343. /**
  3344. * Adjust the height of a single column for dragging.
  3345. *
  3346. * @since 1.9
  3347. * @access private
  3348. * @method _adjustColHeightForDrag
  3349. */
  3350. _adjustColHeightForDrag: function( col )
  3351. {
  3352. if ( col.find( '.fl-module:visible, .fl-col:visible' ).length ) {
  3353. col.css( 'min-height', col.height() + 45 );
  3354. }
  3355. },
  3356. /**
  3357. * Returns a helper element for column drag operations.
  3358. *
  3359. * @since 1.9
  3360. * @access private
  3361. * @method _colDragHelper
  3362. * @return {Object} The helper element.
  3363. */
  3364. _colDragHelper: function()
  3365. {
  3366. return $('<div class="fl-builder-block-drag-helper">' + FLBuilderStrings.column + '</div>');
  3367. },
  3368. /**
  3369. * Initializes dragging for columns. Columns themselves aren't sortables
  3370. * as nesting that many sortables breaks down quickly and draggable by
  3371. * itself is slow. Instead, we are programmatically triggering the drag
  3372. * of our helper div that isn't a nested sortable but connected to the
  3373. * sortables in the main layout.
  3374. *
  3375. * @since 1.9
  3376. * @access private
  3377. * @method _colDragInit
  3378. * @param {Object} e The event object.
  3379. */
  3380. _colDragInit: function( e )
  3381. {
  3382. var handle = $( e.target ),
  3383. helper = $( '.fl-col-sortable-proxy-item', window.parent.document ),
  3384. col = handle.closest( '.fl-col' );
  3385. if ( handle.closest( '.fl-block-move-menu' ).length ) {
  3386. return;
  3387. } else if ( handle.hasClass( 'fl-block-col-move-parent' ) ) {
  3388. col = col.parents( '.fl-col' );
  3389. } else if ( handle.data( 'target-node' ) ) {
  3390. col = $( '.fl-col[data-node=' + handle.data( 'target-node' ) + ']' );
  3391. }
  3392. col.addClass( 'fl-node-dragging' );
  3393. FLBuilder._blockDragInit( e );
  3394. e.target = helper[ 0 ];
  3395. helper.trigger( e );
  3396. },
  3397. /**
  3398. * @method _colDragInitTouch
  3399. * @param {Object} e The event object.
  3400. */
  3401. _colDragInitTouch: function( startEvent )
  3402. {
  3403. var handle = $( startEvent.target ),
  3404. helper = $( '.fl-col-sortable-proxy-item', window.parent.document ),
  3405. col = handle.closest( '.fl-col' ),
  3406. moved = false;
  3407. if ( handle.data( 'target-node' ) ) {
  3408. col = $( '.fl-col[data-node=' + handle.data( 'target-node' ) + ']' );
  3409. }
  3410. handle.on( 'touchmove', function( moveEvent ) {
  3411. if ( ! moved ) {
  3412. startEvent.currentTarget = col[0];
  3413. FLBuilder._colDragInit( startEvent );
  3414. moved = true;
  3415. }
  3416. moveEvent.target = helper[0];
  3417. helper.trigger( moveEvent );
  3418. } );
  3419. handle.on( 'touchend', function( endEvent ) {
  3420. endEvent.target = helper[0];
  3421. helper.trigger( endEvent );
  3422. endEvent.stopPropagation();
  3423. } );
  3424. },
  3425. /**
  3426. * Callback that fires when dragging starts for a column.
  3427. *
  3428. * @since 1.9
  3429. * @access private
  3430. * @method _colDragStart
  3431. * @param {Object} e The event object.
  3432. * @param {Object} ui An object with additional info for the drag.
  3433. */
  3434. _colDragStart: function( e, ui )
  3435. {
  3436. var col = $( '.fl-node-dragging' );
  3437. col.hide();
  3438. FLBuilder._resetColumnWidths( col.parent() );
  3439. FLBuilder._blockDragStart( e, ui );
  3440. },
  3441. /**
  3442. * Callback that fires when dragging stops for a column.
  3443. *
  3444. * @since 1.9
  3445. * @access private
  3446. * @method _colDragStop
  3447. * @param {Object} e The event object.
  3448. * @param {Object} ui An object with additional info for the drag.
  3449. */
  3450. _colDragStop: function( e, ui )
  3451. {
  3452. FLBuilder._blockDragStop( e, ui );
  3453. var col = $( '.fl-node-dragging' ).removeClass( 'fl-node-dragging' ).show(),
  3454. colId = col.attr( 'data-node' ),
  3455. newParent = ui.item.parent(),
  3456. oldGroup = col.parent(),
  3457. oldGroupId = oldGroup.attr( 'data-node' )
  3458. newGroup = newParent.closest( '.fl-col-group' ),
  3459. newGroupId = newGroup.attr( 'data-node' ),
  3460. newRow = newParent.closest('.fl-row'),
  3461. position = 0,
  3462. actions = FL.Builder.data.getLayoutActions();
  3463. // Cancel if a column was dropped into itself.
  3464. if ( newParent.closest( '[data-node="' + colId + '"]' ).length ) {
  3465. FLBuilder._resetColumnWidths( oldGroup );
  3466. }
  3467. // Cancel the drop if the sortable is disabled?
  3468. else if ( newParent.hasClass( 'fl-sortable-disabled' ) ) {
  3469. FLBuilder._resetColumnWidths( oldGroup );
  3470. }
  3471. // A column was dropped back into the sortable proxy.
  3472. else if ( newParent.hasClass( 'fl-col-sortable-proxy' ) ) {
  3473. FLBuilder._resetColumnWidths( oldGroup );
  3474. }
  3475. // A column was dropped into a column.
  3476. else if ( newParent.hasClass( 'fl-col-content' ) ) {
  3477. // Remove the column.
  3478. col.remove();
  3479. // Remove empty old groups (needs to be done here for correct position).
  3480. if ( 0 === oldGroup.find( '.fl-col' ).length ) {
  3481. actions.removeNode( oldGroup.attr( 'data-node' ) );
  3482. oldGroup.remove();
  3483. }
  3484. // Find the new group position.
  3485. position = newParent.find( '> .fl-module, .fl-col-group, .fl-col-sortable-proxy-item' ).index( ui.item );
  3486. // Add the new group.
  3487. FLBuilder._addColGroup( newParent.closest( '.fl-col' ).attr('data-node'), colId, position );
  3488. }
  3489. // A column was dropped into a column position.
  3490. else if ( newParent.hasClass( 'fl-col-drop-target' ) ) {
  3491. // Move the column in the UI.
  3492. if ( newParent.hasClass( 'fl-col-drop-target-last' ) ) {
  3493. newParent.parent().after( col );
  3494. }
  3495. else {
  3496. newParent.parent().before( col );
  3497. }
  3498. // Reset the UI column widths.
  3499. FLBuilder._resetColumnWidths( newGroup );
  3500. // Save the column move via AJAX.
  3501. const actions = FL.Builder.data.getLayoutActions()
  3502. if ( oldGroupId == newGroupId ) {
  3503. FL.Builder.getActions().moveNode( colId, col.index() )
  3504. }
  3505. else {
  3506. FL.Builder.getActions().moveNode( colId, col.index(), newGroupId, [ oldGroupId, newGroupId ] )
  3507. }
  3508. // Trigger a layout resize.
  3509. FLBuilder._resizeLayout();
  3510. }
  3511. // A column was dropped into a column group position.
  3512. else if ( newParent.hasClass( 'fl-col-group-drop-target' ) ) {
  3513. // Remove the column.
  3514. col.remove();
  3515. // Remove empty old groups (needs to be done here for correct position).
  3516. if ( 0 === oldGroup.find( '.fl-col' ).length ) {
  3517. actions.removeNode( oldGroup.attr( 'data-node' ) );
  3518. oldGroup.remove();
  3519. }
  3520. // Find the new group position.
  3521. position = newRow.find( '.fl-row-content' ).find( ' > .fl-col-group, > .fl-module' ).index( newGroup );
  3522. position = newParent.hasClass( 'fl-drop-target-last' ) ? position + 1 : position;
  3523. // Add the new group.
  3524. FLBuilder._addColGroup( newRow.attr('data-node'), colId, position );
  3525. }
  3526. // A column was dropped into a row position.
  3527. else if ( newParent.hasClass( 'fl-row-drop-target' ) ) {
  3528. // Remove the column.
  3529. col.remove();
  3530. // Find the new row position.
  3531. position = newParent.closest( '.fl-builder-content' ).find( '.fl-row' ).index( newRow );
  3532. position = newParent.hasClass( 'fl-drop-target-last' ) ? position + 1 : position;
  3533. // Add the new row.
  3534. FLBuilder._addRow( colId, position );
  3535. }
  3536. // Remove empty old groups.
  3537. if ( 0 === oldGroup.find( '.fl-col' ).length ) {
  3538. actions.removeNode( oldGroup.attr( 'data-node' ) );
  3539. oldGroup.remove();
  3540. }
  3541. // Revert the proxy to its parent.
  3542. $( '.fl-col-sortable-proxy', window.parent.document ).append( ui.item );
  3543. // Finish the drag.
  3544. FLBuilder._highlightEmptyCols();
  3545. FLBuilder._initDropTargets();
  3546. FLBuilder._initSortables();
  3547. FLBuilder._closeAllSubmenus();
  3548. },
  3549. /**
  3550. * Shows the settings lightbox and loads the column settings
  3551. * when the column settings button is clicked.
  3552. *
  3553. * @since 1.1.9
  3554. * @access private
  3555. * @method _colSettingsClicked
  3556. * @param {Object} e The event object.
  3557. */
  3558. _colSettingsClicked: function(e)
  3559. {
  3560. var button = $( this ),
  3561. col = button.closest('.fl-col'),
  3562. id = col.attr( 'data-node' ),
  3563. global = button.closest( '.fl-block-overlay-global' ).length > 0;
  3564. if ( FLBuilder._colResizing ) {
  3565. return;
  3566. }
  3567. if ( global && ! FLBuilderConfig.userCanEditGlobalTemplates ) {
  3568. return;
  3569. }
  3570. // If we clicked the edit parent button
  3571. if ( button.hasClass( 'fl-block-col-edit-parent' ) ) {
  3572. id = col.parents( '.fl-col' ).attr( 'data-node' )
  3573. }
  3574. const actions = FL.Builder.data.getLayoutActions()
  3575. actions.displaySettings( id )
  3576. e.stopPropagation();
  3577. },
  3578. /**
  3579. * Copy settings of a column.
  3580. *
  3581. * @since 2.6
  3582. * @access private
  3583. * @method _colCopySettingsClicked
  3584. */
  3585. _colCopySettingsClicked: function () {
  3586. const menuEl = $(this);
  3587. const nodeId = menuEl.closest('.fl-col').data('node');
  3588. // bind copy to the el
  3589. FLBuilderSettingsCopyPaste._bindCopyToElement(menuEl, 'column', nodeId);
  3590. },
  3591. /**
  3592. * Paste settings of a column.
  3593. *
  3594. * @since 2.6
  3595. * @access private
  3596. * @method _colPasteSettingsClicked
  3597. */
  3598. _colPasteSettingsClicked: function () {
  3599. const menuEl = $(this);
  3600. const menuText = menuEl.text();
  3601. const nodeId = menuEl.closest('.fl-col').data('node');
  3602. const success = FLBuilderSettingsCopyPaste._importFromClipboard('column', nodeId);
  3603. if (!success) {
  3604. // set button text
  3605. menuEl.text(FLBuilderStrings.module_import.error);
  3606. // restore button text
  3607. setTimeout(() => {
  3608. menuEl.text(menuText)
  3609. }, 1000);
  3610. }
  3611. },
  3612. /**
  3613. * Show Column Settings Form
  3614. *
  3615. * @since 2.?
  3616. * @access private
  3617. * @method _showColSettings
  3618. */
  3619. _showColSettings: function( nodeId, global, isNodeTemplate ) {
  3620. if ( global && isNodeTemplate && 'row' !== FLBuilderConfig.userTemplateType ) {
  3621. if ( FLBuilderConfig.userCanEditGlobalTemplates ) {
  3622. let win = window.parent.open( $( '.fl-col[data-node="' + nodeId + '"]' ).attr( 'data-template-url' ) );
  3623. win.FLBuilderGlobalNodeId = nodeId;
  3624. }
  3625. }
  3626. else {
  3627. FLBuilderSettingsForms.render( {
  3628. id : 'col',
  3629. nodeId : nodeId,
  3630. className : 'fl-builder-col-settings',
  3631. attrs : 'data-node="' + nodeId + '"',
  3632. buttons : ! global && ! FLBuilderConfig.lite && ! FLBuilderConfig.simpleUi ? ['save-as'] : [],
  3633. badges : global ? [ FLBuilderStrings.global ] : [],
  3634. settings : FLBuilderSettingsConfig.nodes[ nodeId ],
  3635. preview : {
  3636. type: 'col'
  3637. }
  3638. }, function() {
  3639. var col = $('.fl-col.fl-node-' + nodeId )
  3640. if ( col.siblings( '.fl-col' ).length === 0 ) {
  3641. $( '#fl-field-equal_height, #fl-field-content_alignment', window.parent.document ).hide();
  3642. }
  3643. } );
  3644. }
  3645. },
  3646. /**
  3647. * Callback for when the copy column button is clicked.
  3648. *
  3649. * @since 2.0
  3650. * @access private
  3651. * @method _copyColClicked
  3652. * @param {Object} e The event object.
  3653. */
  3654. _copyColClicked: function( e )
  3655. {
  3656. var id = $( this ).closest( '.fl-col' ).attr( 'data-node' );
  3657. FLBuilder._copyColumn( id );
  3658. e.stopPropagation();
  3659. },
  3660. /**
  3661. * Handle Column Duplication
  3662. *
  3663. * @since 2.5
  3664. * @access private
  3665. * @method _copyColumn
  3666. * @param String id of node
  3667. * @return void
  3668. */
  3669. _copyColumn: function( nodeId ) {
  3670. var col = FLBuilder._getJQueryElement( nodeId ),
  3671. clone = col.clone(),
  3672. group = col.parent(),
  3673. form = $( '.fl-builder-settings[data-node]', window.parent.document ),
  3674. formNodeId = form.attr( 'data-node' ),
  3675. formNode = ( formNodeId === nodeId ) ? col : col.find( '[data-node="' + formNodeId + '"]' ),
  3676. settings = null;
  3677. if ( form.length && formNode.length ) {
  3678. settings = FLBuilder._getSettings( form );
  3679. FLBuilderSettingsConfig.nodes[ formNodeId ] = settings;
  3680. }
  3681. clone.addClass( 'fl-node-' + nodeId + '-clone fl-builder-node-clone' );
  3682. clone.find( '.fl-block-overlay' ).remove();
  3683. clone.removeAttr( 'data-node' );
  3684. col.after( clone );
  3685. FLBuilder._showNodeLoading( nodeId + '-clone' );
  3686. FLBuilder._resetColumnWidths( group );
  3687. const actions = FL.Builder.data.getLayoutActions()
  3688. const callback = function( response ) {
  3689. var data = FLBuilder._jsonParse( response );
  3690. data.nodeParent = group;
  3691. data.nodePosition = clone.index();
  3692. data.duplicatedColumn = nodeId;
  3693. data.onAddNewHTML = function() { clone.remove() };
  3694. FLBuilder._copyColComplete( data );
  3695. }
  3696. actions.copyColumn( nodeId, settings, formNodeId, callback )
  3697. },
  3698. /**
  3699. * Callback for when a column has been duplicated.
  3700. *
  3701. * @since 2.0
  3702. * @access private
  3703. * @method _copyColComplete
  3704. * @param {Object} data
  3705. */
  3706. _copyColComplete: function( data )
  3707. {
  3708. FLBuilder._renderLayout( data, function(){
  3709. FLBuilder._resetColumnWidths( data.nodeParent );
  3710. FLBuilder.triggerHook( 'didDuplicateColumn', {
  3711. newNodeId : data.nodeId,
  3712. oldNodeId : data.duplicatedColumn
  3713. } );
  3714. } );
  3715. },
  3716. /**
  3717. * Callback for when the delete column button is clicked.
  3718. *
  3719. * @since 1.0
  3720. * @access private
  3721. * @method _deleteColClicked
  3722. * @param {Object} e The event object.
  3723. */
  3724. _deleteColClicked: function( e )
  3725. {
  3726. var id = $( e.target ).closest( '.fl-col' ).data( 'node' );
  3727. var actions = FL.Builder.getActions();
  3728. actions.deleteNode( id );
  3729. e.stopPropagation();
  3730. },
  3731. /**
  3732. * Handle selecting the proper dom node to delete in nested column situations.
  3733. */
  3734. _getColToDelete: function( initialCol ) {
  3735. var col = initialCol,
  3736. parentGroup = col.closest( '.fl-col-group' ),
  3737. parentCol = col.parents( '.fl-col' ),
  3738. hasParentCol = parentCol.length > 0,
  3739. parentChildren = parentCol.find( '> .fl-col-content > .fl-module, > .fl-col-content > .fl-col-group' ),
  3740. siblingCols = col.siblings( '.fl-col' );
  3741. // Handle deleting of nested columns.
  3742. if ( hasParentCol && 1 === parentChildren.length ) {
  3743. if ( 0 === siblingCols.length ) {
  3744. col = parentCol;
  3745. }
  3746. else if ( 1 === siblingCols.length && ! siblingCols.find( '.fl-module' ).length ) {
  3747. col = parentGroup;
  3748. }
  3749. }
  3750. return col
  3751. },
  3752. /**
  3753. * Deletes a column.
  3754. *
  3755. * @since 1.0
  3756. * @access private
  3757. * @method _deleteCol
  3758. * @param {Object} col A jQuery reference of the column to delete (can also be a group).
  3759. */
  3760. _deleteCol: function(col)
  3761. {
  3762. var nodeId = col.attr('data-node'),
  3763. row = col.closest('.fl-row'),
  3764. group = col.closest('.fl-col-group'),
  3765. cols = null,
  3766. width = 0;
  3767. col.remove();
  3768. rowCols = row.find('.fl-row-content > .fl-col-group > .fl-col, .fl-row-content > .fl-module');
  3769. groupCols = group.find(' > .fl-col');
  3770. if(0 === rowCols.length && 'row' != FLBuilderConfig.userTemplateType && 'column' != FLBuilderConfig.userTemplateType) {
  3771. FLBuilder._deleteRow(row);
  3772. }
  3773. else {
  3774. if(0 === groupCols.length) {
  3775. group.remove();
  3776. }
  3777. else {
  3778. if ( 6 === groupCols.length ) {
  3779. width = 16.65;
  3780. }
  3781. else if ( 7 === groupCols.length ) {
  3782. width = 14.28;
  3783. }
  3784. else {
  3785. width = Math.round( 100 / groupCols.length * 100 ) / 100;
  3786. }
  3787. groupCols.css('width', width + '%');
  3788. FLBuilder.triggerHook( 'didResetColumnWidths', {
  3789. cols : groupCols
  3790. } );
  3791. }
  3792. const actions = FL.Builder.data.getLayoutActions()
  3793. actions.deleteColumn( nodeId, width )
  3794. FLBuilder._initDropTargets();
  3795. FLBuilder._initSortables();
  3796. FLBuilder.triggerHook( 'col-deleted' );
  3797. FLBuilder.triggerHook( 'didDeleteColumn', nodeId );
  3798. }
  3799. },
  3800. /**
  3801. * Deletes a column group.
  3802. *
  3803. * @since 2.8
  3804. * @param {Object} group A jQuery reference of the group to delete.
  3805. */
  3806. _deleteColGroup: function( group )
  3807. {
  3808. var nodeId = group.attr( 'data-node' );
  3809. const actions = FL.Builder.data.getLayoutActions()
  3810. actions.deleteNode( nodeId )
  3811. group.empty();
  3812. group.remove();
  3813. FLBuilder.triggerHook( 'didDeleteColumnGroup', nodeId );
  3814. },
  3815. /**
  3816. * Inserts a column (or columns) before or after another column.
  3817. *
  3818. * @since 1.6.4
  3819. * @access private
  3820. * @method _addCols
  3821. * @param {Object|String} col A jQuery reference of the column to insert before or after.
  3822. * @param {String} insert Either before or after.
  3823. * @param {String} type The type of column(s) to insert.
  3824. * @param {Boolean} nested Whether these columns are nested or not.
  3825. * @param {String} module Optional. The node ID of an existing module to move to this group.
  3826. */
  3827. _addCols: function( col, insert, type, nested, module )
  3828. {
  3829. var col = 'string' === typeof col ? $( '.fl-node-' + col ) : col,
  3830. parent = col.closest( '.fl-col-group' ),
  3831. position = parent.find( '.fl-col' ).index( col ),
  3832. id = col.attr('data-node');
  3833. type = typeof type == 'undefined' ? '1-col' : type;
  3834. nested = typeof nested == 'undefined' ? false : nested;
  3835. nested = nested ? 1 : 0
  3836. if ( 'after' == insert ) {
  3837. position++;
  3838. }
  3839. FLBuilder._showNodeLoadingPlaceholder( parent, position );
  3840. FLBuilder._removeAllOverlays();
  3841. const actions = FL.Builder.data.getLayoutActions()
  3842. actions.addColumns( id, insert, type, nested, module )
  3843. },
  3844. /**
  3845. * Adds the HTML for columns to the layout when the AJAX add
  3846. * operation is complete. Adds a module if one is queued to
  3847. * go in a new column.
  3848. *
  3849. * @since 1.9
  3850. * @access private
  3851. * @method _addColsComplete
  3852. * @param {Object|String} response The JSON response with the HTML for the new column(s).
  3853. */
  3854. _addColsComplete: function( response )
  3855. {
  3856. var data = 'object' === typeof response ? response : FLBuilder._jsonParse( response ),
  3857. col = null;
  3858. data.nodeParent = FLBuilder._newColParent;
  3859. data.nodePosition = FLBuilder._newColPosition;
  3860. // Render the layout.
  3861. FLBuilder._renderLayout( data, function() {
  3862. FLBuilder._removeNodeLoadingPlaceholder( $( '.fl-node-' + data.nodeId ) );
  3863. FLBuilder.triggerHook( 'didAddColumn', data.nodeId );
  3864. FLBuilder.triggerHook( 'didResetColumnWidths', {
  3865. cols : $( '.fl-node-' + data.nodeId ).find( '> .fl-col' )
  3866. } );
  3867. } );
  3868. },
  3869. /**
  3870. * Adds a new column group to the layout.
  3871. *
  3872. * @since 1.0
  3873. * @access private
  3874. * @method _addColGroup
  3875. * @param {String} nodeId The node ID of the parent row.
  3876. * @param {String} cols The type of column layout to use.
  3877. * @param {Number} position The position of the new column group.
  3878. * @param {String} module Optional. The node ID of an existing module to move to this group.
  3879. */
  3880. _addColGroup: function( nodeId, cols, position, module )
  3881. {
  3882. var parent = $( '.fl-node-' + nodeId );
  3883. // Save the new column group info.
  3884. FLBuilder._newColGroupPosition = position;
  3885. if ( parent.hasClass( 'fl-col' ) ) {
  3886. FLBuilder._newColGroupParent = parent.find( ' > .fl-col-content' );
  3887. }
  3888. else {
  3889. FLBuilder._newColGroupParent = parent.find( '.fl-row-content' );
  3890. }
  3891. // Show the loader.
  3892. FLBuilder._showNodeLoadingPlaceholder( FLBuilder._newColGroupParent, position );
  3893. const actions = FL.Builder.data.getLayoutActions()
  3894. actions.addColumnGroup( nodeId, cols, position, module )
  3895. },
  3896. /**
  3897. * Adds the HTML for a new column group to the layout when
  3898. * the AJAX add operation is complete. Adds a module if one
  3899. * is queued to go in the new column group.
  3900. *
  3901. * @since 1.0
  3902. * @access private
  3903. * @method _addColGroupComplete
  3904. * @param {String} response The JSON response with the HTML for the new column group.
  3905. */
  3906. _addColGroupComplete: function(response)
  3907. {
  3908. var data = FLBuilder._jsonParse(response),
  3909. html = $(data.html),
  3910. groupId = html.data('node'),
  3911. colId = html.find('.fl-col').data('node');
  3912. // Add new column group info to the data.
  3913. data.nodeParent = FLBuilder._newColGroupParent;
  3914. data.nodePosition = FLBuilder._newColGroupPosition;
  3915. // Render the layout.
  3916. FLBuilder._renderLayout( data, function(){
  3917. // Added the nested columns class if needed.
  3918. if ( data.nodeParent.hasClass( 'fl-col-content' ) ) {
  3919. data.nodeParent.parents( '.fl-col' ).addClass( 'fl-col-has-cols' );
  3920. }
  3921. // Remove the loading placeholder.
  3922. FLBuilder._removeNodeLoadingPlaceholder( $( '.fl-node-' + groupId ) );
  3923. FLBuilder.triggerHook( 'didAddColumnGroup', groupId );
  3924. } );
  3925. },
  3926. /**
  3927. * Initializes draggables for column resizing.
  3928. *
  3929. * @since 1.6.4
  3930. * @access private
  3931. * @method _initColDragResizing
  3932. */
  3933. _initColDragResizing: function()
  3934. {
  3935. $( '.fl-block-col-resize' ).not( '.fl-block-row-resize' ).draggable( {
  3936. axis : 'x',
  3937. start : FLBuilder._colDragResizeStart,
  3938. drag : FLBuilder._colDragResize,
  3939. stop : FLBuilder._colDragResizeStop
  3940. } );
  3941. },
  3942. /**
  3943. * Fires when dragging for a column resize starts.
  3944. *
  3945. * @since 1.6.4
  3946. * @access private
  3947. * @method _colDragResizeStart
  3948. * @param {Object} e The event object.
  3949. * @param {Object} ui An object with additional info for the drag.
  3950. */
  3951. _colDragResizeStart: function( e, ui )
  3952. {
  3953. // Setup resize vars.
  3954. var handle = $( ui.helper ),
  3955. direction = '',
  3956. resizeParent = handle.hasClass( 'fl-block-col-resize-parent' ),
  3957. parentCol = resizeParent ? handle.closest( '.fl-col' ).parents( '.fl-col' ) : null,
  3958. group = resizeParent ? parentCol.parents( '.fl-col-group' ) : handle.closest( '.fl-col-group' ),
  3959. cols = group.find( '> .fl-col' ),
  3960. col = resizeParent ? parentCol : handle.closest( '.fl-col' ),
  3961. colId = col.attr( 'data-node' ),
  3962. colSetting = $( '[data-node=' + colId + '] #fl-field-size input', window.parent.document ),
  3963. sibling = null,
  3964. siblingId = null,
  3965. siblingSetting = null,
  3966. availWidth = 100,
  3967. i = 0,
  3968. setting = null,
  3969. settingType = null;
  3970. // Find the direction and sibling.
  3971. if ( handle.hasClass( 'fl-block-col-resize-e' ) ) {
  3972. direction = 'e';
  3973. sibling = col.nextAll( '.fl-col' ).first();
  3974. }
  3975. else {
  3976. direction = 'w';
  3977. sibling = col.prevAll( '.fl-col' ).first();
  3978. }
  3979. siblingId = sibling.attr( 'data-node' );
  3980. siblingSetting = $( '[data-node=' + siblingId + '] #fl-field-size input', window.parent.document );
  3981. // Find the available width.
  3982. for ( ; i < cols.length; i++ ) {
  3983. if ( cols.eq( i ).data( 'node' ) == col.data( 'node' ) ) {
  3984. continue;
  3985. }
  3986. if ( cols.eq( i ).data( 'node' ) == sibling.data( 'node' ) ) {
  3987. continue;
  3988. }
  3989. availWidth -= parseFloat( cols.eq( i )[ 0 ].style.width );
  3990. }
  3991. // Find the setting if a column form is open.
  3992. if ( colSetting.length ) {
  3993. setting = colSetting;
  3994. settingType = 'col';
  3995. } else if ( siblingSetting.length ) {
  3996. setting = siblingSetting;
  3997. settingType = 'sibling';
  3998. }
  3999. // Build the resize data object.
  4000. FLBuilder._colResizeData = {
  4001. handle : handle,
  4002. feedbackLeft : handle.find( '.fl-block-col-resize-feedback-left' ),
  4003. feedbackRight : handle.find( '.fl-block-col-resize-feedback-right' ),
  4004. direction : direction,
  4005. groupWidth : group.outerWidth(),
  4006. col : col,
  4007. id : col.attr( 'data-node' ),
  4008. colWidth : parseFloat( col[ 0 ].style.width ) / 100,
  4009. sibling : sibling,
  4010. siblingId : sibling.attr( 'data-node' ),
  4011. offset : ui.position.left,
  4012. availWidth : availWidth,
  4013. setting : setting,
  4014. settingType : settingType,
  4015. layoutActions : FL.Builder.data.getLayoutActions()
  4016. };
  4017. // Set the resizing flag.
  4018. FLBuilder._colResizing = true;
  4019. // Add the body col resize class.
  4020. $( 'body' ).addClass( 'fl-builder-col-resizing' );
  4021. // Close the builder panel and destroy overlay events.
  4022. FLBuilder._closePanel();
  4023. FLBuilder._destroyOverlayEvents();
  4024. // Trigger the col-resize-start hook.
  4025. FLBuilder.triggerHook( 'col-resize-start' );
  4026. },
  4027. /**
  4028. * Fires when dragging for a column resize is in progress.
  4029. *
  4030. * @since 1.6.4
  4031. * @access private
  4032. * @method _colDragResize
  4033. * @param {Object} e The event object.
  4034. * @param {Object} ui An object with additional info for the drag.
  4035. */
  4036. _colDragResize: function( e, ui )
  4037. {
  4038. // Setup resize vars.
  4039. var data = FLBuilder._colResizeData,
  4040. directionRef = FLBuilderConfig.isRtl ? 'w' : 'e',
  4041. overlay = data.handle.closest( '.fl-block-overlay' ),
  4042. change = ( data.offset - ui.position.left ) / data.groupWidth,
  4043. colWidth = directionRef == data.direction ? ( data.colWidth - change ) * 100 : ( data.colWidth + change ) * 100,
  4044. colRound = Math.round( colWidth * 100 ) / 100,
  4045. siblingWidth = data.availWidth - colWidth,
  4046. siblingRound = Math.round( siblingWidth * 100 ) / 100,
  4047. minRound = 8,
  4048. maxRound = Math.round( ( data.availWidth - minRound ) * 100 ) / 100;
  4049. // Set the min/max width if needed.
  4050. if ( colRound < minRound ) {
  4051. colRound = minRound;
  4052. siblingRound = maxRound;
  4053. }
  4054. else if ( siblingRound < minRound ) {
  4055. colRound = maxRound;
  4056. siblingRound = minRound;
  4057. }
  4058. // Show the feedback divs. Race condition with requestAnimationFrame
  4059. // and drag stop requires it to be done here.
  4060. data.feedbackLeft.show();
  4061. data.feedbackRight.show();
  4062. requestAnimationFrame( () => {
  4063. // rapid DOM manipulations should generally happen inside a requestAnimationFrame
  4064. // Set the feedback values.
  4065. if ( directionRef == data.direction ) {
  4066. data.feedbackLeft.html( colRound.toFixed( 1 ) + '%' );
  4067. data.feedbackRight.html( siblingRound.toFixed( 1 ) + '%' );
  4068. }
  4069. else {
  4070. data.feedbackLeft.html( siblingRound.toFixed( 1 ) + '%' );
  4071. data.feedbackRight.html( colRound.toFixed( 1 ) + '%' );
  4072. }
  4073. // Set the width attributes.
  4074. data.col.css( 'width', colRound + '%' );
  4075. data.sibling.css( 'width', siblingRound + '%' );
  4076. // Update the setting if the col or sibling's settings are open.
  4077. if ( data.setting ) {
  4078. if ( 'col' === data.settingType ) {
  4079. data.setting.val( parseFloat( data.col[ 0 ].style.width ) );
  4080. } else if ( 'sibling' === data.settingType ) {
  4081. data.setting.val( parseFloat( data.sibling[ 0 ].style.width ) );
  4082. }
  4083. }
  4084. // Dispatch to store
  4085. data.layoutActions.resizeColumn( data.id, colRound, data.siblingId, siblingRound, false );
  4086. // Build the overlay overflow menu if needed.
  4087. FLBuilder._buildOverlayOverflowMenu( overlay );
  4088. } )
  4089. // Trigger the col-resize-drag hook.
  4090. FLBuilder.triggerHook( 'col-resize-drag' );
  4091. },
  4092. /**
  4093. * Fires when dragging for a column resize stops.
  4094. *
  4095. * @since 1.6.4
  4096. * @access private
  4097. * @method _colDragResizeStop
  4098. * @param {Object} e The event object.
  4099. * @param {Object} ui An object with additional info for the drag.
  4100. */
  4101. _colDragResizeStop: function( e, ui )
  4102. {
  4103. var data = FLBuilder._colResizeData,
  4104. overlay = FLBuilder._colResizeData.handle.closest( '.fl-block-overlay' ),
  4105. colId = data.id,
  4106. colWidth = parseFloat( data.col[ 0 ].style.width ),
  4107. siblingId = data.sibling.data( 'node' ),
  4108. siblingWidth = parseFloat( data.sibling[ 0 ].style.width );
  4109. // Hide the feedback divs.
  4110. FLBuilder._colResizeData.feedbackLeft.hide();
  4111. FLBuilder._colResizeData.feedbackRight.hide();
  4112. // Update layout store
  4113. const actions = FL.Builder.data.getLayoutActions()
  4114. actions.resizeColumn( colId, colWidth, siblingId, siblingWidth )
  4115. // Build the overlay overflow menu if needed.
  4116. FLBuilder._buildOverlayOverflowMenu( overlay );
  4117. // Reset the resize data.
  4118. FLBuilder._colResizeData = null;
  4119. // Remove the body col resize class.
  4120. $( 'body' ).removeClass( 'fl-builder-col-resizing' );
  4121. // Rebind overlay events.
  4122. FLBuilder._bindOverlayEvents();
  4123. // Set the resizing flag to false with a timeout so other events get the right value.
  4124. setTimeout( function() { FLBuilder._colResizing = false; }, 50 );
  4125. // Trigger the col-resize-stop hook.
  4126. FLBuilder.triggerHook( 'col-resize-stop' );
  4127. FLBuilder.triggerHook( 'didResizeColumn', {
  4128. colId : colId,
  4129. colWidth : colWidth,
  4130. siblingId : siblingId,
  4131. siblingWidth : siblingWidth
  4132. } );
  4133. },
  4134. /**
  4135. * Resets the widths of all columns in a row when the
  4136. * Reset Column Widths button is clicked.
  4137. *
  4138. * @since 1.6.4
  4139. * @access private
  4140. * @method _resetColumnWidthsClicked
  4141. * @param {Object} e The event object.
  4142. */
  4143. _resetColumnWidthsClicked: function( e )
  4144. {
  4145. var button = $( this ),
  4146. isRow = !! button.closest( '.fl-row-overlay' ).length,
  4147. group = null,
  4148. groups = null,
  4149. groupIds = [],
  4150. children = null,
  4151. i = 0,
  4152. settings = $( '.fl-builder-col-settings', window.parent.document ),
  4153. col = null;
  4154. if ( isRow ) {
  4155. groups = button.closest( '.fl-row' ).find( '.fl-row-content > .fl-col-group' );
  4156. } else {
  4157. groups = button.parents( '.fl-col-group' ).last();
  4158. }
  4159. groups.each( function() {
  4160. group = $( this );
  4161. children = group.find( '.fl-col-group' );
  4162. groupIds.push( group.data( 'node' ) );
  4163. FLBuilder._resetColumnWidths( group );
  4164. for ( i = 0; i < children.length; i++ ) {
  4165. FLBuilder._resetColumnWidths( children.eq( i ) );
  4166. groupIds.push( children.eq( i ).data( 'node' ) );
  4167. }
  4168. } );
  4169. if ( settings.length ) {
  4170. col = $( '.fl-node-' + settings.attr( 'data-node' ) );
  4171. settings.find( '#fl-field-size input' ).val( parseFloat( col[ 0 ].style.width ) );
  4172. }
  4173. const actions = FL.Builder.data.getLayoutActions()
  4174. actions.resetColWidths( groupIds )
  4175. FLBuilder.triggerHook( 'col-reset-widths' );
  4176. FLBuilder._closeAllSubmenus();
  4177. e.stopPropagation();
  4178. },
  4179. /**
  4180. * Resets the widths of all columns in a group.
  4181. *
  4182. * @since 1.6.4
  4183. * @access private
  4184. * @method _resetColumnWidths
  4185. * @param {Object} jQuery|HTMLElement - the column group node.
  4186. */
  4187. _resetColumnWidths: function( group )
  4188. {
  4189. // Check jQuery object first. This allows passing a plain HTMLElement in.
  4190. var isJQueryObject = group instanceof jQuery
  4191. var colGroup = group
  4192. if ( ! isJQueryObject ){
  4193. colGroup = $( group );
  4194. }
  4195. var cols = colGroup.find( ' > .fl-col:visible' ),
  4196. width = 0;
  4197. if ( 6 === cols.length ) {
  4198. width = 16.65;
  4199. }
  4200. else if ( 7 === cols.length ) {
  4201. width = 14.28;
  4202. }
  4203. else {
  4204. width = Math.round( 100 / cols.length * 100 ) / 100;
  4205. }
  4206. cols.css( 'width', width + '%' );
  4207. FLBuilder.triggerHook( 'didResetColumnWidths', {
  4208. cols : cols
  4209. } );
  4210. },
  4211. /* Modules
  4212. ----------------------------------------------------------*/
  4213. /**
  4214. * Returns a helper element for module drag operations.
  4215. *
  4216. * @since 1.0
  4217. * @access private
  4218. * @method _moduleDragHelper
  4219. * @param {Object} e The event object.
  4220. * @param {Object} item The element being dragged.
  4221. * @return {Object} The helper element.
  4222. */
  4223. _moduleDragHelper: function(e, item)
  4224. {
  4225. var module = $( '.fl-node-' + item.data( 'node' ) )
  4226. return $('<div class="fl-builder-block-drag-helper">' + module.attr('data-name') + '</div>');
  4227. },
  4228. /**
  4229. * @method _moduleDragInit
  4230. * @param {Object} e The event object.
  4231. */
  4232. _moduleDragInit: function( e )
  4233. {
  4234. var handle = $( e.target ),
  4235. helper = $( '.fl-module-sortable-proxy-item', window.parent.document ),
  4236. module = handle.closest( '.fl-module' );
  4237. if ( handle.closest( '.fl-block-move-menu' ).length ) {
  4238. return;
  4239. } else if ( handle.data( 'target-node' ) ) {
  4240. module = $( '.fl-module[data-node=' + handle.data( 'target-node' ) + ']' );
  4241. }
  4242. helper.data( 'type', module.data('type' ) );
  4243. module.addClass( 'fl-node-dragging' );
  4244. FLBuilder._blockDragInit( e );
  4245. e.target = helper[ 0 ];
  4246. helper.data( 'node', module.data( 'node' ) )
  4247. helper.data( 'name', module.data( 'name' ) )
  4248. helper.data( 'type', module.data('type' ) )
  4249. helper.trigger( e );
  4250. },
  4251. /**
  4252. * @method _moduleDragInitTouch
  4253. * @param {Object} e The event object.
  4254. */
  4255. _moduleDragInitTouch: function( startEvent )
  4256. {
  4257. var handle = $( startEvent.target ),
  4258. helper = $( '.fl-module-sortable-proxy-item', window.parent.document ),
  4259. module = handle.closest( '.fl-module' ),
  4260. moved = false;
  4261. if ( handle.data( 'target-node' ) ) {
  4262. module = $( '.fl-module[data-node=' + handle.data( 'target-node' ) + ']' );
  4263. }
  4264. handle.on( 'touchmove', function( moveEvent ) {
  4265. if ( ! moved ) {
  4266. startEvent.currentTarget = module[0];
  4267. FLBuilder._moduleDragInit( startEvent );
  4268. moved = true;
  4269. }
  4270. moveEvent.target = helper[0];
  4271. helper.trigger( moveEvent );
  4272. } );
  4273. handle.on( 'touchend', function( endEvent ) {
  4274. endEvent.target = helper[0];
  4275. helper.trigger( endEvent );
  4276. endEvent.stopPropagation();
  4277. } );
  4278. },
  4279. /**
  4280. * Callback that fires when dragging starts for a module.
  4281. *
  4282. * @since 1.9
  4283. * @access private
  4284. * @method _moduleDragStart
  4285. * @param {Object} e The event object.
  4286. * @param {Object} ui An object with additional info for the drag.
  4287. */
  4288. _moduleDragStart: function( e, ui )
  4289. {
  4290. var module = $( '.fl-node-dragging' );
  4291. module.hide();
  4292. FLBuilder._removeRowOverlays();
  4293. FLBuilder._blockDragStart( e, ui );
  4294. },
  4295. /**
  4296. * Callback for when a module drag operation completes.
  4297. *
  4298. * @since 1.0
  4299. * @access private
  4300. * @method _moduleDragStop
  4301. * @param {Object} e The event object.
  4302. * @param {Object} ui An object with additional info for the drag.
  4303. */
  4304. _moduleDragStop: function(e, ui)
  4305. {
  4306. FLBuilder._blockDragStop( e, ui );
  4307. var module = $( '.fl-node-dragging' ).removeClass( 'fl-node-dragging' ),
  4308. item = ui.item,
  4309. parent = ui.item.parent(),
  4310. node = null,
  4311. position = 0,
  4312. parentId = 0,
  4313. cols = null;
  4314. // A module was dropped back into the module list.
  4315. if ( parent.hasClass( 'fl-builder-modules' ) || parent.hasClass( 'fl-builder-widgets' ) ) {
  4316. item.remove();
  4317. return;
  4318. }
  4319. // A new module was dropped.
  4320. else if ( item.hasClass( 'fl-builder-block' ) ) {
  4321. // Cancel the drop if the sortable is disabled?
  4322. if ( parent.hasClass( 'fl-sortable-disabled' ) ) {
  4323. item.remove();
  4324. FLBuilder._showPanel();
  4325. return;
  4326. }
  4327. // A new module was dropped into a row position.
  4328. else if ( parent.hasClass( 'fl-row-drop-target' ) ) {
  4329. parent = item.closest('.fl-builder-content');
  4330. parentId = 0;
  4331. node = item.closest('.fl-row');
  4332. position = parent.find( '.fl-row' ).index( node );
  4333. }
  4334. // A new module was dropped into a column group position.
  4335. else if ( parent.hasClass( 'fl-col-group-drop-target' ) ) {
  4336. parent = item.closest( '.fl-row-content' );
  4337. parentId = parent.closest( '.fl-row' ).attr( 'data-node' );
  4338. node = item.closest( '.fl-col-group, .fl-module' );
  4339. position = parent.find( '> .fl-col-group, > .fl-module' ).index( node );
  4340. }
  4341. // A new module was dropped into a column position.
  4342. else if ( parent.hasClass( 'fl-col-drop-target' ) ) {
  4343. parent = item.closest( '.fl-col-group' );
  4344. parentId = parent.attr( 'data-node' );
  4345. node = item.closest( '.fl-col' );
  4346. position = parent.find( ' > .fl-col' ).index( node );
  4347. }
  4348. // A new module was dropped into a container module WITHOUT a wrapper.
  4349. else if ( parent.hasClass( 'fl-module' ) ) {
  4350. var layoutDirection = FLBuilder._getNodeLayoutDirection( item );
  4351. if ( 'layered' === layoutDirection ) {
  4352. // Drop as top most item in layers.
  4353. position = parent.find( '> .fl-module' ).length;
  4354. } else {
  4355. position = parent.find( '> .fl-module, .fl-builder-block' ).index( item );
  4356. }
  4357. parentId = parent.attr( 'data-node' );
  4358. }
  4359. // A new module was dropped into a container module WITH a wrapper.
  4360. else if ( parent.hasClass( 'fl-module-content' ) ) {
  4361. position = parent.find( '> .fl-module, .fl-builder-block' ).index( item );
  4362. parentId = item.closest( '.fl-module' ).attr( 'data-node' );
  4363. }
  4364. // A new module was dropped into a column.
  4365. else {
  4366. position = parent.find( '> .fl-module, .fl-col-group, .fl-builder-block' ).index( item );
  4367. parentId = item.closest( '.fl-col' ).attr( 'data-node' );
  4368. }
  4369. // Increment the position?
  4370. if ( item.closest( '.fl-drop-target-last' ).length ) {
  4371. position += 1;
  4372. }
  4373. // Add the new module.
  4374. FLBuilder._addModule( parent, parentId, item.attr( 'data-type' ), position, item.attr( 'data-widget' ), item.attr( 'data-alias' ) );
  4375. // Remove the drag helper.
  4376. item.remove();
  4377. }
  4378. // An existing module was dropped.
  4379. else {
  4380. // Cancel the drop if the sortable is disabled?
  4381. if ( parent.hasClass( 'fl-sortable-disabled' ) ) {
  4382. FLBuilder._highlightEmptyCols();
  4383. module.show();
  4384. }
  4385. // A module was dropped into a row position.
  4386. else if ( parent.hasClass( 'fl-row-drop-target' ) ) {
  4387. node = item.closest( '.fl-row' );
  4388. position = item.closest( '.fl-builder-content' ).children( '.fl-row' ).index( node );
  4389. position = item.closest( '.fl-drop-target-last' ).length ? position + 1 : position;
  4390. cols = module.attr( 'data-accepts' ) ? null : '1-col';
  4391. FLBuilder._addRow( cols, position, module.attr( 'data-node' ) );
  4392. module.remove();
  4393. }
  4394. // A module was dropped into a column group position.
  4395. else if ( parent.hasClass( 'fl-col-group-drop-target' ) ) {
  4396. node = item.closest( '.fl-col-group' );
  4397. position = item.closest( '.fl-row-content ').find( ' > .fl-col-group' ).index( node );
  4398. position = item.closest( '.fl-drop-target-last' ).length ? position + 1 : position;
  4399. parentId = item.closest( '.fl-row' ).attr( 'data-node' );
  4400. if ( module.attr( 'data-accepts' ) ) {
  4401. FLBuilder._moveNode( parentId, module.attr( 'data-node' ), position );
  4402. module.show();
  4403. } else {
  4404. FLBuilder._addColGroup( parentId, '1-col', position, module.attr( 'data-node' ) );
  4405. module.remove();
  4406. }
  4407. }
  4408. // A module was dropped into a column position.
  4409. else if ( parent.hasClass( 'fl-col-drop-target' ) ) {
  4410. node = item.closest( '.fl-col' );
  4411. position = item.closest( '.fl-col-drop-target-last' ).length ? 'after' : 'before';
  4412. FLBuilder._addCols( node, position, '1-col', item.closest( '.fl-col-group-nested' ).length > 0, module.attr( 'data-node' ) );
  4413. module.remove();
  4414. }
  4415. // A module was dropped into another column or container module.
  4416. else {
  4417. var isContainer = item.closest( '[data-accepts]' ).length;
  4418. var layoutDirection = FLBuilder._getNodeLayoutDirection( item );
  4419. if ( isContainer && 'layered' === layoutDirection ) {
  4420. // Drop as top most item in layers.
  4421. item.parent().append( module );
  4422. } else {
  4423. item.after( module );
  4424. }
  4425. item.remove();
  4426. module.show();
  4427. FLBuilder._reorderModule( module );
  4428. }
  4429. // Revert the proxy to its parent.
  4430. $( '.fl-module-sortable-proxy', window.parent.document ).append( item );
  4431. }
  4432. FLBuilder._resizeLayout();
  4433. FLBuilder._initDropTargets();
  4434. },
  4435. /**
  4436. * Reorders a module within a column.
  4437. *
  4438. * @since 1.0
  4439. * @access private
  4440. * @method _reorderModule
  4441. * @param {Object} module The module element being dragged.
  4442. */
  4443. _reorderModule: function(module)
  4444. {
  4445. var newParent = module.parents('.fl-row, .fl-col, .fl-module').eq(0).attr('data-node'),
  4446. oldParent = module.attr('data-parent'),
  4447. nodeId = module.attr('data-node'),
  4448. position = module.index();
  4449. if(newParent == oldParent) {
  4450. FLBuilder._reorderNode( nodeId, position );
  4451. }
  4452. else {
  4453. module.attr('data-parent', newParent);
  4454. FLBuilder._moveNode( newParent, nodeId, position );
  4455. }
  4456. },
  4457. /**
  4458. * Callback for when the delete module button is clicked.
  4459. *
  4460. * @since 1.0
  4461. * @access private
  4462. * @method _deleteModuleClicked
  4463. * @param {Object} e The event object.
  4464. */
  4465. _deleteModuleClicked: function(e)
  4466. {
  4467. var id = $( e.target ).closest('.fl-module').data( 'node' );
  4468. var actions = FL.Builder.getActions()
  4469. actions.deleteNode( id )
  4470. e.stopPropagation();
  4471. },
  4472. /**
  4473. * Deletes a module.
  4474. *
  4475. * @since 1.0
  4476. * @access private
  4477. * @method _deleteModule
  4478. * @param {Object} module A jQuery reference of the module to delete.
  4479. */
  4480. _deleteModule: function(module)
  4481. {
  4482. var row = module.closest('.fl-row'),
  4483. rowNodes = row.find( '[data-node]' ),
  4484. nodeId = module.attr('data-node'),
  4485. acceptsChildren = module.attr('data-accepts');
  4486. const actions = FL.Builder.data.getLayoutActions()
  4487. actions.deleteNode( nodeId )
  4488. // Delete the row if this is the last node in it.
  4489. if ( acceptsChildren && 1 === rowNodes.length ) {
  4490. FLBuilder._deleteRow( row );
  4491. }
  4492. module.empty();
  4493. module.remove();
  4494. row.removeClass('fl-block-overlay-muted');
  4495. FLBuilder._highlightEmptyCols();
  4496. FLBuilder._removeAllOverlays();
  4497. FLBuilder.triggerHook( 'didDeleteModule', {
  4498. nodeId: nodeId,
  4499. moduleType: module.attr( 'data-type' ),
  4500. } );
  4501. },
  4502. /**
  4503. * Duplicates a module.
  4504. *
  4505. * @since 1.0
  4506. * @access private
  4507. * @method _moduleCopyClicked
  4508. * @param {Object} e The event object.
  4509. */
  4510. _moduleCopyClicked: function(e)
  4511. {
  4512. const id = $( this ).closest( '.fl-module' ).data( 'node' );
  4513. const actions = FL.Builder.getActions()
  4514. actions.copyNode( id );
  4515. e.stopPropagation();
  4516. },
  4517. /**
  4518. * Duplicate a module for a given id.
  4519. */
  4520. _copyModule: function( id )
  4521. {
  4522. var module = FLBuilder._getJQueryElement( id ),
  4523. clone = module.clone(),
  4524. parent = module.parent(),
  4525. form = $( '.fl-builder-module-settings[data-node=' + id + ']', window.parent.document ),
  4526. settings = {};
  4527. if ( form.length ) {
  4528. settings = FLBuilder._getSettings( form );
  4529. FLBuilderSettingsConfig.nodes[ id ] = settings;
  4530. }
  4531. // Setup clone
  4532. clone.addClass( 'fl-node-' + id + '-clone fl-builder-node-clone' );
  4533. clone.find( '.fl-block-overlay' ).remove();
  4534. clone.removeAttr( 'data-node' );
  4535. module.after( clone );
  4536. // Show Loader
  4537. FLBuilder._showNodeLoading( id + '-clone' );
  4538. // Animate scroll to new element
  4539. const el = clone.get(0);
  4540. el.scrollIntoView( {
  4541. behavior: 'smooth',
  4542. block: 'center',
  4543. } );
  4544. const actions = FL.Builder.data.getLayoutActions()
  4545. const callback = function( response ) {
  4546. var data = FLBuilder._jsonParse( response );
  4547. data.nodeParent = parent;
  4548. data.nodePosition = parent.find( ' > .fl-col-group, > .fl-module' ).index( clone );
  4549. data.duplicatedModule = id;
  4550. data.onAddNewHTML = function() { clone.remove() };
  4551. FLBuilder._moduleCopyComplete( data );
  4552. }
  4553. actions.copyModule( id, settings, callback )
  4554. },
  4555. /**
  4556. * Callback for when a module has been duplicated.
  4557. *
  4558. * @since 1.7
  4559. * @access private
  4560. * @method _moduleCopyComplete
  4561. * @param {Object}
  4562. */
  4563. _moduleCopyComplete: function( data )
  4564. {
  4565. FLBuilder._renderLayout( data, function(){
  4566. FLBuilder.triggerHook( 'didDuplicateModule', {
  4567. newNodeId : data.nodeId,
  4568. oldNodeId : data.duplicatedModule,
  4569. moduleType : data.moduleType,
  4570. } );
  4571. } );
  4572. },
  4573. /**
  4574. * Shows the settings lightbox and loads the module settings
  4575. * when the module settings button is clicked.
  4576. *
  4577. * @since 1.0
  4578. * @access private
  4579. * @method _moduleSettingsClicked
  4580. * @param {Object} e The event object.
  4581. */
  4582. _moduleSettingsClicked: function(e)
  4583. {
  4584. var button = $( this ),
  4585. overlay = button.closest( '.fl-block-overlay' ),
  4586. type = button.closest( '.fl-module' ).attr( 'data-type' ),
  4587. nodeId = button.closest( '.fl-module' ).attr( 'data-node' ),
  4588. parentId = button.closest( '.fl-col' ).attr( 'data-node' ),
  4589. global = button.closest( '.fl-block-overlay-global' ).length > 0;
  4590. e.stopPropagation();
  4591. if ( FLBuilder._colResizing ) {
  4592. return;
  4593. }
  4594. if ( global && ! FLBuilderConfig.userCanEditGlobalTemplates ) {
  4595. return;
  4596. }
  4597. // Show module settings
  4598. const actions = FL.Builder.getActions();
  4599. actions.openSettings( nodeId );
  4600. },
  4601. /**
  4602. * Copy settings of a module.
  4603. *
  4604. * @since 2.6
  4605. * @access private
  4606. * @method _moduleCopySettingsClicked
  4607. */
  4608. _moduleCopySettingsClicked: function () {
  4609. const menuEl = $(this);
  4610. const nodeId = menuEl.closest('.fl-module').data('node');
  4611. const type = menuEl.closest('.fl-module').data('type');
  4612. // bind copy to the el
  4613. FLBuilderSettingsCopyPaste._bindCopyToElement(menuEl, type, nodeId);
  4614. },
  4615. /**
  4616. * Copy settings of a module.
  4617. *
  4618. * @since 2.6
  4619. * @access private
  4620. * @method _modulePasteSettingsClicked
  4621. */
  4622. _modulePasteSettingsClicked: function () {
  4623. const menuEl = $(this);
  4624. const menuText = menuEl.text();
  4625. const nodeId = menuEl.closest('.fl-module').data('node');
  4626. const type = menuEl.closest('.fl-module').data('type');
  4627. const success = FLBuilderSettingsCopyPaste._importFromClipboard(type, nodeId);
  4628. if (!success) {
  4629. // set button text
  4630. menuEl.text(FLBuilderStrings.module_import.error);
  4631. // restore button text
  4632. setTimeout(() => {
  4633. menuEl.text(menuText)
  4634. }, 1000);
  4635. }
  4636. },
  4637. /**
  4638. * Shows the lightbox and loads the settings for a module.
  4639. *
  4640. * @since 1.0
  4641. * @access private
  4642. * @method _showModuleSettings
  4643. * @param {Object} data
  4644. * @param {Function} callback
  4645. */
  4646. _showModuleSettings: function( data, callback )
  4647. {
  4648. if ( ! FLBuilderSettingsConfig.modules ) {
  4649. return;
  4650. }
  4651. var config = FLBuilderSettingsConfig.modules[ data.type ],
  4652. settings = data.settings ? data.settings : FLBuilderSettingsConfig.nodes[ data.nodeId ],
  4653. head = $( 'head', window.parent.document ),
  4654. module = $( '.fl-module[data-node="' + data.nodeId + '"]' ),
  4655. layout = null;
  4656. if ( data.global && ! FLBuilderConfig.userTemplateType && module.attr( 'data-accepts' ) ) {
  4657. if ( FLBuilderConfig.userCanEditGlobalTemplates ) {
  4658. win = window.parent.open( module.attr( 'data-template-url' ) );
  4659. win.FLBuilderGlobalNodeId = data.nodeId;
  4660. }
  4661. } else {
  4662. // Add settings CSS and JS.
  4663. if ( -1 === $.inArray( data.type, FLBuilder._loadedModuleAssets ) ) {
  4664. if ( '' !== config.assets.css ) {
  4665. head.append( config.assets.css );
  4666. }
  4667. if ( '' !== config.assets.js ) {
  4668. head.append( config.assets.js );
  4669. }
  4670. FLBuilder._loadedModuleAssets.push( data.type );
  4671. }
  4672. // Render the form.
  4673. FLBuilderSettingsForms.render( {
  4674. type : 'module',
  4675. id : data.type,
  4676. nodeId : data.nodeId,
  4677. className : 'fl-builder-module-settings fl-builder-' + data.type + '-settings',
  4678. attrs : 'data-node="' + data.nodeId + '" data-parent="' + data.parentId + '" data-type="' + data.type + '"',
  4679. buttons : ! data.global && ! FLBuilderConfig.lite && ! FLBuilderConfig.simpleUi ? ['save-as'] : [],
  4680. badges : data.global ? [ FLBuilderStrings.global ] : [],
  4681. settings : settings,
  4682. legacy : data.legacy,
  4683. helper : FLBuilder._moduleHelpers[ data.type ],
  4684. rules : FLBuilder._moduleHelpers[ data.type ] ? FLBuilder._moduleHelpers[ data.type ].rules : null,
  4685. messages : FLBuilder._moduleHelpers[ data.type ] ? FLBuilder._moduleHelpers[ data.type ].messages : null,
  4686. hide : ( ! FLBuilderConfig.userCanEditGlobalTemplates && data.global ) ? true : false,
  4687. preview : {
  4688. type : 'module',
  4689. layout : data.layout,
  4690. callback : function() {
  4691. FLBuilder.triggerHook( 'didAddModule', {
  4692. nodeId: data.nodeId,
  4693. moduleType: settings.type,
  4694. settings: settings
  4695. } );
  4696. }
  4697. }
  4698. }, callback );
  4699. }
  4700. },
  4701. /**
  4702. * Validates the module settings and saves them if
  4703. * the form is valid.
  4704. *
  4705. * @since 1.0
  4706. * @access private
  4707. * @method _saveModuleClicked
  4708. */
  4709. _saveModuleClicked: function()
  4710. {
  4711. var form = $(this).closest('.fl-builder-settings'),
  4712. type = form.attr('data-type'),
  4713. id = form.attr('data-node'),
  4714. helper = FLBuilder._moduleHelpers[type],
  4715. valid = true;
  4716. if(typeof helper !== 'undefined') {
  4717. form.find('label.error').remove();
  4718. form.validate().hideErrors();
  4719. valid = form.validate().form();
  4720. if(valid) {
  4721. valid = helper.submit();
  4722. }
  4723. }
  4724. if(valid) {
  4725. FLBuilder._saveSettings();
  4726. }
  4727. else {
  4728. FLBuilder._toggleSettingsTabErrors();
  4729. }
  4730. },
  4731. /**
  4732. * Adds a new module to a column and loads the settings.
  4733. *
  4734. * @since 1.0
  4735. * @access private
  4736. * @method _addModule
  4737. * @param {Object} parent A jQuery reference to the new module's parent.
  4738. * @param {String} parentId The node id of the new module's parent.
  4739. * @param {String} type The type of module to add.
  4740. * @param {Number} position The position of the new module within its parent.
  4741. * @param {String} widget The type of widget if this module is a widget.
  4742. * @param {String} alias A module alias key if this module is an alias to another module.
  4743. */
  4744. _addModule: function( parent, parentId, type, position, widget, alias )
  4745. {
  4746. // Show the loader.
  4747. FLBuilder._showNodeLoadingPlaceholder( parent, position );
  4748. // Save the new module data.
  4749. if ( parent.hasClass( 'fl-col-group' ) ) {
  4750. FLBuilder._newModuleParent = null;
  4751. FLBuilder._newModulePosition = 0;
  4752. }
  4753. else {
  4754. FLBuilder._newModuleParent = parent;
  4755. FLBuilder._newModulePosition = position;
  4756. }
  4757. // Dispatch to layout store
  4758. const actions = FL.Builder.data.getLayoutActions()
  4759. actions.addModule( type, parentId, position, {
  4760. widget: typeof widget === 'undefined' ? '' : widget,
  4761. alias: typeof alias === 'undefined' ? '' : alias,
  4762. nodePreview: 1
  4763. } )
  4764. },
  4765. /**
  4766. * Shows the settings lightbox and sets the content when
  4767. * the module settings have finished loading.
  4768. *
  4769. * @since 1.0
  4770. * @access private
  4771. * @method _addModuleComplete
  4772. * @param {String} response The JSON encoded response.
  4773. */
  4774. _addModuleComplete: function( response )
  4775. {
  4776. var data = FLBuilder._jsonParse( response ),
  4777. showSettingsForm = false;
  4778. // Setup a preview layout if we have one.
  4779. if ( data.layout ) {
  4780. if ( FLBuilder._newModuleParent ) {
  4781. FLBuilder._newModuleParent.find( '.fl-builder-node-loading-placeholder' ).hide();
  4782. }
  4783. data.layout.nodeParent = FLBuilder._newModuleParent;
  4784. data.layout.nodePosition = FLBuilder._newModulePosition;
  4785. }
  4786. // Make sure we have settings before rendering the form.
  4787. if ( ! data.settings ) {
  4788. data.settings = FLBuilderSettingsConfig.defaults.modules[ data.type ];
  4789. }
  4790. // Render the module if a settings form is already open.
  4791. if ( $( 'form.fl-builder-settings', window.parent.document ).length ) {
  4792. if ( data.layout ) {
  4793. FLBuilder._renderLayout( data.layout );
  4794. showSettingsForm = true;
  4795. }
  4796. } else {
  4797. showSettingsForm = true;
  4798. }
  4799. if ( showSettingsForm ) {
  4800. FLBuilder._showModuleSettings( data, function() {
  4801. $( '.fl-builder-module-settings', window.parent.document ).data( 'new-module', '1' );
  4802. } );
  4803. }
  4804. },
  4805. /**
  4806. * Registers a helper class for a module's settings.
  4807. *
  4808. * @since 1.0
  4809. * @method registerModuleHelper
  4810. * @param {String} type The type of module.
  4811. * @param {Object} obj The module helper.
  4812. */
  4813. registerModuleHelper: function(type, obj)
  4814. {
  4815. var defaults = {
  4816. node: null,
  4817. form: null,
  4818. rules: {},
  4819. init: function(){},
  4820. submit: function(){ return true; },
  4821. preview: function(){},
  4822. getForm: function() {
  4823. if ( ! this.form ) {
  4824. this.form = $( 'form.fl-builder-settings:visible', window.parent.document ).get(0)
  4825. }
  4826. return this.form
  4827. },
  4828. getSettings: function() { return FLBuilder._getSettings( $( this.form ) ) },
  4829. getNodeID: function() { return this.getForm().dataset.node },
  4830. getNode: function() {
  4831. if ( ! this.node ) {
  4832. this.node = document.querySelector( `${FLBuilder._contentClass} .fl-module[data-node="${this.getNodeID()}"]` )
  4833. }
  4834. return this.node
  4835. },
  4836. };
  4837. FLBuilder._moduleHelpers[type] = $.extend({}, defaults, obj);
  4838. },
  4839. /**
  4840. * Deprecated. Use the public method registerModuleHelper instead.
  4841. *
  4842. * @since 1.0
  4843. * @access private
  4844. * @method _registerModuleHelper
  4845. * @param {String} type The type of module.
  4846. * @param {Object} obj The module helper.
  4847. */
  4848. _registerModuleHelper: function(type, obj)
  4849. {
  4850. FLBuilder.registerModuleHelper(type, obj);
  4851. },
  4852. /**
  4853. * Update the margin placeholders for modules inside containers
  4854. * such as the box module that set different default margins.
  4855. *
  4856. * @since 2.8
  4857. * @return void
  4858. */
  4859. _initModuleMarginPlaceholders: function()
  4860. {
  4861. var form = $( '.fl-builder-module-settings:visible', window.parent.document );
  4862. var nodeId = form.data( 'node' );
  4863. var node = $( '.fl-node-' + nodeId );
  4864. var content = node.find( '.fl-node-content' );
  4865. var sides = [ 'top', 'right', 'bottom', 'left' ];
  4866. if ( ! form.length ) {
  4867. return;
  4868. } else if ( ! node.closest( '.fl-module[data-accepts]' ).length ) {
  4869. return;
  4870. } else if ( ! content.length ) {
  4871. content = node;
  4872. }
  4873. for ( var key in sides ) {
  4874. var input = form.find( 'input[name="margin_' + sides[ key ] + '"]' );
  4875. var value = input.val();
  4876. node.removeClass( 'fl-node-' + nodeId );
  4877. input.attr( 'placeholder', parseInt( content.css( 'margin-' + sides[ key ] ) ) );
  4878. node.addClass( 'fl-node-' + nodeId );
  4879. }
  4880. },
  4881. /* Node Templates
  4882. ----------------------------------------------------------*/
  4883. /**
  4884. * Saves a node's settings and shows the node template settings
  4885. * when the Save As button is clicked.
  4886. *
  4887. * @since 1.6.3
  4888. * @access private
  4889. * @method _showNodeTemplateSettings
  4890. * @param {Object} e An event object.
  4891. */
  4892. _showNodeTemplateSettings: function( e )
  4893. {
  4894. var form = $( '.fl-builder-settings-lightbox .fl-builder-settings', window.parent.document ),
  4895. nodeId = form.attr( 'data-node' ),
  4896. title = FLBuilderStrings.saveModule;
  4897. if ( form.hasClass( 'fl-builder-row-settings' ) ) {
  4898. title = FLBuilderStrings.saveRow;
  4899. }
  4900. else if ( form.hasClass( 'fl-builder-col-settings' ) ) {
  4901. title = FLBuilderStrings.saveColumn;
  4902. }
  4903. if ( ! FLBuilder._triggerSettingsSave( false, false, false ) ) {
  4904. return false;
  4905. }
  4906. FLBuilderSettingsForms.render( {
  4907. id : 'node_template',
  4908. nodeId : nodeId,
  4909. title : title,
  4910. attrs : 'data-node="' + nodeId + '"',
  4911. className : 'fl-builder-node-template-settings',
  4912. rules : {
  4913. name: {
  4914. required: true
  4915. }
  4916. }
  4917. }, function() {
  4918. var form = $( '.fl-builder-settings:visible' );
  4919. var cats = FLBuilderConfig.nodeCategoies;
  4920. select = form.find('#fl-field-categories').find('select');
  4921. desc = select.parent().find( '.fl-field-description' ).hide();
  4922. $.each(cats, function (i, item) {
  4923. select.append($('<option>', {
  4924. value: item.id,
  4925. text : item.name
  4926. }
  4927. ));
  4928. });
  4929. select.on('change', function(){
  4930. if ( 'add_new' === $(this).val() ) {
  4931. $( '<input type="text" name="categories" value="" class="text text-full">' ).insertBefore(select);
  4932. select.remove();
  4933. desc.show();
  4934. }
  4935. });
  4936. if ( ! FLBuilderConfig.userCanEditGlobalTemplates ) {
  4937. $( '#fl-field-global', window.parent.document ).hide();
  4938. }
  4939. } );
  4940. },
  4941. /**
  4942. * Saves a node as a template when the save button is clicked.
  4943. *
  4944. * @since 1.6.3
  4945. * @access private
  4946. * @method _saveNodeTemplate
  4947. */
  4948. _saveNodeTemplate: function()
  4949. {
  4950. var form = $( '.fl-builder-node-template-settings', window.parent.document ),
  4951. nodeId = form.attr( 'data-node' ),
  4952. valid = form.validate().form();
  4953. if ( valid ) {
  4954. FLBuilder._showNodeLoading( nodeId );
  4955. const actions = FL.Builder.data.getLayoutActions()
  4956. actions.saveNodeTemplate( nodeId, FLBuilder._getSettings( form ) )
  4957. FLBuilder._lightbox.close();
  4958. }
  4959. },
  4960. /**
  4961. * Callback for when a node template has been saved.
  4962. *
  4963. * @since 1.6.3
  4964. * @access private
  4965. * @method _saveNodeTemplateComplete
  4966. */
  4967. _saveNodeTemplateComplete: function( response )
  4968. {
  4969. var data = FLBuilder._jsonParse( response ),
  4970. panel = $( '.fl-builder-saved-' + data.type + 's', window.parent.document ),
  4971. blocks = panel.find( '.fl-builder-block' ),
  4972. block = null,
  4973. text = '',
  4974. name = data.name.toLowerCase(),
  4975. i = 0,
  4976. template = wp.template( 'fl-node-template-block' ),
  4977. newLibraryItem = {
  4978. name: data.name,
  4979. isGlobal: data.global,
  4980. content: data.type,
  4981. id: data.id,
  4982. postID: data.postID,
  4983. kind: "template",
  4984. type: "user",
  4985. link: data.link,
  4986. category: {
  4987. uncategorized: FLBuilderStrings.uncategorized
  4988. }
  4989. };
  4990. FLBuilderConfig.contentItems.template.push(newLibraryItem);
  4991. FLBuilder.triggerHook('contentItemsChanged');
  4992. // Update the layout for global templates.
  4993. if ( data.layout ) {
  4994. FLBuilder._renderLayout( data.layout );
  4995. FLBuilder.triggerHook( 'didSaveGlobalNodeTemplate', data.config );
  4996. }
  4997. // Add the new template to the builder panel.
  4998. if ( 0 === blocks.length ) {
  4999. panel.append( template( data ) );
  5000. }
  5001. else {
  5002. for ( ; i < blocks.length; i++ ) {
  5003. block = blocks.eq( i );
  5004. text = block.text().toLowerCase().trim();
  5005. if ( 0 === i && name < text ) {
  5006. panel.prepend( template( data ) );
  5007. break;
  5008. }
  5009. else if ( name < text ) {
  5010. block.before( template( data ) );
  5011. break;
  5012. }
  5013. else if ( blocks.length - 1 === i ) {
  5014. panel.append( template( data ) );
  5015. break;
  5016. }
  5017. }
  5018. }
  5019. // Remove the no templates placeholder.
  5020. panel.find( '.fl-builder-block-no-node-templates' ).remove();
  5021. },
  5022. /**
  5023. * Callback for when a node template drag from the
  5024. * builder panel has stopped.
  5025. *
  5026. * @since 1.6.3
  5027. * @access private
  5028. * @method _nodeTemplateDragStop
  5029. * @param {Object} e The event object.
  5030. * @param {Object} ui An object with additional info for the drag.
  5031. */
  5032. _nodeTemplateDragStop: function( e, ui )
  5033. {
  5034. FLBuilder._blockDragStop( e, ui );
  5035. var item = ui.item,
  5036. parent = item.parent(),
  5037. parentId = null,
  5038. position = 0,
  5039. node = null,
  5040. action = '',
  5041. callback = null;
  5042. // A node template was dropped back into the templates list.
  5043. if ( parent.hasClass( 'fl-builder-blocks-section-content' ) ) {
  5044. item.remove();
  5045. return;
  5046. }
  5047. // A saved row was dropped.
  5048. else if ( item.hasClass( 'fl-builder-block-saved-row' ) || item.hasClass( 'fl-builder-block-row-template' ) ) {
  5049. node = item.closest( '.fl-row' );
  5050. position = ! node.length ? 0 : $( FLBuilder._contentClass + ' .fl-row' ).index( node );
  5051. position = parent.hasClass( 'fl-drop-target-last' ) ? position + 1 : position;
  5052. parentId = null;
  5053. action = 'render_new_row_template';
  5054. callback = FLBuilder._addRowComplete;
  5055. FLBuilder._newRowPosition = position;
  5056. FLBuilder._showNodeLoadingPlaceholder( $( FLBuilder._contentClass ), position );
  5057. }
  5058. // A saved column was dropped.
  5059. else if ( item.hasClass( 'fl-builder-block-saved-column' ) ) {
  5060. node = item.closest( '.fl-col' ),
  5061. colGroup = parent.closest( '.fl-col-group' ),
  5062. colGroupId = colGroup.attr( 'data-node' );
  5063. action = 'render_new_col_template';
  5064. callback = FLBuilder._addColsComplete;
  5065. // Cancel the drop if the sortable is disabled?
  5066. if ( parent.hasClass( 'fl-sortable-disabled' ) ) {
  5067. item.remove();
  5068. FLBuilder._showPanel();
  5069. return;
  5070. }
  5071. // A column was dropped into a row position.
  5072. else if ( parent.hasClass( 'fl-row-drop-target' ) ) {
  5073. node = item.closest( '.fl-row' ),
  5074. parentId = 0;
  5075. parent = $( FLBuilder._contentClass );
  5076. position = ! node.length ? 0 : parent.find( '.fl-row' ).index( node );
  5077. }
  5078. // A column was dropped into a column group position.
  5079. else if ( parent.hasClass( 'fl-col-group-drop-target' ) ) {
  5080. parent = item.closest( '.fl-row-content' );
  5081. parentId = item.closest( '.fl-row' ).attr( 'data-node' );
  5082. position = item.closest( '.fl-row' ).find( '.fl-row-content' ).find( ' > .fl-col-group, > .fl-module' ).index( item.closest( '.fl-col-group, .fl-module' ) );
  5083. }
  5084. // A column was dropped into a column position.
  5085. else if ( parent.hasClass( 'fl-col-drop-target' ) ) {
  5086. parent = item.closest('.fl-col-group');
  5087. position = parent.children('.fl-col').index( item.closest('.fl-col') );
  5088. parentId = parent.attr('data-node');
  5089. }
  5090. // Increment the position?
  5091. if ( item.closest( '.fl-drop-target-last' ).length ) {
  5092. position += 1;
  5093. }
  5094. if ( parent.hasClass( 'fl-col-group' ) ) {
  5095. FLBuilder._newColParent = null;
  5096. }
  5097. else {
  5098. FLBuilder._newColParent = parent;
  5099. }
  5100. FLBuilder._newColPosition = position;
  5101. // Show the loader.
  5102. FLBuilder._showNodeLoadingPlaceholder( parent, position );
  5103. }
  5104. // A saved module was dropped.
  5105. else if ( item.hasClass( 'fl-builder-block-saved-module' ) || item.hasClass( 'fl-builder-block-module-template' ) ) {
  5106. action = 'render_new_module';
  5107. callback = FLBuilder._addModuleComplete;
  5108. // Cancel the drop if the sortable is disabled?
  5109. if ( parent.hasClass( 'fl-sortable-disabled' ) ) {
  5110. item.remove();
  5111. FLBuilder._showPanel();
  5112. return;
  5113. }
  5114. // Dropped into a row position.
  5115. else if ( parent.hasClass( 'fl-row-drop-target' ) ) {
  5116. parent = item.closest('.fl-builder-content');
  5117. parentId = 0;
  5118. position = parent.find( '.fl-row' ).index( item.closest('.fl-row') );
  5119. }
  5120. // Dropped into a column group position.
  5121. else if ( parent.hasClass( 'fl-col-group-drop-target' ) ) {
  5122. parent = item.closest( '.fl-row-content' );
  5123. parentId = parent.closest( '.fl-row' ).attr( 'data-node' );
  5124. position = parent.find( ' > .fl-col-group, > .fl-module' ).index( item.closest( '.fl-col-group, .fl-module' ) );
  5125. }
  5126. // Dropped into a column position.
  5127. else if ( parent.hasClass( 'fl-col-drop-target' ) ) {
  5128. parent = item.closest('.fl-col-group');
  5129. position = parent.children('.fl-col').index( item.closest('.fl-col') );
  5130. parentId = parent.attr('data-node');
  5131. }
  5132. // Dropped into a container module WITHOUT a wrapper.
  5133. else if ( parent.hasClass( 'fl-module' ) ) {
  5134. var layoutDirection = FLBuilder._getNodeLayoutDirection( item );
  5135. if ( 'layered' === layoutDirection ) {
  5136. // Drop as top most item in layers.
  5137. position = parent.find( '> .fl-module' ).length;
  5138. } else {
  5139. position = parent.find( '> .fl-module, .fl-builder-block' ).index( item );
  5140. }
  5141. parentId = parent.attr( 'data-node' );
  5142. }
  5143. // Dropped into a container module WITH a wrapper.
  5144. else if ( parent.hasClass( 'fl-module-content' ) ) {
  5145. position = parent.find( '> .fl-module, .fl-builder-block' ).index( item );
  5146. parentId = item.closest( '.fl-module' ).attr( 'data-node' );
  5147. }
  5148. // Dropped into a column.
  5149. else {
  5150. position = parent.children( '.fl-module, .fl-builder-block' ).index( item );
  5151. parentId = item.closest( '.fl-col' ).attr( 'data-node' );
  5152. }
  5153. // Increment the position?
  5154. if ( item.closest( '.fl-drop-target-last' ).length ) {
  5155. position += 1;
  5156. }
  5157. // Save the new module data.
  5158. if ( parent.hasClass( 'fl-col-group' ) ) {
  5159. FLBuilder._newModuleParent = null;
  5160. FLBuilder._newModulePosition = 0;
  5161. }
  5162. else {
  5163. FLBuilder._newModuleParent = parent;
  5164. FLBuilder._newModulePosition = position;
  5165. }
  5166. // Show the loader.
  5167. FLBuilder._showNodeLoadingPlaceholder( parent, position );
  5168. }
  5169. const templateId = item.attr( 'data-id' )
  5170. const templateType = item.attr( 'data-type' )
  5171. const ajaxCallback = function( response ) {
  5172. if ( action.indexOf( 'row' ) > -1 ) {
  5173. var data = FLBuilder._jsonParse( response );
  5174. FLBuilder.triggerHook( 'didApplyRowTemplateComplete', data.config );
  5175. callback( data.layout );
  5176. } else if ( action.indexOf( 'col' ) > -1 ) {
  5177. var data = FLBuilder._jsonParse( response );
  5178. FLBuilder.triggerHook( 'didApplyColTemplateComplete', data.config );
  5179. callback( data.layout );
  5180. } else {
  5181. callback( response );
  5182. }
  5183. }
  5184. let type = 'module'
  5185. if ( 'render_new_col_template' === action ) {
  5186. type = 'column'
  5187. }
  5188. if ( 'render_new_row_template' === action ) {
  5189. type = 'row'
  5190. }
  5191. // Dispatch to layout store
  5192. const actions = FL.Builder.data.getLayoutActions()
  5193. actions.addNodeTemplate( type, templateId, templateType, parentId, position, ajaxCallback )
  5194. // Remove the helper.
  5195. item.remove();
  5196. },
  5197. /**
  5198. * Launches the builder in a new tab to edit a user
  5199. * defined node template when the edit link is clicked.
  5200. *
  5201. * @since 1.6.3
  5202. * @access private
  5203. * @method _editUserTemplateClicked
  5204. * @param {Object} e The event object.
  5205. */
  5206. _editNodeTemplateClicked: function( e )
  5207. {
  5208. e.preventDefault();
  5209. e.stopPropagation();
  5210. window.parent.open( $( this ).attr( 'href' ) );
  5211. },
  5212. /**
  5213. * Fires when the delete node template icon is clicked in the builder's panel.
  5214. *
  5215. * @since 1.6.3
  5216. * @access private
  5217. * @method _deleteNodeTemplateClicked
  5218. * @param {Object} e The event object.
  5219. */
  5220. _deleteNodeTemplateClicked: function( e )
  5221. {
  5222. var button = $( e.target ),
  5223. section = button.closest( '.fl-builder-blocks-section' ),
  5224. panel = section.find( '.fl-builder-blocks-section-content' ),
  5225. blocks = panel.find( '.fl-builder-block' ),
  5226. block = button.closest( '.fl-builder-block' ),
  5227. global = block.hasClass( 'fl-builder-block-global' ),
  5228. message = global ? FLBuilderStrings.deleteGlobalTemplate : FLBuilderStrings.deleteTemplate,
  5229. index = null,
  5230. id = block.attr( 'data-id' );
  5231. if ( confirm( message ) ) {
  5232. // Delete the UI block.
  5233. block.remove();
  5234. // Add the no templates placeholder?
  5235. if ( 1 === blocks.length ) {
  5236. if ( block.hasClass( 'fl-builder-block-saved-row' ) ) {
  5237. panel.append( '<span class="fl-builder-block-no-node-templates">' + FLBuilderStrings.noSavedRows + '</span>' );
  5238. }
  5239. else {
  5240. panel.append( '<span class="fl-builder-block-no-node-templates">' + FLBuilderStrings.noSavedModules + '</span>' );
  5241. }
  5242. }
  5243. // Show the loader?
  5244. if ( block.hasClass( 'fl-builder-block-global' ) ) {
  5245. FLBuilder.showAjaxLoader();
  5246. }
  5247. // Delete the template.
  5248. const actions = FL.Builder.data.getLayoutActions()
  5249. actions.deleteNodeTemplate( id, global )
  5250. // Remove the item from library
  5251. index = _.findIndex(FLBuilderConfig.contentItems.template, {
  5252. id: block.attr('data-id'),
  5253. type: 'user'
  5254. });
  5255. FLBuilderConfig.contentItems.template.splice(index, 1);
  5256. FLBuilder.triggerHook('contentItemsChanged');
  5257. }
  5258. },
  5259. /* Settings
  5260. ----------------------------------------------------------*/
  5261. /**
  5262. * Initializes logic for settings forms.
  5263. *
  5264. * @since 1.0
  5265. * @access private
  5266. * @method _initSettingsForms
  5267. */
  5268. _initSettingsForms: function()
  5269. {
  5270. FLBuilder._initSettingsSections();
  5271. FLBuilder._initButtonGroupFields();
  5272. FLBuilder._initCompoundFields();
  5273. FLBuilder._CodeFieldSSLCheck();
  5274. FLBuilder._initCodeFields();
  5275. FLBuilder._initColorPickers();
  5276. FLBuilder._initGradientPickers();
  5277. FLBuilder._initIconFields();
  5278. FLBuilder._initPhotoFields();
  5279. FLBuilder._initSelectFields();
  5280. FLBuilder._initEditorFields();
  5281. FLBuilder._initMultipleFields();
  5282. FLBuilder._initAutoSuggestFields();
  5283. FLBuilder._initLinkFields();
  5284. FLBuilder._initFontFields();
  5285. FLBuilder._initPostTypeFields();
  5286. FLBuilder._initOrderingFields();
  5287. FLBuilder._initTimezoneFields();
  5288. FLBuilder._initDimensionFields();
  5289. FLBuilder._initFieldPopupSliders();
  5290. FLBuilder._initPresetFields();
  5291. FLBuilder._initModuleMarginPlaceholders();
  5292. FLBuilder._focusFirstSettingsControl();
  5293. FLBuilder._calculateSettingsTabsOverflow();
  5294. FLBuilder._lightbox._resizeEditors();
  5295. $( '.fl-builder-settings-fields', window.parent.document ).css( 'visibility', 'visible' );
  5296. $( '.fl-builder-settings button', window.parent.document ).on( 'click', function( e ) { e.preventDefault() } )
  5297. /**
  5298. * Hook for settings form init.
  5299. */
  5300. FLBuilder.triggerHook('settings-form-init');
  5301. },
  5302. /**
  5303. * Destroys all active settings forms.
  5304. *
  5305. * @since 2.0
  5306. * @access private
  5307. * @method _destroySettingsForms
  5308. */
  5309. _destroySettingsForms: function()
  5310. {
  5311. FLBuilder._destroyEditorFields();
  5312. },
  5313. /**
  5314. * Inserts settings forms rendered with PHP. This method is only around for
  5315. * backwards compatibility with third party settings forms that are
  5316. * still being rendered via AJAX. Going forward, all settings forms
  5317. * should be rendered on the frontend using FLBuilderSettingsForms.render.
  5318. *
  5319. * @since 1.0
  5320. * @access private
  5321. * @method _setSettingsFormContent
  5322. * @param {String} html
  5323. */
  5324. _setSettingsFormContent: function( html )
  5325. {
  5326. $( '.fl-legacy-settings', window.parent.document ).remove();
  5327. $( 'body', window.parent.document ).append( html );
  5328. },
  5329. /**
  5330. * Shows the content for a settings form tab when it is clicked.
  5331. *
  5332. * @since 1.0
  5333. * @access private
  5334. * @method _settingsTabClicked
  5335. * @param {Object} e The event object.
  5336. */
  5337. _settingsTabClicked: function(e)
  5338. {
  5339. var tab = $( this ),
  5340. form = tab.closest( '.fl-builder-settings' ),
  5341. id = tab.attr( 'href' ).split( '#' ).pop();
  5342. FLBuilder._resetSettingsTabsState();
  5343. form.find( '.fl-builder-settings-tab' ).removeClass( 'fl-active' );
  5344. form.find( '#' + id ).addClass( 'fl-active' );
  5345. form.find( '.fl-builder-settings-tabs .fl-active' ).removeClass( 'fl-active' );
  5346. form.find( 'a[href*=' + id + ']' ).addClass( 'fl-active' );
  5347. if ( FLBuilderConfig.rememberTab ) {
  5348. localStorage.setItem( 'fl-builder-settings-tab', id );
  5349. } else {
  5350. localStorage.setItem( 'fl-builder-settings-tab', '' );
  5351. }
  5352. FLBuilder._focusFirstSettingsControl();
  5353. e.preventDefault();
  5354. },
  5355. _resetSettingsTabsState: function() {
  5356. var $lightbox = $('.fl-lightbox:visible', window.parent.document);
  5357. FLBuilder._hideTabsOverflowMenu();
  5358. $lightbox.find('.fl-builder-settings-tabs .fl-active').removeClass('fl-active');
  5359. $lightbox.find('.fl-builder-settings-tabs-overflow-menu .fl-active').removeClass('fl-active');
  5360. $lightbox.find('.fl-contains-active').removeClass('fl-contains-active');
  5361. },
  5362. /**
  5363. * Measures tabs and adds extra items to overflow menu.
  5364. *
  5365. * @since 2.0
  5366. * @access private
  5367. * @return void
  5368. * @method _settingsTabsToOverflowMenu
  5369. */
  5370. _calculateSettingsTabsOverflow: function() {
  5371. var $lightbox = $('.fl-lightbox:visible', window.parent.document),
  5372. lightboxWidth = $lightbox.outerWidth(),
  5373. isSlim = $lightbox.hasClass('fl-lightbox-width-slim'),
  5374. $tabWrap = $lightbox.find('.fl-builder-settings-tabs'),
  5375. $overflowMenu = $lightbox.find('.fl-builder-settings-tabs-overflow-menu'),
  5376. $overflowMenuBtn = $lightbox.find('.fl-builder-settings-tabs-more'),
  5377. $tabs = $tabWrap.find('a'),
  5378. shouldEjectRemainingTabs = false,
  5379. tabsAreaWidth = lightboxWidth - 44, /* 60 is size of "more" btn */
  5380. tabsWidthTotal = 0,
  5381. tabPadding = isSlim ? ( 5 * 2 ) : ( 10 * 2 );
  5382. // Reset the menu
  5383. $overflowMenu.html('');
  5384. FLBuilder._hideTabsOverflowMenu();
  5385. $tabs.removeClass('fl-overflowed');
  5386. // Measure each tab
  5387. $tabs.each(function() {
  5388. if ( !$(this).is(":visible") ) {
  5389. return true;
  5390. }
  5391. // Calculate size until too wide for tab area.
  5392. if ( !shouldEjectRemainingTabs ) {
  5393. // Width of text + padding + bumper space
  5394. var currentTabWidth = $(this).textWidth() + tabPadding + 12;
  5395. tabsWidthTotal += currentTabWidth;
  5396. if ( tabsWidthTotal >= tabsAreaWidth ) {
  5397. shouldEjectRemainingTabs = true;
  5398. } else {
  5399. }
  5400. }
  5401. if ( shouldEjectRemainingTabs ) {
  5402. var label = $(this).html(),
  5403. handle = $(this).attr('href'),
  5404. classAttr = "";
  5405. if ( $(this).hasClass('fl-active') ) {
  5406. classAttr = 'fl-active';
  5407. }
  5408. if ( $(this).hasClass('error') ) {
  5409. classAttr += ' error';
  5410. }
  5411. if ( classAttr !== '' ) {
  5412. classAttr = 'class="' + classAttr + '"';
  5413. }
  5414. var $item = $('<a href="' + handle + '" ' + classAttr + '>' + label + '</a>');
  5415. $overflowMenu.append( $item );
  5416. $(this).addClass('fl-overflowed');
  5417. } else {
  5418. $(this).removeClass('fl-overflowed');
  5419. }
  5420. });
  5421. if ( shouldEjectRemainingTabs ) {
  5422. $lightbox.addClass('fl-lightbox-has-tab-overflow');
  5423. } else {
  5424. $lightbox.removeClass('fl-lightbox-has-tab-overflow');
  5425. }
  5426. if ( $overflowMenu.find('.fl-active').length > 0 ) {
  5427. $overflowMenuBtn.addClass('fl-contains-active');
  5428. } else {
  5429. $overflowMenuBtn.removeClass('fl-contains-active');
  5430. }
  5431. if ( $overflowMenu.find('.error').length > 0 ) {
  5432. $overflowMenuBtn.addClass('fl-contains-errors');
  5433. } else {
  5434. $overflowMenuBtn.removeClass('fl-contains-errors');
  5435. }
  5436. },
  5437. /**
  5438. * Trigger the orignal tab when a menu item is clicked.
  5439. *
  5440. * @since 2.0
  5441. * @var {Event} e
  5442. * @return void
  5443. */
  5444. _settingsTabsToOverflowMenuItemClicked: function(e) {
  5445. var $item = $(e.currentTarget, window.parent.document),
  5446. handle = $item.attr('href'),
  5447. $tabsWrap = $item.closest('.fl-lightbox-header-wrap').find('.fl-builder-settings-tabs'),
  5448. $tab = $tabsWrap.find('a[href="' + handle + '"]'),
  5449. $moreBtn = $tabsWrap.find('.fl-builder-settings-tabs-more');
  5450. FLBuilder._resetSettingsTabsState();
  5451. $tab.trigger('click');
  5452. $item.addClass('fl-active');
  5453. $moreBtn.addClass('fl-contains-active');
  5454. FLBuilder._hideTabsOverflowMenu();
  5455. e.preventDefault();
  5456. },
  5457. /**
  5458. * Check if overflow menu contains any tabs
  5459. *
  5460. * @since 2.0
  5461. * @return bool
  5462. */
  5463. _hasOverflowTabs: function() {
  5464. var $lightbox = $('.fl-lightbox:visible', window.parent.document),
  5465. $tabs = $lightbox.find('.fl-builder-settings-tabs-overflow-menu a');
  5466. if ( $tabs.length > 0 ) {
  5467. return true;
  5468. } else {
  5469. return false;
  5470. }
  5471. },
  5472. /**
  5473. * Show the overflow menu
  5474. *
  5475. */
  5476. _showTabsOverflowMenu: function() {
  5477. if ( ! FLBuilder._hasOverflowTabs() ) return;
  5478. var $lightbox = $('.fl-lightbox:visible', window.parent.document);
  5479. $lightbox.find('.fl-builder-settings-tabs-overflow-menu').css('display', 'flex');
  5480. $lightbox.find('.fl-builder-settings-tabs-overflow-click-mask').show();
  5481. this.isShowingSettingsTabsOverflowMenu = true;
  5482. },
  5483. /**
  5484. * Hide the overflow menu
  5485. */
  5486. _hideTabsOverflowMenu: function() {
  5487. var $lightbox = $('.fl-lightbox:visible', window.parent.document);
  5488. $lightbox.find('.fl-builder-settings-tabs-overflow-menu').css('display', 'none');
  5489. $lightbox.find('.fl-builder-settings-tabs-overflow-click-mask').hide();
  5490. this.isShowingSettingsTabsOverflowMenu = false;
  5491. },
  5492. /**
  5493. * Toggle the overflow menu
  5494. */
  5495. _toggleTabsOverflowMenu: function( e ) {
  5496. if ( FLBuilder.isShowingSettingsTabsOverflowMenu ) {
  5497. FLBuilder._hideTabsOverflowMenu();
  5498. } else {
  5499. FLBuilder._showTabsOverflowMenu();
  5500. }
  5501. e.stopPropagation();
  5502. },
  5503. /**
  5504. * Setup section toggling for all sections
  5505. *
  5506. * @since 2.2
  5507. * @access private
  5508. * @method _initSettingsSections
  5509. * @return void
  5510. */
  5511. _initSettingsSections: function() {
  5512. $( '.fl-builder-settings:visible', window.parent.document ).find( '.fl-builder-settings-section' ).each( FLBuilder._initSection );
  5513. },
  5514. /**
  5515. * Reverts an active preview and hides the lightbox when
  5516. * the cancel button of a settings lightbox is clicked.
  5517. *
  5518. * @since 1.0
  5519. * @access private
  5520. * @method _settingsCancelClicked
  5521. * @param {Object} e The event object.
  5522. */
  5523. _settingsCancelClicked: function(e)
  5524. {
  5525. var nestedLightbox = $( '.fl-builder-lightbox[data-parent]', window.parent.document ),
  5526. moduleSettings = $( '.fl-builder-module-settings', window.parent.document ),
  5527. existingNodes = null,
  5528. previewModule = null,
  5529. previewContainer = null,
  5530. previewCol = null,
  5531. existingCol = null,
  5532. isRootCol = 'column' == FLBuilderConfig.userTemplateType;
  5533. // Close a nested settings lightbox.
  5534. if ( nestedLightbox.length > 0 ) {
  5535. FLBuilder._closeNestedSettings();
  5536. return;
  5537. }
  5538. // Delete a new module preview?
  5539. else if(moduleSettings.length > 0 && typeof moduleSettings.data('new-module') != 'undefined') {
  5540. existingNodes = $(FLBuilder.preview.state.html);
  5541. previewModule = $('.fl-node-' + moduleSettings.data('node'));
  5542. previewContainer = previewModule.parents('.fl-module[data-accepts]');
  5543. previewCol = previewModule.closest('.fl-col');
  5544. existingCol = existingNodes.find('.fl-node-' + previewCol.data('node'));
  5545. if(previewContainer.length > 0 || existingCol.length > 0 || isRootCol) {
  5546. FLBuilder._deleteModule(previewModule);
  5547. }
  5548. else {
  5549. FLBuilder._deleteCol(previewCol);
  5550. }
  5551. }
  5552. // Do a standard preview revert.
  5553. else if( FLBuilder.preview ) {
  5554. FLBuilder.preview.revert();
  5555. }
  5556. const actions = FL.Builder.data.getLayoutActions()
  5557. actions.cancelDisplaySettings()
  5558. FLBuilder.preview = null;
  5559. FLLightbox.closeParent(this);
  5560. FLBuilder.triggerHook( 'didCancelNodeSettings' );
  5561. },
  5562. /**
  5563. * Focus the first visible control in a settings panel
  5564. *
  5565. * @since 2.0
  5566. */
  5567. _focusFirstSettingsControl: function() {
  5568. var form = $( '.fl-builder-settings:visible', window.parent.document ),
  5569. tab = form.find( '.fl-builder-settings-tab.fl-active' ),
  5570. nodeId = form.data( 'node' ),
  5571. field = tab.find('.fl-field').first(),
  5572. input = field.find( 'input:not([type="hidden"]), textarea, select, button, a, .fl-editor-field' ).first();
  5573. // Don't focus fields that have an inline editor.
  5574. if ( nodeId && $( '.fl-node-' + nodeId + ' .fl-inline-editor' ).length ) {
  5575. return;
  5576. }
  5577. if ( 'undefined' !== typeof window.parent.tinyMCE && input.hasClass('fl-editor-field') ) {
  5578. // TinyMCE fields
  5579. var id = input.find('textarea.wp-editor-area').attr('id');
  5580. window.parent.tinyMCE.get( id ).focus();
  5581. } else {
  5582. // Everybody else
  5583. setTimeout(function() {
  5584. input.focus().css('animation-name', 'fl-grab-attention');
  5585. }, 300 );
  5586. }
  5587. // Grab attention
  5588. field.css('animation-name', 'fl-grab-attention');
  5589. field.on('animationend', function() {
  5590. field.css('animation-name', '');
  5591. });
  5592. },
  5593. /**
  5594. * Initializes validation logic for a settings form.
  5595. *
  5596. * @since 1.0
  5597. * @access private
  5598. * @method _initSettingsValidation
  5599. * @param {Object} rules The validation rules object.
  5600. * @param {Object} messages Custom messages to show for invalid fields.
  5601. */
  5602. _initSettingsValidation: function(rules, messages)
  5603. {
  5604. var form = $('.fl-builder-settings', window.parent.document).last();
  5605. if ( ! messages ) {
  5606. messages = {}
  5607. }
  5608. form.validate({
  5609. ignore: '.fl-ignore-validation',
  5610. rules: rules,
  5611. messages: messages,
  5612. errorPlacement: FLBuilder._settingsErrorPlacement
  5613. });
  5614. },
  5615. /**
  5616. * Places a validation error after the invalid field.
  5617. *
  5618. * @since 1.0
  5619. * @access private
  5620. * @method _settingsErrorPlacement
  5621. * @param {Object} error The error element.
  5622. * @param {Object} element The invalid field.
  5623. */
  5624. _settingsErrorPlacement: function(error, element)
  5625. {
  5626. error.appendTo(element.parent());
  5627. },
  5628. /**
  5629. * Resets all tab error icons and then shows any for tabs
  5630. * that have fields with errors.
  5631. *
  5632. * @since 1.0
  5633. * @access private
  5634. * @method _toggleSettingsTabErrors
  5635. */
  5636. _toggleSettingsTabErrors: function()
  5637. {
  5638. var form = $('.fl-builder-settings:visible', window.parent.document),
  5639. tabs = form.find('.fl-builder-settings-tab'),
  5640. tab = null,
  5641. tabErrors = null,
  5642. i = 0;
  5643. for( ; i < tabs.length; i++) {
  5644. tab = tabs.eq(i);
  5645. tabErrors = tab.find('label.error');
  5646. tabLink = form.find('.fl-builder-settings-tabs a[href*='+ tab.attr('id') +']');
  5647. tabLink.find('.fl-error-icon').remove();
  5648. tabLink.removeClass('error');
  5649. if(tabErrors.length > 0) {
  5650. tabLink.append('<span class="fl-error-icon"></span>');
  5651. tabLink.addClass('error');
  5652. }
  5653. }
  5654. FLBuilder._calculateSettingsTabsOverflow();
  5655. },
  5656. /**
  5657. * Returns an object with key/value pairs for all fields
  5658. * within a settings form.
  5659. *
  5660. * @since 1.0
  5661. * @access private
  5662. * @method _getSettings
  5663. * @param {Object} form The settings form element.
  5664. * @return {Object} The settings object.
  5665. */
  5666. _getSettings: function( form )
  5667. {
  5668. FLBuilder._updateEditorFields();
  5669. var data = form.serializeArray(),
  5670. i = 0,
  5671. k = 0,
  5672. value = '',
  5673. key = '',
  5674. keys = [],
  5675. setting = null,
  5676. settings = {};
  5677. // Loop through the form data.
  5678. for ( i = 0; i < data.length; i++ ) {
  5679. value = data[ i ].value.replace( /\r/gm, '' ).replace( /&#39;/g, "'" );
  5680. // Don't save fields without a name.
  5681. if ( 'undefined' === data[ i ].name ) {
  5682. continue;
  5683. }
  5684. // Don't save text editor textareas.
  5685. if ( data[ i ].name.indexOf( 'flrich' ) > -1 ) {
  5686. continue;
  5687. }
  5688. // Support foo[]... setting keys.
  5689. if ( data[ i ].name.indexOf( '[' ) > -1 ) {
  5690. // Get the top level settings key and subkeys.
  5691. key = data[ i ].name.replace( /\[(.*)\]/, '' );
  5692. keys = data[ i ].name.replace( key, '' ).replace( '[', '' ).replaceAll( ']', '' ).split( '[' );
  5693. // Set the top level settings key.
  5694. if ( 'undefined' === typeof settings[ key ] ) {
  5695. if ( 1 === keys.length && '' === keys[ 0 ] ) {
  5696. settings[ key ] = [];
  5697. } else {
  5698. settings[ key ] = {};
  5699. }
  5700. }
  5701. // Reference to use in the keys loop below.
  5702. setting = settings[ key ];
  5703. // Set the subkeys and value.
  5704. for ( k = 0; k < keys.length; k++ ) {
  5705. // Handle foo[] arrays otherwise ignore empty keys.
  5706. if ( '' === keys[ k ] ) {
  5707. if ( 1 === keys.length ) {
  5708. setting.push( value );
  5709. break;
  5710. } else {
  5711. continue;
  5712. }
  5713. }
  5714. // Handle foo[][bar][] or foo[][bar][][baz][] style arrays.
  5715. if ( keys.length - 2 === k && '' === keys[ keys.length - 1 ] ) {
  5716. if ( $.inArray( typeof setting[ keys[ k ] ], [ 'undefined', 'string' ] ) > -1 ) {
  5717. setting[ keys[ k ] ] = [];
  5718. }
  5719. setting[ keys[ k ] ].push( value );
  5720. break;
  5721. }
  5722. // Handle everything else as objects like foo[bar], foo[][bar] or foo[bar][baz].
  5723. if ( keys.length - 1 === k ) {
  5724. setting[ keys[ k ] ] = value;
  5725. } else {
  5726. if ( $.inArray( typeof setting[ keys[ k ] ], [ 'undefined', 'string' ] ) > -1 ) {
  5727. setting[ keys[ k ] ] = {};
  5728. }
  5729. setting = setting[ keys[ k ] ];
  5730. }
  5731. }
  5732. }
  5733. // Standard name/value pair.
  5734. else {
  5735. settings[ data[ i ].name ] = value;
  5736. }
  5737. }
  5738. // Update auto suggest values.
  5739. for ( key in settings ) {
  5740. if ( 'undefined' !== typeof settings[ 'as_values_' + key ] ) {
  5741. settings[ key ] = $.grep(
  5742. settings[ 'as_values_' + key ].split( ',' ),
  5743. function( n ) {
  5744. return n !== '';
  5745. }
  5746. ).join( ',' );
  5747. try {
  5748. delete settings[ 'as_values_' + key ];
  5749. }
  5750. catch( e ) {}
  5751. }
  5752. }
  5753. // In the case of multi-select or checkboxes we need to put the blank setting back in.
  5754. $.each( form.find( '[name]' ), function( key, input ) {
  5755. var name = $( input ).attr( 'name' ).replace( /\[(.*)\]/, '' );
  5756. if ( ! ( name in settings ) && 'undefined' !== name ) {
  5757. settings[ name ] = '';
  5758. }
  5759. });
  5760. // Merge in the original settings in case legacy fields haven't rendered yet.
  5761. settings = $.extend( {}, FLBuilder._getOriginalSettings( form ), settings );
  5762. // Return the settings.
  5763. return settings;
  5764. },
  5765. /**
  5766. * Returns JSON encoded settings to be used in HTML form elements.
  5767. *
  5768. * @since 2.0
  5769. * @access private
  5770. * @method _getSettingsJSONForHTML
  5771. * @param {Object} settings The settings object.
  5772. * @return {String} The settings JSON.
  5773. */
  5774. _getSettingsJSONForHTML: function( settings )
  5775. {
  5776. return JSON.stringify( settings ).replace( /\'/g, '&#39;' ).replace( '<wbr \/>', '<wbr>' );
  5777. },
  5778. /**
  5779. * Returns the original settings for a settings form. This is only
  5780. * used to work with legacy PHP settings fields.
  5781. *
  5782. * @since 2.0
  5783. * @access private
  5784. * @method _getOriginalSettings
  5785. * @param {Object} form The settings form element.
  5786. * @param {Boolean} all Whether to include all of the settings or just those with fields.
  5787. * @return {Object} The settings object.
  5788. */
  5789. _getOriginalSettings: function( form, all )
  5790. {
  5791. var formJSON = form.find( '.fl-builder-settings-json' ),
  5792. nodeId = form.data( 'node' ),
  5793. config = FLBuilderSettingsConfig.nodes,
  5794. original = null,
  5795. settings = {};
  5796. if ( nodeId && config[ nodeId ] ) {
  5797. original = config[ nodeId ];
  5798. } else if ( formJSON.length ) {
  5799. original = FLBuilder._jsonParse( formJSON.val().replace( /&#39;/g, "'" ) );
  5800. }
  5801. if ( original ) {
  5802. for ( key in original ) {
  5803. if ( key.match( /[a-z0-9-_]+$/ ) && $( '#fl-field-' + key ).length || all ) {
  5804. settings[ key ] = original[ key ];
  5805. }
  5806. }
  5807. }
  5808. return settings;
  5809. },
  5810. /**
  5811. * Gets the settings that are saved to see if settings
  5812. * have changed when saving or canceling.
  5813. *
  5814. * @since 2.1
  5815. * @method getSettingsForChangedCheck
  5816. * @param {Object} form
  5817. * @return {Object}
  5818. */
  5819. _getSettingsForChangedCheck: function( nodeId, form ) {
  5820. var settings = FLBuilder._getSettings( form );
  5821. // Make sure we're getting the original setting if even it
  5822. // was changed by inline editing before the form loaded.
  5823. if ( nodeId ) {
  5824. var node = $( '.fl-node-' + nodeId );
  5825. if ( node.hasClass( 'fl-module' ) ) {
  5826. var type = node.data( 'type' );
  5827. var config = FLBuilderSettingsConfig.editables[ type ];
  5828. if ( config && FLBuilderSettingsConfig.nodes[ nodeId ] ) {
  5829. for ( var key in config ) {
  5830. settings[ key ] = FLBuilderSettingsConfig.nodes[ nodeId ][ key ]
  5831. }
  5832. }
  5833. }
  5834. }
  5835. return settings;
  5836. },
  5837. /**
  5838. * Saves the settings for the current settings form, shows
  5839. * the loader and hides the lightbox.
  5840. *
  5841. * @since 1.0
  5842. * @access private
  5843. * @method _saveSettings
  5844. * @param {Boolean} render Whether the layout should render after saving.
  5845. */
  5846. _saveSettings: function( render )
  5847. {
  5848. var form = $( '.fl-builder-settings-lightbox .fl-builder-settings', window.parent.document ),
  5849. newModule = form.data( 'new-module' ),
  5850. nodeId = form.attr( 'data-node' ),
  5851. settings = FLBuilder._getSettings( form ),
  5852. preview = FLBuilder.preview;
  5853. // Default to true for render.
  5854. if ( FLBuilder.isUndefined( render ) || ! FLBuilder.isBoolean( render ) ) {
  5855. render = true;
  5856. }
  5857. // Only proceed if the settings have changed.
  5858. if ( preview && ! preview._settingsHaveChanged() && FLBuilder.isUndefined( newModule ) ) {
  5859. preview.clear();
  5860. FLBuilder._lightbox.close();
  5861. return;
  5862. }
  5863. function finishSavingSettings() {
  5864. // Show the loader.
  5865. FLBuilder._showNodeLoading( nodeId );
  5866. // Update the settings config object.
  5867. FLBuilderSettingsConfig.nodes[ nodeId ] = settings;
  5868. // Dispatch to store
  5869. const actions = FL.Builder.data.getLayoutActions()
  5870. const callback = FLBuilder._saveSettingsComplete.bind( this, render )
  5871. actions.updateNodeSettings( nodeId, settings, callback )
  5872. // Trigger the hook.
  5873. FLBuilder.triggerHook( 'didSaveNodeSettings', {
  5874. nodeId : nodeId,
  5875. settings : settings
  5876. } );
  5877. // Close the lightbox.
  5878. FLBuilder._lightbox.close();
  5879. }
  5880. if ( FLBuilderConfig.userCaps.unfiltered_html ) {
  5881. finishSavingSettings()
  5882. } else {
  5883. FLBuilderSettingsForms.showLightboxLoader()
  5884. FLBuilder.ajax( {
  5885. action : 'verify_settings',
  5886. settings : settings,
  5887. }, function( response ) {
  5888. if ( 'true' === response ) {
  5889. finishSavingSettings()
  5890. } else {
  5891. msg = '<p style="font-weight:bold;text-align:center;">' + FLBuilderStrings.noScriptWarn.heading + '</p>';
  5892. if ( FLBuilderConfig.userCaps.global_unfiltered_html ) {
  5893. msg += '<p>' + FLBuilderStrings.noScriptWarn.global + '</p>';
  5894. } else {
  5895. msg += '<p>' + FLBuilderStrings.noScriptWarn.message + '</p>';
  5896. }
  5897. msg += '<p><div class="fl-diff"></div></p>';
  5898. msg += '<p>' + FLBuilderStrings.noScriptWarn.footer + '</p>';
  5899. FLBuilderSettingsForms.hideLightboxLoader()
  5900. FLBuilder.alert( msg );
  5901. data = $.parseJSON(response);
  5902. if ( '' !== data.diff ) {
  5903. $('.fl-diff', window.parent.document).html( data.diff );
  5904. $('.fl-diff', window.parent.document).prepend( '<p>' + FLBuilderStrings.codeErrorDetected + '</p>');
  5905. $('.fl-diff .diff-deletedline', window.parent.document).each(function(){
  5906. if ( $(this).find('del').length < 1 ) {
  5907. $(this).css('background-color', 'rgb(255, 192, 203, 0.7)').css('padding', '10px').css('border', '1px solid pink');
  5908. } else {
  5909. $(this).find('del').css('background-color', 'rgb(255, 192, 203, 0.7)').css('border', '1px solid pink');
  5910. }
  5911. });
  5912. console.log( '============' );
  5913. console.log( 'key: ' + data.key );
  5914. console.log( 'value: ' + data.value );
  5915. console.log( 'parsed: ' + data.parsed );
  5916. console.log( '============' );
  5917. }
  5918. }
  5919. } );
  5920. }
  5921. },
  5922. /**
  5923. * Renders a new layout when the settings for the current
  5924. * form have finished saving.
  5925. *
  5926. * @since 1.0
  5927. * @access private
  5928. * @method _saveSettingsComplete
  5929. * @param {Boolean} render Whether the layout should render after saving.
  5930. * @param {String} response The layout data from the server.
  5931. */
  5932. _saveSettingsComplete: function( render, response )
  5933. {
  5934. var data = FLBuilder._jsonParse( response ),
  5935. type = data.layout.nodeType,
  5936. moduleType = data.layout.moduleType,
  5937. hook = 'didSave' + type.charAt(0).toUpperCase() + type.slice(1) + 'SettingsComplete',
  5938. preview = FLBuilder.preview,
  5939. callback = function() {
  5940. var clearPreview = preview && data.layout.partial && data.layout.nodeId === preview.nodeId && !FLBuilder._publishAndRemain;
  5941. if ( clearPreview ) {
  5942. preview.clear();
  5943. FLBuilder.preview = null;
  5944. }
  5945. FLBuilder._publishAndRemain = false;
  5946. };
  5947. if ( true === render ) {
  5948. FLBuilder._renderLayout( data.layout, callback );
  5949. } else {
  5950. callback();
  5951. }
  5952. FLBuilder.triggerHook( 'didSaveNodeSettingsComplete', {
  5953. nodeId : data.node_id,
  5954. nodeType : type,
  5955. moduleType : moduleType,
  5956. settings : data.settings
  5957. } );
  5958. FLBuilder.triggerHook( hook, {
  5959. nodeId : data.node_id,
  5960. nodeType : type,
  5961. moduleType : moduleType,
  5962. settings : data.settings
  5963. } );
  5964. },
  5965. /**
  5966. * Triggers a click on the settings save button so all save
  5967. * logic runs for any form that is currently in the lightbox.
  5968. *
  5969. * @since 2.0
  5970. * @access private
  5971. * @method _triggerSettingsSave
  5972. * @param {Boolean} disableClose
  5973. * @param {Boolean} showAlert
  5974. * @param {Boolean} destroy
  5975. * @return {Boolean}
  5976. */
  5977. _triggerSettingsSave: function( disableClose, showAlert, destroy )
  5978. {
  5979. var form = FLBuilder._lightbox._node.find( 'form.fl-builder-settings' ),
  5980. lightboxId = FLBuilder._lightbox._node.data( 'instance-id' ),
  5981. lightbox = FLLightbox._instances[ lightboxId ],
  5982. nested = $( '.fl-lightbox-wrap[data-parent]:visible', window.parent.document ),
  5983. changed = false,
  5984. valid = true;
  5985. disableClose = _.isUndefined( disableClose ) ? false : disableClose;
  5986. showAlert = _.isUndefined( showAlert ) ? false : showAlert;
  5987. destroy = _.isUndefined( destroy ) ? ! disableClose : destroy;
  5988. // prevent clearing preview.
  5989. if (!destroy) {
  5990. FLBuilder._publishAndRemain = true;
  5991. }
  5992. if ( form.length ) {
  5993. // Save any nested settings forms.
  5994. if ( nested.length ) {
  5995. // Save the form.
  5996. nested.find( '.fl-builder-settings-save' ).trigger( 'click' );
  5997. // Don't proceed if not saved.
  5998. if ( nested.find( 'label.error' ).length || $( '.fl-builder-alert-lightbox:visible', window.parent.document ).length ) {
  5999. valid = false;
  6000. }
  6001. }
  6002. // Do a validation check of the main form to see if we should save.
  6003. if ( valid && ! form.validate({ignore: '.fl-ignore-validation'}).form() ) {
  6004. valid = false;
  6005. }
  6006. // Check to see if the main settings have changed.
  6007. changed = FLBuilderSettingsForms.settingsHaveChanged();
  6008. // Save the main settings form if it has changes.
  6009. if ( valid && changed ) {
  6010. // Disable lightbox close?
  6011. if ( disableClose ) {
  6012. lightbox.disableClose();
  6013. }
  6014. // Save the form.
  6015. form.find( '.fl-builder-settings-save' ).trigger( 'click' );
  6016. // Enable lightbox close if it was disabled.
  6017. if ( disableClose ) {
  6018. lightbox.enableClose();
  6019. }
  6020. // Don't proceed if not saved.
  6021. if ( form.find( 'label.error' ).length || $( '.fl-builder-alert-lightbox:visible', window.parent.document ).length ) {
  6022. valid = false;
  6023. }
  6024. }
  6025. // Destroy the settings form?
  6026. if ( destroy ) {
  6027. FLBuilder._destroySettingsForms();
  6028. // Destroy the preview if settings don't have changes.
  6029. if ( ! changed && FLBuilder.preview ) {
  6030. FLBuilder.preview.clear();
  6031. FLBuilder.preview = null;
  6032. }
  6033. } else {
  6034. // cache current settings
  6035. FLBuilderSettingsForms.cacheCurrentSettings();
  6036. }
  6037. // Close the main lightbox if it doesn't have changes and closing isn't disabled.
  6038. if ( ! changed && ! disableClose ) {
  6039. lightbox.close();
  6040. }
  6041. }
  6042. if ( ! valid ) {
  6043. FLBuilder._publishAndRemain = false; // Reset preview clearing
  6044. FLBuilder.triggerHook( 'didFailSettingsSave' );
  6045. FLBuilder._toggleSettingsTabErrors();
  6046. if ( showAlert && ! $( '.fl-builder-alert-lightbox:visible', window.parent.document ).length ) {
  6047. FLBuilder.alert( FLBuilderStrings.settingsHaveErrors );
  6048. }
  6049. } else {
  6050. FLBuilder.triggerHook( 'didTriggerSettingsSave' );
  6051. }
  6052. return valid;
  6053. },
  6054. /**
  6055. * Refreshes preview references for a node's settings panel
  6056. * in case they have been broken by work in the layout.
  6057. *
  6058. * @since 2.0
  6059. * @access private
  6060. * @method _refreshSettingsPreviewReference
  6061. */
  6062. _refreshSettingsPreviewReference: function()
  6063. {
  6064. if ( FLBuilder.preview ) {
  6065. FLBuilder.preview._initElementsAndClasses();
  6066. }
  6067. },
  6068. /* Nested Settings Forms
  6069. ----------------------------------------------------------*/
  6070. /**
  6071. * Opens a nested settings lightbox.
  6072. *
  6073. * @since 1.10
  6074. * @access private
  6075. * @method _openNestedSettings
  6076. * @return object The settings lightbox object.
  6077. */
  6078. _openNestedSettings: function( settings )
  6079. {
  6080. if ( settings.className && -1 === settings.className.indexOf( 'fl-builder-settings-lightbox' ) ) {
  6081. settings.className += ' fl-builder-settings-lightbox';
  6082. }
  6083. settings = $.extend( {
  6084. className: 'fl-builder-lightbox fl-builder-settings-lightbox',
  6085. destroyOnClose: true,
  6086. resizable: true
  6087. }, settings );
  6088. var parentBoxWrap = $( '.fl-lightbox-wrap:visible', window.parent.document ),
  6089. parentBox = parentBoxWrap.find( '.fl-lightbox' ),
  6090. nestedBoxObj = new FLLightbox( settings ),
  6091. nestedBoxWrap = nestedBoxObj._node,
  6092. nestedBox = nestedBoxWrap.find( '.fl-lightbox' );
  6093. parentBoxWrap.hide();
  6094. nestedBoxWrap.attr( 'data-parent', parentBoxWrap.attr( 'data-instance-id' ) );
  6095. nestedBox.attr( 'style', parentBox.attr( 'style' ) );
  6096. nestedBoxObj.on( 'resized', FLBuilder._calculateSettingsTabsOverflow );
  6097. nestedBoxObj.open( '<div class="fl-builder-lightbox-loading"></div>' );
  6098. return nestedBoxObj;
  6099. },
  6100. /**
  6101. * Opens the active nested settings lightbox.
  6102. *
  6103. * @since 1.10
  6104. * @access private
  6105. * @method _closeNestedSettings
  6106. */
  6107. _closeNestedSettings: function()
  6108. {
  6109. var nestedBoxWrap = $( '.fl-builder-lightbox[data-parent]:visible', window.parent.document ),
  6110. nestedBox = nestedBoxWrap.find( '.fl-lightbox' ),
  6111. nestedBoxId = nestedBoxWrap.attr( 'data-instance-id' ),
  6112. nestedBoxObj = FLLightbox._instances[ nestedBoxId ],
  6113. parentBoxId = nestedBoxWrap.attr( 'data-parent' ),
  6114. parentBoxWrap = $( '[data-instance-id="' + parentBoxId + '"]', window.parent.document ),
  6115. parentBox = parentBoxWrap.find( '.fl-lightbox' ),
  6116. parentBoxForm = parentBoxWrap.find('form'),
  6117. parentBoxObj = FLLightbox._instances[ parentBoxId ];
  6118. if ( ! nestedBoxObj ) {
  6119. return
  6120. }
  6121. nestedBoxObj.on( 'close', function() {
  6122. parentBox.attr( 'style', nestedBox.attr( 'style' ) );
  6123. parentBoxWrap.show();
  6124. parentBoxObj._resize();
  6125. parentBoxWrap.find( 'label.error' ).remove();
  6126. parentBoxForm.validate().hideErrors();
  6127. FLBuilder._toggleSettingsTabErrors();
  6128. FLBuilder._initMultipleFields();
  6129. } );
  6130. nestedBoxObj.close();
  6131. },
  6132. /* Tooltips
  6133. ----------------------------------------------------------*/
  6134. /**
  6135. * Shows a help tooltip in the settings lightbox.
  6136. *
  6137. * @since 1.0
  6138. * @access private
  6139. * @method _showHelpTooltip
  6140. */
  6141. _showHelpTooltip: function()
  6142. {
  6143. $(this).siblings('.fl-help-tooltip-text').fadeIn();
  6144. },
  6145. /**
  6146. * Hides a help tooltip in the settings lightbox.
  6147. *
  6148. * @since 1.0
  6149. * @access private
  6150. * @method _hideHelpTooltip
  6151. */
  6152. _hideHelpTooltip: function()
  6153. {
  6154. $(this).siblings('.fl-help-tooltip-text').fadeOut();
  6155. },
  6156. /**
  6157. * Setup section toggling
  6158. *
  6159. * @since 2.2
  6160. * @access private
  6161. * @method _initSection
  6162. * @return void
  6163. */
  6164. _initSection: function() {
  6165. var wrap = $(this),
  6166. button = wrap.find('.fl-builder-settings-section-header');
  6167. button.on('click', function() {
  6168. wrap.toggleClass('fl-builder-settings-section-collapsed')
  6169. });
  6170. },
  6171. /* Align Fields
  6172. ----------------------------------------------------------*/
  6173. /**
  6174. * Initializes all button group fields within a settings form.
  6175. *
  6176. * @since 2.2
  6177. * @access private
  6178. * @method _initButtonGroupFields
  6179. */
  6180. _initButtonGroupFields: function()
  6181. {
  6182. $( '.fl-builder-settings:visible', window.parent.document )
  6183. .find( '.fl-button-group-field' ).each( FLBuilder._initButtonGroupField );
  6184. },
  6185. /**
  6186. * Initializes a button group field within a settings form.
  6187. *
  6188. * @since 2.2
  6189. * @access private
  6190. * @method _initButtonGroupField
  6191. */
  6192. _initButtonGroupField: function()
  6193. {
  6194. var wrap = $( this ),
  6195. options = wrap.find( '.fl-button-group-field-option' ),
  6196. multiple = wrap.data( 'multiple' ),
  6197. min = wrap.data( 'min' ),
  6198. max = wrap.data( 'max' ),
  6199. input = wrap.find( 'input:not(.fl-preview-ignore)' ),
  6200. allowEmpty = !! wrap.data( 'allowEmpty' ),
  6201. value = function( format ) {
  6202. var val = [];
  6203. options.each(function(i, option) {
  6204. if ($(option).attr('data-selected') === '1') {
  6205. val.push($(option).attr('data-value'));
  6206. }
  6207. })
  6208. if ( 'array' == format ) {
  6209. return val;
  6210. }
  6211. return val.join(',');
  6212. };
  6213. options.on( 'click', function() {
  6214. var option = $( this ),
  6215. length = value( 'array' ).length,
  6216. isSelected = '1' === option.attr( 'data-selected' );
  6217. if ( ! allowEmpty && isSelected ) {
  6218. return;
  6219. }
  6220. if ( isSelected ) {
  6221. if ( false == min ) {
  6222. option.attr( 'data-selected', '0' );
  6223. } else {
  6224. if ( length - 1 >= min ) {
  6225. option.attr( 'data-selected', '0' );
  6226. }
  6227. }
  6228. } else {
  6229. // Unset other options
  6230. if ( multiple !== true ) {
  6231. options.attr( 'data-selected', '0' );
  6232. }
  6233. if ( false == max ) {
  6234. option.attr( 'data-selected', '1' );
  6235. } else {
  6236. if ( length + 1 <= max ) {
  6237. option.attr( 'data-selected', '1' );
  6238. }
  6239. }
  6240. }
  6241. input.val( value( '' ) ).trigger( 'change' );
  6242. } );
  6243. // Handle value being changed externally
  6244. input.on( 'change', function( e ) {
  6245. var value = input.val().split(',');
  6246. // Unset other options
  6247. if (multiple !== true) {
  6248. options.attr('data-selected', '0' );
  6249. }
  6250. $.each(value, function(i, val) {
  6251. var option = options.filter( '[data-value="' + val + '"]' );
  6252. // Set the matching one.
  6253. option.attr( 'data-selected', '1' );
  6254. });
  6255. });
  6256. },
  6257. /* Compound Fields
  6258. ----------------------------------------------------------*/
  6259. /**
  6260. * Initializes all compound fields within a settings form.
  6261. *
  6262. * @since 2.2
  6263. * @access private
  6264. * @method _initCompoundFields
  6265. */
  6266. _initCompoundFields: function()
  6267. {
  6268. $( '.fl-builder-settings:visible', window.parent.document )
  6269. .find( '.fl-compound-field' ).each( FLBuilder._initCompoundField );
  6270. },
  6271. /**
  6272. * Initializes a compound field within a settings form.
  6273. *
  6274. * @since 2.2
  6275. * @access private
  6276. * @method _initCompoundField
  6277. */
  6278. _initCompoundField: function()
  6279. {
  6280. var wrap = $( this ),
  6281. sections = wrap.find( '.fl-compound-field-section' ),
  6282. toggles = wrap.find( '.fl-compound-field-section-toggle' ),
  6283. dimensions = wrap.find( '.fl-compound-field-setting' ).has( '.fl-dimension-field-units' );
  6284. sections.each( function() {
  6285. var section = $( this );
  6286. if ( ! section.find( '.fl-compound-field-section-toggle' ).length ) {
  6287. section.addClass( 'fl-compound-field-section-visible' );
  6288. }
  6289. } );
  6290. toggles.on( 'click', function() {
  6291. var toggle = $( this ),
  6292. field = toggle.closest( '.fl-field' ),
  6293. section = toggle.closest( '.fl-compound-field-section' ),
  6294. className = '.' + section.attr( 'class' ).split( ' ' ).join( '.' );
  6295. field.find( className ).toggleClass( 'fl-compound-field-section-visible' );
  6296. } );
  6297. // Init linking for compound dimension fields.
  6298. dimensions.each( function() {
  6299. var field = $( this ),
  6300. label = field.find( '.fl-compound-field-label' ),
  6301. icon = '<i class="fl-dimension-field-link fl-tip dashicons dashicons-admin-links" title="Link Values"></i>';
  6302. if ( ! label.length || field.find( '.fl-shadow-field' ).length ) {
  6303. return;
  6304. }
  6305. label.append( icon );
  6306. } );
  6307. },
  6308. /* Auto Suggest Fields
  6309. ----------------------------------------------------------*/
  6310. /**
  6311. * Initializes all auto suggest fields within a settings form.
  6312. *
  6313. * @since 1.2.3
  6314. * @access private
  6315. * @method _initAutoSuggestFields
  6316. */
  6317. _initAutoSuggestFields: function()
  6318. {
  6319. var fields = $('.fl-builder-settings:visible .fl-suggest-field', window.parent.document),
  6320. field = null,
  6321. values = null,
  6322. name = null,
  6323. data = [];
  6324. fields.each( function() {
  6325. field = $( this );
  6326. if ( '' !== field.attr( 'data-value' ) ) {
  6327. FLBuilderSettingsForms.showFieldLoader( field );
  6328. data.push( {
  6329. name : field.attr( 'name' ),
  6330. value : field.attr( 'data-value' ),
  6331. action : field.attr( 'data-action' ),
  6332. data : field.attr( 'data-action-data' ),
  6333. } );
  6334. }
  6335. } );
  6336. if ( data.length ) {
  6337. FLBuilder.ajax( {
  6338. action: 'get_autosuggest_values',
  6339. fields: data
  6340. }, function( response ) {
  6341. values = FLBuilder._jsonParse( response );
  6342. for ( name in values ) {
  6343. $( '.fl-suggest-field[name="' + name + '"]', window.parent.document )
  6344. .attr( 'data-value', values[ name ] );
  6345. }
  6346. fields.each( FLBuilder._initAutoSuggestField );
  6347. } );
  6348. } else {
  6349. fields.each( FLBuilder._initAutoSuggestField );
  6350. }
  6351. },
  6352. /**
  6353. * Initializes a single auto suggest field.
  6354. *
  6355. * @since 1.2.3
  6356. * @access private
  6357. * @method _initAutoSuggestField
  6358. */
  6359. _initAutoSuggestField: function()
  6360. {
  6361. var field = $(this);
  6362. field.autoSuggest(FLBuilder._ajaxUrl({
  6363. 'fl_action' : 'fl_builder_autosuggest',
  6364. 'fl_as_action' : field.data('action'),
  6365. 'fl_as_action_data' : field.data('action-data'),
  6366. '_wpnonce' : FLBuilderConfig.ajaxNonce
  6367. }), $.extend({}, {
  6368. asHtmlID : field.attr('name'),
  6369. selectedItemProp : 'name',
  6370. searchObjProps : 'name',
  6371. minChars : 2,
  6372. keyDelay : 1000,
  6373. fadeOut : false,
  6374. usePlaceholder : true,
  6375. emptyText : FLBuilderStrings.noResultsFound,
  6376. showResultListWhenNoMatch : true,
  6377. preFill : field.data('value'),
  6378. queryParam : 'fl_as_query',
  6379. afterSelectionAdd : FLBuilder._updateAutoSuggestField,
  6380. afterSelectionRemove : FLBuilder._updateAutoSuggestField,
  6381. selectionLimit : field.data('limit'),
  6382. canGenerateNewSelections : false
  6383. }, field.data( 'args' )));
  6384. FLBuilderSettingsForms.hideFieldLoader( field );
  6385. },
  6386. /**
  6387. * Updates the value of an auto suggest field.
  6388. *
  6389. * @since 1.2.3
  6390. * @access private
  6391. * @method _initAutoSuggestField
  6392. * @param {Object} element The auto suggest field.
  6393. * @param {Object} item The current selection.
  6394. * @param {Array} selections An array of selected values.
  6395. */
  6396. _updateAutoSuggestField: function(element, item, selections)
  6397. {
  6398. var that = this;
  6399. $(this).siblings('.as-values').val(selections.join(',')).trigger('change');
  6400. // sortable stuff.
  6401. $(this).parents( '.as-selections').sortable({
  6402. items : ':not(.as-original)',
  6403. 'update': function( event, ui ) {
  6404. var selected = [];
  6405. set = that.parents( '.as-selections').find('li.as-selection-item');
  6406. $.each(set, function(i,n) {
  6407. selected.push($(n).attr('data-value'));
  6408. });
  6409. $(that).siblings('.as-values').val(selected.join(',')).trigger('change');
  6410. }
  6411. })
  6412. },
  6413. /* Code Fields
  6414. ----------------------------------------------------------*/
  6415. /**
  6416. * SiteGround ForceSSL fix
  6417. */
  6418. _CodeFieldSSLCheck: function() {
  6419. $('body').append('<div class="sg-test" style="display:none"><svg xmlns="http://www.w3.org/2000/svg"></svg></div>');
  6420. if ( 'https://www.w3.org/2000/svg' === $('.sg-test').find('svg').attr('xmlns') ) {
  6421. FLBuilder._codeDisabled = true;
  6422. }
  6423. $('.sg-test').remove()
  6424. },
  6425. /**
  6426. * Initializes all code fields in a settings form.
  6427. *
  6428. * @since 2.0
  6429. * @access private
  6430. * @method _initCodeFields
  6431. */
  6432. _initCodeFields: function()
  6433. {
  6434. if ( ! FLBuilder._codeDisabled ) {
  6435. $( '.fl-builder-settings:visible', window.parent.document )
  6436. .find( '.fl-code-field' ).each( FLBuilder._initCodeField );
  6437. }
  6438. },
  6439. /**
  6440. * Initializes a single code field in a settings form.
  6441. *
  6442. * @since 2.0
  6443. * @access private
  6444. * @method _initCodeField
  6445. */
  6446. _initCodeField: function()
  6447. {
  6448. var field = $( this ),
  6449. settings = field.closest( '.fl-builder-settings' ),
  6450. textarea = field.find( 'textarea' ),
  6451. editorId = textarea.attr( 'id' ),
  6452. mode = textarea.data( 'editor' ),
  6453. wrap = textarea.data( 'wrap' ),
  6454. editDiv = $( '<div>', {
  6455. position: 'absolute',
  6456. height: parseInt( textarea.attr( 'rows' ), 10 ) * 20
  6457. } ),
  6458. editor = null,
  6459. global_layout = ( settings.hasClass('fl-builder-global-settings') || settings.hasClass('fl-builder-layout-settings' ) ) ? true : false;
  6460. editDiv.insertBefore( textarea );
  6461. editDiv.attr('contentEditable', true );
  6462. editDiv.addClass('fl-ignore-validation');
  6463. textarea.css( 'display', 'none' );
  6464. ace.require( 'ace/ext/language_tools' );
  6465. editor = ace.edit( editDiv[0] );
  6466. editor.$blockScrolling = Infinity;
  6467. editor.getSession().setValue( textarea.val() );
  6468. editor.getSession().setMode( 'ace/mode/' + mode );
  6469. if ( wrap ) {
  6470. editor.getSession().setUseWrapMode( true );
  6471. }
  6472. editor.setOptions( FLBuilderConfig.AceEditorSettings );
  6473. editor.getSession().on( 'change', function( e ) {
  6474. textarea.val( editor.getSession().getValue() ).trigger( 'change' );
  6475. } );
  6476. /**
  6477. * Watch the editor for annotation changes and let the
  6478. * user know if there are any errors.
  6479. */
  6480. editor.getSession().on( 'changeAnnotation', function() {
  6481. var annot = editor.getSession().getAnnotations();
  6482. var saveBtn = settings.find( '.fl-builder-settings-save' );
  6483. var errorBtn = settings.find( '.fl-builder-settings-error' );
  6484. var hasError = false;
  6485. for ( var i = 0; i < annot.length; i++ ) {
  6486. if ( annot[ i ].text.indexOf( 'DOCTYPE' ) > -1 ) {
  6487. continue;
  6488. }
  6489. if ( annot[ i ].text.indexOf( 'Named entity expected' ) > -1 ) {
  6490. continue;
  6491. }
  6492. if ( annot[ i ].text.indexOf( '@supports' ) > -1 ) {
  6493. continue;
  6494. }
  6495. if ( 'error' === annot[ i ].type ) {
  6496. hasError = true;
  6497. break;
  6498. }
  6499. }
  6500. val = editor.getSession().getValue();
  6501. if( global_layout && hasError && null !== val.match( /<\/iframe>|<\/script>|<meta/gm ) ) {
  6502. saveBtn.addClass( 'fl-builder-settings-error' );
  6503. saveBtn.on( 'click', FLBuilder._showCodeFieldCriticalError );
  6504. }
  6505. if ( hasError && settings.find( '#fl-builder-settings-section-bb_js_code' ).length > 0 && 'fl-field-bb_js_code' === field.closest( '.fl-field' ).attr('ID') ) {
  6506. saveBtn.addClass( 'fl-builder-settings-error' );
  6507. saveBtn.on( 'click', FLBuilder._showCodeFieldCriticalError );
  6508. }
  6509. if ( hasError && ! saveBtn.hasClass( 'fl-builder-settings-error' ) && errorBtn.length && FLBuilderConfig.CheckCodeErrors ) {
  6510. saveBtn.addClass( 'fl-builder-settings-error' );
  6511. saveBtn.on( 'click', FLBuilder._showCodeFieldError );
  6512. }
  6513. if ( ! hasError ) {
  6514. errorBtn.removeClass( 'fl-builder-settings-error' );
  6515. errorBtn.off( 'click', FLBuilder._showCodeFieldError );
  6516. errorBtn.off( 'click', FLBuilder._showCodeFieldCriticalError );
  6517. }
  6518. });
  6519. textarea.closest( '.fl-field' ).data( 'editor', editor );
  6520. },
  6521. /**
  6522. * Shows the code error alert when a code field
  6523. * has an error.
  6524. *
  6525. * @since 2.1
  6526. * @access private
  6527. * @method _showCodeFieldError
  6528. */
  6529. _showCodeFieldError: function( e ) {
  6530. e.stopImmediatePropagation();
  6531. FLBuilder.confirm( {
  6532. message: FLBuilderStrings.codeError,
  6533. cancel: function(){
  6534. var saveBtn = $( '.fl-builder-settings:visible .fl-builder-settings-save', window.parent.document );
  6535. saveBtn.removeClass( 'fl-builder-settings-error' );
  6536. saveBtn.off( 'click', FLBuilder._showCodeFieldError );
  6537. saveBtn.trigger( 'click' );
  6538. },
  6539. strings: {
  6540. ok: FLBuilderStrings.codeErrorFix,
  6541. cancel: FLBuilderStrings.codeErrorIgnore
  6542. }
  6543. } );
  6544. },
  6545. _showCodeFieldCriticalError: function( e ) {
  6546. e.stopImmediatePropagation();
  6547. FLBuilder.alert( FLBuilderStrings.codeerrorhtml );
  6548. },
  6549. /* Multiple Fields
  6550. ----------------------------------------------------------*/
  6551. /**
  6552. * Initializes all multiple fields in a settings form.
  6553. *
  6554. * @since 1.0
  6555. * @access private
  6556. * @method _initMultipleFields
  6557. */
  6558. _initMultipleFields: function()
  6559. {
  6560. $('.fl-builder-settings:visible .fl-builder-field-multiples', window.parent.document).each(function(){
  6561. var multiples = $(this),
  6562. multiple = null,
  6563. fields = null,
  6564. i = 0,
  6565. cursorAt = FLBuilderConfig.isRtl ? { left: 10 } : { right: 10 },
  6566. limit = multiples.attr( 'data-limit' ) || 0,
  6567. count = multiples.find('tr').length || 0
  6568. if( parseInt(limit) > 0 && count -1 >= parseInt( limit ) ) {
  6569. multiples.find('.fl-builder-field-copy').hide()
  6570. multiples.find('.fl-builder-field-add').fadeOut()
  6571. } else {
  6572. multiples.find('.fl-builder-field-copy, .fl-builder-field-add').show()
  6573. }
  6574. for( ; i < multiples.length; i++) {
  6575. multiple = multiples.eq(i);
  6576. fields = multiple.find('.fl-builder-field-multiple');
  6577. if(fields.length === 1) {
  6578. fields.eq(0).find('.fl-builder-field-actions').addClass('fl-builder-field-actions-single');
  6579. }
  6580. else {
  6581. fields.find('.fl-builder-field-actions').removeClass('fl-builder-field-actions-single');
  6582. }
  6583. }
  6584. $('.fl-builder-field-multiples', window.parent.document).sortable({
  6585. items: '.fl-builder-field-multiple',
  6586. cursor: 'move',
  6587. cursorAt: cursorAt,
  6588. distance: 5,
  6589. opacity: 0.5,
  6590. placeholder: 'fl-builder-field-dd-zone',
  6591. stop: FLBuilder._fieldDragStop,
  6592. tolerance: 'pointer',
  6593. axis: "y"
  6594. });
  6595. }); // end loop
  6596. },
  6597. /**
  6598. * Adds a new multiple field to the list when the add
  6599. * button is clicked.
  6600. *
  6601. * @since 1.0
  6602. * @access private
  6603. * @method _addFieldClicked
  6604. */
  6605. _addFieldClicked: function()
  6606. {
  6607. var button = $(this),
  6608. fieldName = button.attr('data-field'),
  6609. fieldRow = button.closest('tr').siblings('tr[data-field='+ fieldName +']').last(),
  6610. clone = fieldRow.clone(),
  6611. form = clone.find( '.fl-form-field' ),
  6612. formType = null,
  6613. defaultVal = null,
  6614. index = parseInt(fieldRow.find('label span.fl-builder-field-index').html(), 10) + 1;
  6615. clone.find('th label span.fl-builder-field-index').html(index);
  6616. clone.find('.fl-form-field-preview-text').html('');
  6617. clone.find('.fl-form-field-before').remove();
  6618. clone.find('.fl-form-field-after').remove();
  6619. clone.find('input, textarea, select').val('');
  6620. // clear color
  6621. clone.find('.fl-color-picker-color').css('background-color', 'transparent');
  6622. clone.find('.fl-color-picker-color').addClass('fl-color-picker-empty');
  6623. fieldRow.after(clone);
  6624. FLBuilder._initMultipleFields();
  6625. if ( form.length ) {
  6626. formType = form.find( '.fl-form-field-edit' ).data( 'type' );
  6627. form.find( 'input' ).val( JSON.stringify( FLBuilderSettingsConfig.defaults.forms[ formType ] ) );
  6628. }
  6629. else {
  6630. form = button.closest('form.fl-builder-settings');
  6631. formType = form.data( 'type' );
  6632. if ( formType && form.hasClass( 'fl-builder-module-settings' ) ) {
  6633. defaultVal = FLBuilderSettingsConfig.defaults.modules[ formType ][ fieldName ][0];
  6634. clone.find('input, textarea, select').val( defaultVal );
  6635. }
  6636. FLBuilder._renumberFields( clone.closest( '.fl-field' ) );
  6637. }
  6638. },
  6639. /**
  6640. * Copies a multiple field and adds it to the list when
  6641. * the copy button is clicked.
  6642. *
  6643. * @since 1.0
  6644. * @access private
  6645. * @method _copyFieldClicked
  6646. */
  6647. _copyFieldClicked: function()
  6648. {
  6649. var button = $(this),
  6650. row = button.closest('tr'),
  6651. clone = row.clone(),
  6652. index = parseInt(row.find('label span.fl-builder-field-index').html(), 10) + 1;
  6653. clone.find('th label span.fl-builder-field-index').html(index);
  6654. row.after(clone);
  6655. FLBuilder._renumberFields(row.parent());
  6656. FLBuilder._initMultipleFields();
  6657. if ( FLBuilder.preview ) {
  6658. FLBuilder.preview.delayPreview();
  6659. }
  6660. },
  6661. /**
  6662. * Deletes a multiple field from the list when the
  6663. * delete button is clicked.
  6664. *
  6665. * @since 1.0
  6666. * @access private
  6667. * @method _deleteFieldClicked
  6668. */
  6669. _deleteFieldClicked: function()
  6670. {
  6671. var row = $(this).closest('tr'),
  6672. parent = row.parent(),
  6673. global = parent.closest('.fl-builder-settings').hasClass('fl-builder-global-styles'),
  6674. result = confirm(global ? FLBuilderStrings.deleteGlobalColorsWarning : FLBuilderStrings.deleteFieldMessage);
  6675. if(result) {
  6676. row.remove();
  6677. FLBuilder._renumberFields(parent);
  6678. FLBuilder._initMultipleFields();
  6679. if ( FLBuilder.preview ) {
  6680. FLBuilder.preview.delayPreview();
  6681. }
  6682. }
  6683. },
  6684. /**
  6685. * Renumbers the labels for a list of multiple fields.
  6686. *
  6687. * @since 1.0
  6688. * @access private
  6689. * @method _renumberFields
  6690. * @param {Object} table A table element with multiple fields.
  6691. */
  6692. _renumberFields: function( table )
  6693. {
  6694. var rows = table.find( '.fl-builder-field-multiple' );
  6695. rows.each( function( i, row ) {
  6696. $( row ).find( 'th label span.fl-builder-field-index' ).html( i + 1 );
  6697. FLBuilder._renumberFieldAttr( row, 'name', i );
  6698. FLBuilder._renumberFieldAttr( row, 'id', i );
  6699. FLBuilder._renumberFieldAttr( row, 'for', i );
  6700. } );
  6701. },
  6702. /**
  6703. * @since 2.5
  6704. * @access private
  6705. * @method _renumberFieldAttr
  6706. * @param {String} value
  6707. */
  6708. _renumberFieldAttr: function( row, key, i )
  6709. {
  6710. $( row ).find( '[' + key + ']' ).each( function( k, ele ) {
  6711. var value = $( ele ).attr( key );
  6712. value = value.replace( /\[(\d+)\]/, '[' + ( i ) + ']' );
  6713. $( ele ).attr( key, value );
  6714. } );
  6715. },
  6716. /**
  6717. * Returns an element for multiple field drag operations.
  6718. *
  6719. * @since 1.0
  6720. * @access private
  6721. * @method _fieldDragHelper
  6722. * @return {Object} The helper element.
  6723. */
  6724. _fieldDragHelper: function()
  6725. {
  6726. return $('<div class="fl-builder-field-dd-helper"></div>');
  6727. },
  6728. /**
  6729. * Renumbers and triggers a preview when a multiple field
  6730. * has finished dragging.
  6731. *
  6732. * @since 1.0
  6733. * @access private
  6734. * @method _fieldDragStop
  6735. * @param {Object} e The event object.
  6736. * @param {Object} ui An object with additional info for the drag.
  6737. */
  6738. _fieldDragStop: function(e, ui)
  6739. {
  6740. FLBuilder._renumberFields(ui.item.parent());
  6741. if ( FLBuilder.preview ) {
  6742. FLBuilder.preview.delayPreview();
  6743. }
  6744. },
  6745. /* Select Fields
  6746. ----------------------------------------------------------*/
  6747. /**
  6748. * Initializes select fields for a settings form.
  6749. *
  6750. * @since 1.0
  6751. * @access private
  6752. * @method _initSelectFields
  6753. */
  6754. _initSelectFields: function()
  6755. {
  6756. var selects = $( '.fl-builder-settings:visible', window.parent.document )
  6757. .find( 'select:not(.fl-preview-ignore)' );
  6758. selects.on( 'change', FLBuilder._settingsSelectChanged );
  6759. selects.trigger( 'change' );
  6760. selects.on( 'change', FLBuilder._calculateSettingsTabsOverflow );
  6761. // Button groups use the same options and toggling behavior as selects.
  6762. var buttonGroups = $( '.fl-builder-settings:visible', window.parent.document )
  6763. .find( '.fl-button-group-field input[type=hidden]:not(.fl-preview-ignore)' );
  6764. buttonGroups.on( 'change', FLBuilder._settingsSelectChanged );
  6765. buttonGroups.trigger( 'change' );
  6766. buttonGroups.on( 'change', FLBuilder._calculateSettingsTabsOverflow );
  6767. // Remove hook first, just in case.
  6768. FLBuilder.removeHook('settings-form-init', FLBuilder._initSelectFieldSetFields );
  6769. FLBuilder.addHook('settings-form-init', FLBuilder._initSelectFieldSetFields );
  6770. },
  6771. /**
  6772. * Callback for when a settings form select has been changed.
  6773. * If toggle data is present, other fields will be toggled
  6774. * when this select changes.
  6775. *
  6776. * @since 1.0
  6777. * @access private
  6778. * @method _settingsSelectChanged
  6779. */
  6780. _settingsSelectChanged: function( e )
  6781. {
  6782. var select = $(this),
  6783. name = select.attr('data-root-name'),
  6784. toggle = select.attr('data-toggle'),
  6785. hide = select.attr('data-hide'),
  6786. trigger = select.attr('data-trigger'),
  6787. set = select.attr('data-set'),
  6788. val = select.val(),
  6789. rawVal = val,
  6790. i = 0,
  6791. mode = FLBuilderResponsiveEditing._mode,
  6792. responsive = select.closest( '.fl-field-responsive-setting' ),
  6793. modeClass = 'fl-field-responsive-setting-' + mode,
  6794. allowToggle = false;
  6795. if ( '' === val && name ) {
  6796. const field = FLBuilderSettingsForms.getField( name )
  6797. const inheritedVal = field?.getInheritedValue( mode )
  6798. val = inheritedVal ? inheritedVal : val
  6799. }
  6800. if ( responsive.length ) {
  6801. if(
  6802. ! select.parent().hasClass( modeClass ) &&
  6803. ! select.parent().parent().hasClass( modeClass )
  6804. ) {
  6805. return;
  6806. }
  6807. }
  6808. // TOGGLE sections, fields or tabs.
  6809. if( typeof toggle !== 'undefined' ) {
  6810. toggle = FLBuilder._jsonParse(toggle);
  6811. allowToggle = true;
  6812. for(i in toggle) {
  6813. if ( allowToggle ){
  6814. FLBuilder._settingsSelectToggle(toggle[i].fields, 'hide', '#fl-field-');
  6815. FLBuilder._settingsSelectToggle(toggle[i].sections, 'hide', '#fl-builder-settings-section-');
  6816. FLBuilder._settingsSelectToggle(toggle[i].tabs, 'hide', 'a[href*=fl-builder-settings-tab-', ']');
  6817. }
  6818. }
  6819. if(typeof toggle[val] !== 'undefined') {
  6820. if ( allowToggle ){
  6821. FLBuilder._settingsSelectToggle(toggle[val].fields, 'show', '#fl-field-');
  6822. FLBuilder._settingsSelectToggle(toggle[val].sections, 'show', '#fl-builder-settings-section-');
  6823. FLBuilder._settingsSelectToggle(toggle[val].tabs, 'show', 'a[href*=fl-builder-settings-tab-', ']');
  6824. }
  6825. }
  6826. }
  6827. // HIDE sections, fields or tabs.
  6828. if( typeof hide !== 'undefined' ) {
  6829. hide = FLBuilder._jsonParse(hide);
  6830. for(i in hide) {
  6831. FLBuilder._settingsSelectToggle(hide[i].fields, 'show', '#fl-field-');
  6832. FLBuilder._settingsSelectToggle(hide[i].sections, 'show', '#fl-builder-settings-section-');
  6833. FLBuilder._settingsSelectToggle(hide[i].tabs, 'show', 'a[href*=fl-builder-settings-tab-', ']');
  6834. }
  6835. if(typeof hide[val] !== 'undefined') {
  6836. FLBuilder._settingsSelectToggle(hide[val].fields, 'hide', '#fl-field-');
  6837. FLBuilder._settingsSelectToggle(hide[val].sections, 'hide', '#fl-builder-settings-section-');
  6838. FLBuilder._settingsSelectToggle(hide[val].tabs, 'hide', 'a[href*=fl-builder-settings-tab-', ']');
  6839. }
  6840. }
  6841. // TRIGGER select inputs.
  6842. if( typeof trigger !== 'undefined' ) {
  6843. trigger = FLBuilder._jsonParse(trigger);
  6844. if(typeof trigger[val] !== 'undefined') {
  6845. if(typeof trigger[val].fields !== 'undefined') {
  6846. for(i = 0; i < trigger[val].fields.length; i++) {
  6847. $('#fl-field-' + trigger[val].fields[i]).find('select').trigger('change');
  6848. }
  6849. }
  6850. }
  6851. }
  6852. },
  6853. /**
  6854. * @since 1.0
  6855. * @access private
  6856. * @method _settingsSelectToggle
  6857. * @param {Array} inputArray
  6858. * @param {Function} func
  6859. * @param {String} prefix
  6860. * @param {String} suffix
  6861. */
  6862. _settingsSelectToggle: function(inputArray, func, prefix, suffix)
  6863. {
  6864. var i = 0;
  6865. suffix = 'undefined' == typeof suffix ? '' : suffix;
  6866. if(typeof inputArray !== 'undefined') {
  6867. for( ; i < inputArray.length; i++) {
  6868. $('.fl-builder-settings:visible', window.parent.document).find(prefix + inputArray[i] + suffix)[func]();
  6869. // Resize code editor fields.
  6870. $( prefix + inputArray[i] + suffix ).parent().find( '.fl-field[data-type="code"]' ).each( function() {
  6871. if ( ! FLBuilder._codeDisabled && $( this ).data( 'editor' ) ) {
  6872. $( this ).data( 'editor' ).resize();
  6873. }
  6874. } );
  6875. }
  6876. }
  6877. },
  6878. /**
  6879. * Setup field 'set' configuration. Sets other fields when the value of a selector button group changes.
  6880. * This is set up after the form is done rendering to avoid initial change event.
  6881. *
  6882. * @since 2.8
  6883. * @access private
  6884. * @method _initSelectFieldSetFields
  6885. * @return void
  6886. */
  6887. _initSelectFieldSetFields: function()
  6888. {
  6889. var selects = $( '.fl-builder-settings:visible', window.parent.document )
  6890. .find( 'select:not(.fl-preview-ignore)' );
  6891. var buttonGroups = $( '.fl-builder-settings:visible', window.parent.document )
  6892. .find( '.fl-button-group-field input[type=hidden]:not(.fl-preview-ignore)' );
  6893. selects.on( 'change', FLBuilder._settingsSelectSetFields );
  6894. buttonGroups.on( 'change', FLBuilder._settingsSelectSetFields );
  6895. },
  6896. _settingsSelectSetFields: function()
  6897. {
  6898. var select = $(this),
  6899. name = select.attr('data-root-name'),
  6900. set = select.attr('data-set'),
  6901. val = select.val(),
  6902. mode = FLBuilderResponsiveEditing._mode;
  6903. // SET other fields based on value
  6904. if ( undefined !== set ) {
  6905. set = FLBuilder._jsonParse(set)
  6906. if ( typeof set[val] !== 'undefined' ) {
  6907. // Handle setting fields
  6908. for( let name in set[val] ) {
  6909. const field = FLBuilderSettingsForms.getField( name )
  6910. const value = set[val][name]
  6911. if ( 'object' === typeof value ) {
  6912. for( let key in value ) {
  6913. field.setSubValue( key, value[key], mode )
  6914. }
  6915. } else {
  6916. field.setValue( value, mode )
  6917. }
  6918. }
  6919. }
  6920. }
  6921. },
  6922. _toggleForm: function() {
  6923. const form = $( '.fl-builder-settings:visible', window.parent.document )
  6924. const fields = form.find('select:visible, .fl-button-group-field:visible input[type=hidden]:not(.fl-preview-ignore)')
  6925. fields.each( function() {
  6926. if ( this.hasAttribute('data-toggle') ) {
  6927. const toggle = FLBuilder._jsonParse( this.getAttribute('data-toggle') )
  6928. const hide = FLBuilder._jsonParse( this.getAttribute('data-hide') )
  6929. const name = this.getAttribute('data-root-name')
  6930. let val = this.value
  6931. if ( '' === val && name ) {
  6932. const field = FLBuilderSettingsForms.getField( name )
  6933. const inheritedVal = field?.getInheritedValue( FLBuilderResponsiveEditing._mode )
  6934. val = inheritedVal ? inheritedVal : val
  6935. }
  6936. // Hide everything
  6937. if ( toggle ) {
  6938. for( let i in toggle ) {
  6939. FLBuilder._settingsSelectToggle(toggle[i].fields, 'hide', '#fl-field-');
  6940. FLBuilder._settingsSelectToggle(toggle[i].sections, 'hide', '#fl-builder-settings-section-');
  6941. FLBuilder._settingsSelectToggle(toggle[i].tabs, 'hide', 'a[href*=fl-builder-settings-tab-', ']');
  6942. }
  6943. // Toggle things on
  6944. if( typeof toggle[val] !== 'undefined' ) {
  6945. FLBuilder._settingsSelectToggle(toggle[val].fields, 'show', '#fl-field-');
  6946. FLBuilder._settingsSelectToggle(toggle[val].sections, 'show', '#fl-builder-settings-section-');
  6947. FLBuilder._settingsSelectToggle(toggle[val].tabs, 'show', 'a[href*=fl-builder-settings-tab-', ']');
  6948. }
  6949. }
  6950. if ( hide ) {
  6951. for( let i in hide ) {
  6952. FLBuilder._settingsSelectToggle(hide[i].fields, 'show', '#fl-field-');
  6953. FLBuilder._settingsSelectToggle(hide[i].sections, 'show', '#fl-builder-settings-section-');
  6954. FLBuilder._settingsSelectToggle(hide[i].tabs, 'show', 'a[href*=fl-builder-settings-tab-', ']');
  6955. }
  6956. if( typeof hide[val] !== 'undefined' ) {
  6957. FLBuilder._settingsSelectToggle(hide[val].fields, 'hide', '#fl-field-');
  6958. FLBuilder._settingsSelectToggle(hide[val].sections, 'hide', '#fl-builder-settings-section-');
  6959. FLBuilder._settingsSelectToggle(hide[val].tabs, 'hide', 'a[href*=fl-builder-settings-tab-', ']');
  6960. }
  6961. }
  6962. }
  6963. } )
  6964. },
  6965. /* Color Pickers
  6966. ----------------------------------------------------------*/
  6967. /**
  6968. * Initializes color picker fields for a settings form.
  6969. *
  6970. * @since 1.0
  6971. * @access private
  6972. * @method _initColorPickers
  6973. */
  6974. _initColorPickers: function()
  6975. {
  6976. if ( FLBuilder.colorPicker ) {
  6977. FLBuilder.colorPicker._init();
  6978. return;
  6979. }
  6980. FLBuilder.colorPicker = new FLBuilderColorPicker({
  6981. mode: 'hsv',
  6982. elements: '.fl-color-picker .fl-color-picker-value',
  6983. presets: FLBuilderConfig.colorPresets ? FLBuilderConfig.colorPresets : [],
  6984. globals: function () {
  6985. var styles = FLBuilderConfig.styles;
  6986. var themeJSON = FLBuilderConfig.themeJSON;
  6987. return {
  6988. bb: styles ? styles.colors : [],
  6989. wp: themeJSON && themeJSON.color.palette.default ? themeJSON.color.palette.default : [],
  6990. theme: themeJSON && themeJSON.color.palette.theme ? themeJSON.color.palette.theme : []
  6991. }
  6992. },
  6993. labels: {
  6994. colorPresets : FLBuilderStrings.colorPresets,
  6995. colorPicker : FLBuilderStrings.colorPicker,
  6996. placeholder : FLBuilderStrings.placeholder,
  6997. removePresetConfirm : FLBuilderStrings.removePresetConfirm,
  6998. noneColorSelected : FLBuilderStrings.noneColorSelected,
  6999. alreadySaved : FLBuilderStrings.alreadySaved,
  7000. noPresets : FLBuilderStrings.noPresets,
  7001. presetAdded : FLBuilderStrings.presetAdded,
  7002. }
  7003. });
  7004. $( FLBuilder.colorPicker ).on( 'presetRemoved presetAdded presetSorted', function( event, data ) {
  7005. FLBuilder.ajax({
  7006. action: 'save_color_presets',
  7007. presets: data.presets
  7008. });
  7009. });
  7010. },
  7011. /* Color Pickers
  7012. ----------------------------------------------------------*/
  7013. /**
  7014. * Initializes gradient picker fields for a settings form.
  7015. *
  7016. * @since 2.2
  7017. * @access private
  7018. * @method _initGradientPickers
  7019. */
  7020. _initGradientPickers: function()
  7021. {
  7022. $( '.fl-builder-settings:visible .fl-gradient-picker', window.parent.document )
  7023. .each( FLBuilder._initGradientPicker );
  7024. },
  7025. /**
  7026. * Initializes a single gradient picker field.
  7027. *
  7028. * @since 2.2
  7029. * @access private
  7030. * @method _initGradientPicker
  7031. */
  7032. _initGradientPicker: function()
  7033. {
  7034. var picker = $( this ),
  7035. type = picker.find( '.fl-gradient-picker-type-select' ),
  7036. angle = picker.find( '.fl-gradient-picker-angle-wrap' ),
  7037. position = picker.find( '.fl-gradient-picker-position' );
  7038. type.on( 'change', function() {
  7039. if ( 'linear' === $( this ).val() ) {
  7040. angle.show();
  7041. position.hide();
  7042. } else {
  7043. angle.hide();
  7044. position.show();
  7045. }
  7046. } );
  7047. },
  7048. /* Single Photo Fields
  7049. ----------------------------------------------------------*/
  7050. /**
  7051. * Initializes photo fields for a settings form.
  7052. *
  7053. * @since 2.2
  7054. * @access private
  7055. * @method _initPhotoFields
  7056. */
  7057. _initPhotoFields: function()
  7058. {
  7059. var selects = $( '.fl-builder-settings:visible', window.parent.document )
  7060. .find( '.fl-photo-field select' );
  7061. selects.on( 'change', FLBuilder._toggleSettingsOnIconChange );
  7062. selects.trigger( 'change' );
  7063. },
  7064. /**
  7065. * Initializes the single photo selector.
  7066. *
  7067. * @since 1.8.6
  7068. * @access private
  7069. * @method _initSinglePhotoSelector
  7070. */
  7071. _initSinglePhotoSelector: function()
  7072. {
  7073. if(FLBuilder._singlePhotoSelector === null) {
  7074. FLBuilder._singlePhotoSelector = wp.media({
  7075. title: FLBuilderStrings.selectPhoto,
  7076. button: { text: FLBuilderStrings.selectPhoto },
  7077. library : { type : FLBuilderConfig.uploadTypes.image },
  7078. multiple: false
  7079. });
  7080. FLBuilder._singlePhotoSelector.on( 'open', FLBuilder._wpmedia_reset_errors );
  7081. window.parent._wpPluploadSettings['defaults']['multipart_params']['fl_upload_type']= 'photo';
  7082. }
  7083. },
  7084. /**
  7085. * Shows the single photo selector.
  7086. *
  7087. * @since 1.0
  7088. * @access private
  7089. * @method _selectSinglePhoto
  7090. */
  7091. _selectSinglePhoto: function()
  7092. {
  7093. FLBuilder._initSinglePhotoSelector();
  7094. FLBuilder._singlePhotoSelector.once('open', $.proxy(FLBuilder._singlePhotoOpened, this));
  7095. FLBuilder._singlePhotoSelector.once('select', $.proxy(FLBuilder._singlePhotoSelected, this));
  7096. FLBuilder._singlePhotoSelector.open();
  7097. },
  7098. /**
  7099. * Callback for when the single photo selector is shown.
  7100. *
  7101. * @since 1.0
  7102. * @access private
  7103. * @method _singlePhotoOpened
  7104. */
  7105. _singlePhotoOpened: function()
  7106. {
  7107. var selection = FLBuilder._singlePhotoSelector.state().get('selection'),
  7108. wrap = $(this).closest('.fl-photo-field'),
  7109. photoField = wrap.find('input[type=hidden]'),
  7110. photo = photoField.val(),
  7111. attachment = null;
  7112. if($(this).hasClass('fl-photo-replace')) {
  7113. selection.reset();
  7114. wrap.addClass('fl-photo-empty');
  7115. photoField.val('');
  7116. }
  7117. else if(photo !== '') {
  7118. attachment = wp.media.attachment(photo);
  7119. attachment.fetch();
  7120. selection.add(attachment ? [attachment] : []);
  7121. }
  7122. else {
  7123. selection.reset();
  7124. }
  7125. },
  7126. /**
  7127. * Callback for when a single photo is selected.
  7128. *
  7129. * @since 1.0
  7130. * @access private
  7131. * @method _singlePhotoSelected
  7132. */
  7133. _singlePhotoSelected: function()
  7134. {
  7135. var photo = FLBuilder._singlePhotoSelector.state().get('selection').first().toJSON(),
  7136. wrap = $(this).closest('.fl-photo-field'),
  7137. photoField = wrap.find('input[type=hidden]'),
  7138. preview = wrap.find('.fl-photo-preview img'),
  7139. srcSelect = wrap.find('select');
  7140. if ( photo.url && photo.url.endsWith( '.svg' ) ) {
  7141. photo.sizes = {
  7142. full: {
  7143. url: photo.url,
  7144. filename: photo.url.split( '/' ).pop(),
  7145. height: '',
  7146. width: ''
  7147. }
  7148. }
  7149. }
  7150. photoField.val(photo.id);
  7151. preview.attr('src', FLBuilder._getPhotoSrc(photo));
  7152. wrap.removeClass('fl-photo-empty').removeClass('fl-photo-no-attachment');
  7153. wrap.find('label.error').remove();
  7154. srcSelect.show();
  7155. srcSelect.html(FLBuilder._getPhotoSizeOptions(photo,srcSelect.val()));
  7156. srcSelect.trigger('change');
  7157. FLBuilderSettingsConfig.attachments[ photo.id ] = photo;
  7158. },
  7159. /**
  7160. * Clears a photo that has been selected in a single photo field.
  7161. *
  7162. * @since 1.6.4.3
  7163. * @access private
  7164. * @method _singlePhotoRemoved
  7165. */
  7166. _singlePhotoRemoved: function()
  7167. {
  7168. FLBuilder._initSinglePhotoSelector();
  7169. var state = FLBuilder._singlePhotoSelector.state(),
  7170. selection = 'undefined' != typeof state ? state.get('selection') : null,
  7171. wrap = $(this).closest('.fl-photo-field'),
  7172. photoField = wrap.find('input[type=hidden]'),
  7173. srcSelect = wrap.find('select');
  7174. if ( selection ) {
  7175. selection.reset();
  7176. }
  7177. wrap.addClass('fl-photo-empty');
  7178. photoField.val('');
  7179. srcSelect.html('<option value="" selected></option>');
  7180. srcSelect.trigger('change');
  7181. },
  7182. /**
  7183. * Returns the src URL for a photo.
  7184. *
  7185. * @since 1.0
  7186. * @access private
  7187. * @method _getPhotoSrc
  7188. * @param {Object} photo A photo data object.
  7189. * @return {String} The src URL for a photo.
  7190. */
  7191. _getPhotoSrc: function(photo)
  7192. {
  7193. if(typeof photo.sizes === 'undefined') {
  7194. return photo.url;
  7195. }
  7196. else if(typeof photo.sizes.thumbnail !== 'undefined') {
  7197. return photo.sizes.thumbnail.url;
  7198. }
  7199. else {
  7200. return photo.sizes.full.url;
  7201. }
  7202. },
  7203. /**
  7204. * Builds the options for a photo size select.
  7205. *
  7206. * @since 1.0
  7207. * @access private
  7208. * @method _getPhotoSizeOptions
  7209. * @param {Object} photo A photo data object.
  7210. * @param {String} selectedSize The selected photo size if one is set.
  7211. * @return {String} The HTML for the photo size options.
  7212. */
  7213. _getPhotoSizeOptions: function( photo, selectedSize )
  7214. {
  7215. var html = '',
  7216. size = null,
  7217. selected = null,
  7218. check = null,
  7219. doneselected = false,
  7220. title = '',
  7221. titleSize = '',
  7222. titles = {
  7223. full : FLBuilderStrings.fullSize,
  7224. large : FLBuilderStrings.large,
  7225. medium : FLBuilderStrings.medium,
  7226. thumbnail : FLBuilderStrings.thumbnail
  7227. };
  7228. if(typeof photo.sizes === 'undefined' || 0 === photo.sizes.length) {
  7229. html += '<option value="' + photo.url + '">' + FLBuilderStrings.fullSize + '</option>';
  7230. }
  7231. else {
  7232. // Check the selected value without the protocol so we get a match if
  7233. // a site has switched to HTTPS since selecting this photo (#641).
  7234. if ( selectedSize ) {
  7235. selectedSize = selectedSize.split(/[\\/]/).pop();
  7236. }
  7237. /**
  7238. * We need to make a check here to be sure that the selectedSize
  7239. * is actually the image we selected, or the previous image
  7240. * otherwise it will default to the 1st in the select (thumbnail)
  7241. */
  7242. selectedverified = false;
  7243. for(sizecheck in photo.sizes) {
  7244. if ( photo.sizes[ sizecheck ].url.split(/[\\/]/).pop() === selectedSize ) {
  7245. selectedverified = true;
  7246. break;
  7247. }
  7248. }
  7249. if ( ! selectedverified ) {
  7250. selectedSize = false;
  7251. }
  7252. for(size in photo.sizes) {
  7253. selected = '';
  7254. if ( 'undefined' != typeof titles[ size ] ) {
  7255. title = titles[ size ];
  7256. }
  7257. else if ( 'undefined' != typeof FLBuilderConfig.customImageSizeTitles[ size ] ) {
  7258. title = FLBuilderConfig.customImageSizeTitles[ size ];
  7259. }
  7260. else {
  7261. title = '';
  7262. }
  7263. if ( ! selectedSize ) {
  7264. if ( typeof FLBuilderConfig.photomodulesize !== 'undefined' && size === FLBuilderConfig.photomodulesize && ! doneselected ) {
  7265. selected = ' selected="selected"';
  7266. doneselected = true;
  7267. } else if( size == FLBuilderConfig.defaultImageSize && ! doneselected ){
  7268. selected = ' selected="selected"';
  7269. doneselected = true;
  7270. }
  7271. } else if( selectedSize === photo.sizes[ size ].url.split(/[\\/]/).pop() && ! doneselected ) {
  7272. selected = ' selected="selected"';
  7273. doneselected = true;
  7274. }
  7275. if ( photo.sizes[size].width && photo.sizes[size].height ) {
  7276. title = title ? title + ' - ' : title
  7277. titleSize = photo.sizes[size].width + ' x ' + photo.sizes[size].height
  7278. }
  7279. html += '<option data-size="' + size + '" value="' + photo.sizes[size].url + '"' + selected + '>' + title + titleSize + '</option>';
  7280. }
  7281. }
  7282. return html;
  7283. },
  7284. /* Multiple Photo Fields
  7285. ----------------------------------------------------------*/
  7286. /**
  7287. * Shows the multiple photo selector.
  7288. *
  7289. * @since 1.0
  7290. * @access private
  7291. * @method _selectMultiplePhotos
  7292. */
  7293. _selectMultiplePhotos: function()
  7294. {
  7295. var wrap = $(this).closest('.fl-multiple-photos-field'),
  7296. photosField = wrap.find('input[type=hidden]'),
  7297. photosFieldVal = photosField.val(),
  7298. parsedVal = photosFieldVal === '' ? '' : FLBuilder._jsonParse(photosFieldVal),
  7299. defaultPostId = wp.media.gallery.defaults.id,
  7300. content = '[gallery ids="-1"]',
  7301. shortcode = null,
  7302. attachments = null,
  7303. selection = null,
  7304. i = null,
  7305. ids = [];
  7306. // Builder the gallery shortcode.
  7307. if ( 'object' == typeof parsedVal ) {
  7308. for ( i in parsedVal ) {
  7309. ids.push( parsedVal[ i ] );
  7310. }
  7311. content = '[gallery ids="'+ ids.join() +'"]';
  7312. }
  7313. shortcode = wp.shortcode.next('gallery', content).shortcode;
  7314. if(_.isUndefined(shortcode.get('id')) && !_.isUndefined(defaultPostId)) {
  7315. shortcode.set('id', defaultPostId);
  7316. }
  7317. // Get the selection object.
  7318. attachments = wp.media.gallery.attachments(shortcode);
  7319. selection = new wp.media.model.Selection(attachments.models, {
  7320. props: attachments.props.toJSON(),
  7321. multiple: true
  7322. });
  7323. selection.gallery = attachments.gallery;
  7324. // Fetch the query's attachments, and then break ties from the
  7325. // query to allow for sorting.
  7326. selection.more().done(function() {
  7327. if ( ! selection.length ) {
  7328. FLBuilder._multiplePhotoSelector.setState( 'gallery-library' );
  7329. }
  7330. // Break ties with the query.
  7331. selection.props.set({ query: false });
  7332. selection.unmirror();
  7333. selection.props.unset('orderby');
  7334. });
  7335. // Destroy the previous gallery frame.
  7336. if(FLBuilder._multiplePhotoSelector) {
  7337. FLBuilder._multiplePhotoSelector.dispose();
  7338. }
  7339. window.parent._wpPluploadSettings['defaults']['multipart_params']['fl_upload_type']= 'photo';
  7340. // Store the current gallery frame.
  7341. FLBuilder._multiplePhotoSelector = wp.media({
  7342. frame: 'post',
  7343. state: $(this).hasClass('fl-multiple-photos-edit') ? 'gallery-edit' : 'gallery-library',
  7344. title: wp.media.view.l10n.editGalleryTitle,
  7345. editing: true,
  7346. multiple: true,
  7347. selection: selection
  7348. }).open();
  7349. $(FLBuilder._multiplePhotoSelector.views.view.el).addClass('fl-multiple-photos-lightbox');
  7350. FLBuilder._multiplePhotoSelector.once('update', $.proxy(FLBuilder._multiplePhotosSelected, this));
  7351. },
  7352. /**
  7353. * Callback for when multiple photos have been selected.
  7354. *
  7355. * @since 1.0
  7356. * @access private
  7357. * @method _multiplePhotosSelected
  7358. * @param {Object} data The photo data object.
  7359. */
  7360. _multiplePhotosSelected: function(data)
  7361. {
  7362. var wrap = $(this).closest('.fl-multiple-photos-field'),
  7363. photosField = wrap.find('input[type=hidden]'),
  7364. count = wrap.find('.fl-multiple-photos-count'),
  7365. photos = [],
  7366. i = 0;
  7367. for( ; i < data.models.length; i++) {
  7368. photos.push(data.models[i].id);
  7369. }
  7370. if(photos.length == 1) {
  7371. count.html('1 ' + FLBuilderStrings.photoSelected);
  7372. }
  7373. else {
  7374. count.html(photos.length + ' ' + FLBuilderStrings.photosSelected);
  7375. }
  7376. wrap.removeClass('fl-multiple-photos-empty');
  7377. wrap.find('label.error').remove();
  7378. photosField.val(JSON.stringify(photos)).trigger('change');
  7379. },
  7380. /* Single Video Fields
  7381. ----------------------------------------------------------*/
  7382. /**
  7383. * Initializes the single video selector.
  7384. *
  7385. * @since 1.10.8
  7386. * @access private
  7387. * @method _initSingleVideoSelector
  7388. */
  7389. _initSingleVideoSelector: function()
  7390. {
  7391. if(FLBuilder._singleVideoSelector === null) {
  7392. var defaultFileExtensions = window.parent._wpPluploadSettings.defaults.filters.mime_types[0].extensions;
  7393. window.parent._wpPluploadSettings['defaults']['multipart_params']['fl_upload_type'] = 'video';
  7394. window.parent._wpPluploadSettings.defaults.filters.mime_types[0].extensions = FLBuilderConfig.uploadTypes.videoTypes;
  7395. FLBuilder._singleVideoSelector = wp.media({
  7396. title: FLBuilderStrings.selectVideo,
  7397. button: { text: FLBuilderStrings.selectVideo },
  7398. library: { type: [ 'video/mp4', 'video/webm' ] },
  7399. multiple: false
  7400. });
  7401. FLBuilder._singleVideoSelector.on( 'open', FLBuilder._wpmedia_reset_errors );
  7402. FLBuilder._singleVideoSelector.on( 'close', function () {
  7403. window.parent._wpPluploadSettings.defaults.filters.mime_types[0].extensions = defaultFileExtensions;
  7404. });
  7405. }
  7406. },
  7407. /**
  7408. * Shows the single video selector.
  7409. *
  7410. * @since 1.0
  7411. * @access private
  7412. * @method _selectSingleVideo
  7413. */
  7414. _selectSingleVideo: function()
  7415. {
  7416. FLBuilder._initSingleVideoSelector();
  7417. FLBuilder._singleVideoSelector.once('select', $.proxy(FLBuilder._singleVideoSelected, this));
  7418. FLBuilder._singleVideoSelector.open();
  7419. },
  7420. /**
  7421. * Callback for when a single video is selected.
  7422. *
  7423. * @since 1.0
  7424. * @access private
  7425. * @method _singleVideoSelected
  7426. */
  7427. _singleVideoSelected: function()
  7428. {
  7429. var video = FLBuilder._singleVideoSelector.state().get('selection').first().toJSON(),
  7430. wrap = $(this).closest('.fl-video-field'),
  7431. image = wrap.find('.fl-video-preview-img'),
  7432. filename = wrap.find('.fl-video-preview-filename'),
  7433. videoField = wrap.find('input[type=hidden]');
  7434. image.html('<span class="dashicons dashicons-media-video"></span>');
  7435. filename.html(video.filename);
  7436. wrap.removeClass('fl-video-empty');
  7437. wrap.find('label.error').remove();
  7438. videoField.val(video.id).trigger('change');
  7439. FLBuilderSettingsConfig.attachments[ video.id ] = video;
  7440. },
  7441. /**
  7442. * Clears a video that has been selected in a single video field.
  7443. *
  7444. * @since 2.1
  7445. * @access private
  7446. * @method _singleVideoRemoved
  7447. */
  7448. _singleVideoRemoved: function()
  7449. {
  7450. FLBuilder._initSingleVideoSelector();
  7451. var state = FLBuilder._singleVideoSelector.state(),
  7452. selection = 'undefined' != typeof state ? state.get('selection') : null,
  7453. wrap = $(this).closest('.fl-video-field'),
  7454. image = wrap.find('.fl-video-preview-img img'),
  7455. filename = wrap.find('.fl-video-preview-filename'),
  7456. videoField = wrap.find('input[type=hidden]');
  7457. if ( selection ) {
  7458. selection.reset();
  7459. }
  7460. image.attr('src', '');
  7461. filename.html('');
  7462. wrap.addClass('fl-video-empty');
  7463. videoField.val('').trigger('change');
  7464. },
  7465. /* Multiple Audios Field
  7466. ----------------------------------------------------------*/
  7467. /**
  7468. * Shows the multiple audio selector.
  7469. *
  7470. * @since 1.0
  7471. * @access private
  7472. * @method _selectMultipleAudios
  7473. */
  7474. _selectMultipleAudios: function()
  7475. {
  7476. var wrap = $(this).closest('.fl-multiple-audios-field'),
  7477. audiosField = wrap.find('input[type=hidden]'),
  7478. audiosFieldVal = audiosField.val(),
  7479. content = audiosFieldVal == '' ? '[playlist ids="-1"]' : '[playlist ids="'+ FLBuilder._jsonParse(audiosFieldVal).join() +'"]',
  7480. shortcode = wp.shortcode.next('playlist', content).shortcode,
  7481. defaultPostId = wp.media.playlist.defaults.id,
  7482. attachments = null,
  7483. selection = null;
  7484. if(_.isUndefined(shortcode.get('id')) && !_.isUndefined(defaultPostId)) {
  7485. shortcode.set('id', defaultPostId);
  7486. }
  7487. attachments = wp.media.playlist.attachments(shortcode);
  7488. selection = new wp.media.model.Selection(attachments.models, {
  7489. props: attachments.props.toJSON(),
  7490. multiple: true
  7491. });
  7492. selection.playlist = attachments.playlist;
  7493. // Fetch the query's attachments, and then break ties from the
  7494. // query to allow for sorting.
  7495. selection.more().done(function() {
  7496. // Break ties with the query.
  7497. selection.props.set({ query: false });
  7498. selection.unmirror();
  7499. selection.props.unset('orderby');
  7500. });
  7501. // Destroy the previous frame.
  7502. if(FLBuilder._multipleAudiosSelector) {
  7503. FLBuilder._multipleAudiosSelector.dispose();
  7504. }
  7505. // Store the current frame.
  7506. FLBuilder._multipleAudiosSelector = wp.media({
  7507. frame: 'post',
  7508. state: $(this).hasClass('fl-multiple-audios-edit') ? 'playlist-edit' : 'playlist-library',
  7509. title: wp.media.view.l10n.editPlaylistTitle,
  7510. editing: true,
  7511. multiple: true,
  7512. selection: selection
  7513. }).open();
  7514. // Hide the default playlist settings since we have them added in the audio settings
  7515. FLBuilder._multipleAudiosSelector.content.get('view').sidebar.unset('playlist');
  7516. FLBuilder._multipleAudiosSelector.on( 'content:render:browse', function( browser ) {
  7517. if ( !browser ) return;
  7518. // Hide Playlist Settings in sidebar
  7519. browser.sidebar.on('ready', function(){
  7520. browser.sidebar.unset('playlist');
  7521. });
  7522. });
  7523. FLBuilder._multipleAudiosSelector.once('update', $.proxy(FLBuilder._multipleAudiosSelected, this));
  7524. },
  7525. /**
  7526. * Callback for when a single/multiple audo is selected.
  7527. *
  7528. * @since 1.0
  7529. * @access private
  7530. * @method _multipleAudiosSelected
  7531. */
  7532. _multipleAudiosSelected: function(data)
  7533. {
  7534. var wrap = $(this).closest('.fl-multiple-audios-field'),
  7535. count = wrap.find('.fl-multiple-audios-count'),
  7536. audioField = wrap.find('input[type=hidden]'),
  7537. audios = [],
  7538. i = 0;
  7539. for( ; i < data.models.length; i++) {
  7540. audios.push(data.models[i].id);
  7541. }
  7542. if(audios.length == 1) {
  7543. count.html('1 ' + FLBuilderStrings.audioSelected);
  7544. }
  7545. else {
  7546. count.html(audios.length + ' ' + FLBuilderStrings.audiosSelected);
  7547. }
  7548. audioField.val(JSON.stringify(audios)).trigger('change');
  7549. wrap.removeClass('fl-multiple-audios-empty');
  7550. wrap.find('label.error').remove();
  7551. },
  7552. /* Icon Fields
  7553. ----------------------------------------------------------*/
  7554. /**
  7555. * Initializes icon fields for a settings form.
  7556. *
  7557. * @since 2.2
  7558. * @access private
  7559. * @method _initIconFields
  7560. */
  7561. _initIconFields: function()
  7562. {
  7563. var inputs = $( '.fl-builder-settings:visible', window.parent.document )
  7564. .find( '.fl-icon-field input' );
  7565. inputs.on( 'change', FLBuilder._toggleSettingsOnIconChange );
  7566. inputs.trigger( 'change' );
  7567. },
  7568. /**
  7569. * Callback for when an icon field changes. If the field
  7570. * isn't empty the specified elements (if any) will be shown.
  7571. *
  7572. * @since 2.2
  7573. * @access private
  7574. * @method _toggleSettingsOnIconChange
  7575. */
  7576. _toggleSettingsOnIconChange: function()
  7577. {
  7578. var input = $( this ),
  7579. val = input.val(),
  7580. show = input.attr( 'data-show' ),
  7581. i = 0;
  7582. if ( typeof show === 'undefined' ) {
  7583. return;
  7584. }
  7585. show = FLBuilder._jsonParse( show );
  7586. FLBuilder._settingsSelectToggle( show.fields, 'hide', '#fl-field-' );
  7587. FLBuilder._settingsSelectToggle( show.sections, 'hide', '#fl-builder-settings-section-' );
  7588. FLBuilder._settingsSelectToggle( show.tabs, 'hide', 'a[href*=fl-builder-settings-tab-', ']' );
  7589. if ( val ) {
  7590. FLBuilder._settingsSelectToggle( show.fields, 'show', '#fl-field-' );
  7591. FLBuilder._settingsSelectToggle( show.sections, 'show', '#fl-builder-settings-section-' );
  7592. FLBuilder._settingsSelectToggle( show.tabs, 'show', 'a[href*=fl-builder-settings-tab-', ']' );
  7593. FLBuilder._calculateSettingsTabsOverflow();
  7594. }
  7595. },
  7596. /**
  7597. * Shows the icon selector.
  7598. *
  7599. * @since 1.0
  7600. * @access private
  7601. * @method _selectIcon
  7602. */
  7603. _selectIcon: function()
  7604. {
  7605. var self = this;
  7606. FLIconSelector.open(function(icon){
  7607. FLBuilder._iconSelected.apply(self, [icon]);
  7608. });
  7609. },
  7610. /**
  7611. * Callback for when an icon is selected.
  7612. *
  7613. * @since 1.0
  7614. * @access private
  7615. * @method _iconSelected
  7616. * @param {String} icon The selected icon's CSS classname.
  7617. */
  7618. _iconSelected: function(icon)
  7619. {
  7620. var wrap = $(this).closest('.fl-icon-field'),
  7621. iconField = wrap.find('input[type=hidden]'),
  7622. iconTag = wrap.find('i'),
  7623. oldIcon = iconTag.attr('data-icon');
  7624. iconField.val(icon).trigger('change');
  7625. iconTag.removeClass(oldIcon);
  7626. iconTag.addClass(icon);
  7627. iconTag.attr('data-icon', icon);
  7628. wrap.removeClass('fl-icon-empty');
  7629. wrap.find('label.error').remove();
  7630. },
  7631. /**
  7632. * Callback for when a selected icon is removed.
  7633. *
  7634. * @since 1.0
  7635. * @access private
  7636. * @method _removeIcon
  7637. */
  7638. _removeIcon: function()
  7639. {
  7640. var wrap = $(this).closest('.fl-icon-field'),
  7641. iconField = wrap.find('input[type=hidden]'),
  7642. iconTag = wrap.find('i');
  7643. iconField.val('').trigger('change');
  7644. iconTag.removeClass();
  7645. iconTag.attr('data-icon', '');
  7646. wrap.addClass('fl-icon-empty');
  7647. },
  7648. /* Settings Form Fields
  7649. ----------------------------------------------------------*/
  7650. /**
  7651. * Shows the settings for a nested form field when the
  7652. * edit link is clicked.
  7653. *
  7654. * @since 1.0
  7655. * @access private
  7656. * @method _formFieldClicked
  7657. */
  7658. _formFieldClicked: function()
  7659. {
  7660. var link = $( this ),
  7661. form = link.closest( '.fl-builder-settings' ),
  7662. type = link.attr( 'data-type' ),
  7663. settings = link.siblings( 'input' ).val(),
  7664. helper = FLBuilder._moduleHelpers[ type ],
  7665. config = FLBuilderSettingsConfig.forms[ type ],
  7666. lightbox = FLBuilder._openNestedSettings( { className: 'fl-builder-lightbox fl-form-field-settings' } );
  7667. if ( '' === settings ) {
  7668. settings = JSON.stringify( FLBuilderSettingsConfig.forms[ type ] );
  7669. }
  7670. // Trigger refresh preview
  7671. FLBuilder.preview?.preview()
  7672. FLBuilderSettingsForms.render( {
  7673. id : type,
  7674. nodeId : form.attr( 'data-node' ),
  7675. nodeSettings : FLBuilder._getSettings( form ),
  7676. settings : FLBuilder._jsonParse( settings.replace( /&#39;/g, "'" ) ),
  7677. lightbox : lightbox,
  7678. rules : helper ? helper.rules : null,
  7679. helper : {
  7680. init: function () {
  7681. if ( helper ) {
  7682. helper.init();
  7683. }
  7684. FLBuilder._initFormFieldSettingsPreview( lightbox );
  7685. }
  7686. },
  7687. }, function() {
  7688. link.attr( 'id', 'fl-' + lightbox._node.attr( 'data-instance-id' ) );
  7689. lightbox._node.find( 'form.fl-builder-settings' ).attr( 'data-type', type );
  7690. FLBuilderResponsiveEditing._switchAllSettingsToCurrentMode();
  7691. } );
  7692. },
  7693. /**
  7694. * Saves the settings for a nested form field when the
  7695. * save button is clicked.
  7696. *
  7697. * @since 1.0
  7698. * @access private
  7699. * @method _saveFormFieldClicked
  7700. * @return {Boolean} Whether the save was successful or not.
  7701. */
  7702. _saveFormFieldClicked: function()
  7703. {
  7704. var form = $( this ).closest( '.fl-builder-settings' );
  7705. var saved = FLBuilder._saveFormFieldSettings( form );
  7706. if ( saved ) {
  7707. FLBuilder._closeNestedSettings();
  7708. } else {
  7709. FLBuilder._toggleSettingsTabErrors();
  7710. }
  7711. },
  7712. /**
  7713. * Initialize settings preview for a form field.
  7714. *
  7715. * @since 2.5
  7716. * @access private
  7717. * @method _initFormFieldSettingsPreview
  7718. * @param {Object} lightbox
  7719. */
  7720. _initFormFieldSettingsPreview: function( lightbox )
  7721. {
  7722. var fields = lightbox._node.find( '.fl-field' );
  7723. var editors = lightbox._node.find( 'textarea.wp-editor-area' );
  7724. fields.find( 'input:not([type=hidden]), textarea' ).on( 'input', FLBuilder._previewFormFieldSettings );
  7725. fields.find( 'input[type=hidden], select' ).on( 'change', FLBuilder._previewFormFieldSettings );
  7726. shapename = fields.find('input[name=shape_name]');
  7727. shapeorig = fields.find('input[name=shape_original]');
  7728. if ( shapename.length > 0 ) {
  7729. shapeorig.hide();
  7730. shapename.on( 'keyup', function() {
  7731. FLBuilder._shapesEdited = true;
  7732. });
  7733. if ( shapename.val().length > 0 && shapeorig.val().length < 1 ) {
  7734. shapeorig.val( shapename.val() );
  7735. }
  7736. }
  7737. if ( 'undefined' !== typeof window.parent.tinyMCE ) {
  7738. editors.each( function ( i, editor ) {
  7739. editor = window.parent.tinyMCE.get( $( editor ).attr( 'id' ) );
  7740. editor.on( 'change', FLBuilder._previewFormFieldSettings );
  7741. editor.on( 'keyup', FLBuilder._previewFormFieldSettings );
  7742. } );
  7743. }
  7744. },
  7745. /**
  7746. * Previews the settings for a nested form field when
  7747. * a setting is changed.
  7748. *
  7749. * @since 2.5
  7750. * @access private
  7751. * @method _previewFormFieldSettings
  7752. */
  7753. _previewFormFieldSettings: function()
  7754. {
  7755. var ele = this.formElement ? this.formElement : this;
  7756. var form = $( ele ).closest( '.fl-builder-settings' );
  7757. var timeout = form.data( 'timeout' );
  7758. if ( timeout ) {
  7759. clearTimeout( timeout );
  7760. }
  7761. timeout = setTimeout( function () {
  7762. FLBuilder._saveFormFieldSettings( form );
  7763. }, 1000 );
  7764. form.data( 'timeout', timeout );
  7765. },
  7766. /**
  7767. * Saves the settings for a nested form field.
  7768. *
  7769. * @since 2.5
  7770. * @access private
  7771. * @method _saveFormFieldSettings
  7772. * @param {Object} form
  7773. * @return {Boolean} Whether the save was successful or not.
  7774. */
  7775. _saveFormFieldSettings: function( form )
  7776. {
  7777. var lightboxId = form.closest( '.fl-lightbox-wrap' ).attr( 'data-instance-id' ),
  7778. type = form.attr( 'data-type' ),
  7779. settings = FLBuilder._getSettings( form ),
  7780. oldSettings = {},
  7781. helper = FLBuilder._moduleHelpers[ type ],
  7782. link = $( '.fl-builder-settings #fl-' + lightboxId, window.parent.document ),
  7783. preview = link.parent().attr( 'data-preview-text' ),
  7784. previewOptions = [],
  7785. activePreview = '',
  7786. previewField = null,
  7787. previewText = '',
  7788. selectPreview = null,
  7789. tmp = document.createElement( 'div' ),
  7790. valid = true;
  7791. if ( preview ) {
  7792. previewOptions = preview.split(',');
  7793. }
  7794. // Handle 'preview_text' fields
  7795. for ( var i = 0; i < previewOptions.length; i++ ) {
  7796. if ( settings[ previewOptions[i] ] ) {
  7797. activePreview = previewOptions[i];
  7798. previewText = settings[ activePreview ];
  7799. previewField = form.find( '#fl-field-' + activePreview );
  7800. selectPreview = $( 'select[name="' + activePreview + '"]', window.parent.document )
  7801. break;
  7802. }
  7803. }
  7804. if ( selectPreview && selectPreview.length > 0 ) {
  7805. previewText = selectPreview.find( 'option[value="' + previewText + '"]' ).text();
  7806. }
  7807. if ( typeof helper !== 'undefined' ) {
  7808. form.find('label.error').remove();
  7809. form.validate().hideErrors();
  7810. valid = form.validate().form();
  7811. if ( valid ) {
  7812. valid = helper.submit();
  7813. }
  7814. }
  7815. if ( valid ) {
  7816. if ( typeof preview !== 'undefined' && typeof previewText !== 'undefined' ) {
  7817. if ( 'icon' === previewField?.data( 'type' ) ) {
  7818. previewText = '<i class="' + previewText + '"></i>';
  7819. }
  7820. else if ( previewText.length > 35 ) {
  7821. tmp.innerHTML = previewText;
  7822. previewText = ( tmp.textContent || tmp.innerText || '' ).replace( /^(.{35}[^\s]*).*/, "$1" ) + '...';
  7823. }
  7824. if( 'filter_meta_label' == preview && ! previewText ) {
  7825. previewText = settings[ 'filter_meta_key' ];
  7826. }
  7827. link.siblings( '.fl-form-field-preview-text' ).html( previewText );
  7828. }
  7829. if ( link.length > 0 ) {
  7830. oldSettings = link.siblings( 'input' ).val().replace( /&#39;/g, "'" );
  7831. if ( '' != oldSettings ) {
  7832. settings = $.extend( FLBuilder._jsonParse( oldSettings ), settings );
  7833. }
  7834. link.siblings( 'input' ).val( JSON.stringify( settings ) ).trigger( 'change' );
  7835. }
  7836. return true;
  7837. }
  7838. return false;
  7839. },
  7840. /* Layout Fields
  7841. ----------------------------------------------------------*/
  7842. /**
  7843. * Callback for when the item of a layout field is clicked.
  7844. *
  7845. * @since 1.0
  7846. * @access private
  7847. * @method _layoutFieldClicked
  7848. */
  7849. _layoutFieldClicked: function()
  7850. {
  7851. var option = $(this);
  7852. option.siblings().removeClass('fl-layout-field-option-selected');
  7853. option.addClass('fl-layout-field-option-selected');
  7854. option.siblings('input').val(option.attr('data-value'));
  7855. },
  7856. /* Link Fields
  7857. ----------------------------------------------------------*/
  7858. /**
  7859. * Initializes all link fields in a settings form.
  7860. *
  7861. * @since 1.3.9
  7862. * @access private
  7863. * @method _initLinkFields
  7864. */
  7865. _initLinkFields: function()
  7866. {
  7867. $('.fl-builder-settings:visible .fl-link-field', window.parent.document)
  7868. .each(FLBuilder._initLinkField);
  7869. },
  7870. /**
  7871. * Initializes a single link field in a settings form.
  7872. *
  7873. * @since 1.3.9
  7874. * @access private
  7875. * @method _initLinkFields
  7876. */
  7877. _initLinkField: function()
  7878. {
  7879. var wrap = $(this),
  7880. searchInput = wrap.find('.fl-link-field-search-input'),
  7881. checkboxes = wrap.find( '.fl-link-field-options-wrap input[type=checkbox]' );
  7882. searchInput.autoSuggest(FLBuilder._ajaxUrl({
  7883. 'fl_action' : 'fl_builder_autosuggest',
  7884. 'fl_as_action' : 'fl_as_links',
  7885. '_wpnonce' : FLBuilderConfig.ajaxNonce
  7886. }), {
  7887. asHtmlID : searchInput.attr('name'),
  7888. selectedItemProp : 'name',
  7889. searchObjProps : 'name',
  7890. minChars : 3,
  7891. keyDelay : 1000,
  7892. fadeOut : false,
  7893. usePlaceholder : true,
  7894. emptyText : FLBuilderStrings.noResultsFound,
  7895. showResultListWhenNoMatch : true,
  7896. queryParam : 'fl_as_query',
  7897. selectionLimit : 1,
  7898. afterSelectionAdd : FLBuilder._updateLinkField,
  7899. formatList: function(data, elem){
  7900. var new_elem = elem.html(data.name + '<span class="type">[' + data.type + ']</span>');
  7901. return new_elem;
  7902. }
  7903. });
  7904. checkboxes.on( 'click', FLBuilder._linkFieldCheckboxClicked );
  7905. },
  7906. /**
  7907. * Updates the value of a link field when a link has been
  7908. * selected from the auto suggest menu.
  7909. *
  7910. * @since 1.3.9
  7911. * @access private
  7912. * @method _updateLinkField
  7913. * @param {Object} element The auto suggest field.
  7914. * @param {Object} item The current selection.
  7915. * @param {Array} selections An array of selected values.
  7916. */
  7917. _updateLinkField: function(element, item, selections)
  7918. {
  7919. var wrap = element.closest('.fl-link-field'),
  7920. search = wrap.find('.fl-link-field-search'),
  7921. searchInput = wrap.find('.fl-link-field-search-input'),
  7922. field = wrap.find('.fl-link-field-input');
  7923. field.val(item.value).trigger('keyup');
  7924. searchInput.autoSuggest('remove', item.value);
  7925. search.hide();
  7926. },
  7927. /**
  7928. * Shows the auto suggest input for a link field.
  7929. *
  7930. * @since 1.3.9
  7931. * @access private
  7932. * @method _linkFieldSelectClicked
  7933. */
  7934. _linkFieldSelectClicked: function()
  7935. {
  7936. var $el = $(this).closest('.fl-link-field').find('.fl-link-field-search');
  7937. $el.show();
  7938. $el.find('input').focus();
  7939. },
  7940. /**
  7941. * Hides the auto suggest input for a link field.
  7942. *
  7943. * @since 1.3.9
  7944. * @access private
  7945. * @method _linkFieldSelectCancelClicked
  7946. */
  7947. _linkFieldSelectCancelClicked: function()
  7948. {
  7949. var $button = $(this);
  7950. $button.parent().hide();
  7951. $button.closest('.fl-link-field').find('input.fl-link-field-input').focus();
  7952. },
  7953. /**
  7954. * Handles when a link field checkbox option is clicked.
  7955. *
  7956. * @since 2.2
  7957. * @access private
  7958. * @method _linkFieldCheckboxClicked
  7959. */
  7960. _linkFieldCheckboxClicked: function()
  7961. {
  7962. var checkbox = $( this ),
  7963. checked = checkbox.is( ':checked' ),
  7964. input = checkbox.siblings( 'input[type=hidden]' ),
  7965. value = '';
  7966. if ( checkbox.hasClass( 'fl-link-field-target-cb' ) ) {
  7967. value = checked ? '_blank' : '_self';
  7968. } else {
  7969. value = checked ? 'yes' : 'no';
  7970. }
  7971. input.val( value );
  7972. },
  7973. /* Font Fields
  7974. ----------------------------------------------------------*/
  7975. /**
  7976. * Initializes all font fields in a settings form.
  7977. *
  7978. * @since 1.6.3
  7979. * @access private
  7980. * @method _initFontFields
  7981. */
  7982. _initFontFields: function(){
  7983. $('.fl-builder-settings:visible .fl-font-field', window.parent.document)
  7984. .each( FLBuilder._initFontField );
  7985. },
  7986. /**
  7987. * Initializes a single font field in a settings form.
  7988. *
  7989. * @since 1.6.3
  7990. * @access private
  7991. * @method _initFontFields
  7992. */
  7993. _initFontField: function(){
  7994. var wrap = $( this ),
  7995. value = wrap.attr( 'data-value' ),
  7996. font = wrap.find( '.fl-font-field-font' ),
  7997. weight = wrap.find( '.fl-font-field-weight' );
  7998. if ( FLBuilderConfig.select2Enabled ) {
  7999. font.select2({width:'100%'})
  8000. .on('select2:open', function(e){
  8001. $('.select2-search__field').attr('placeholder', FLBuilderStrings.placeholderSelect2);
  8002. })
  8003. }
  8004. $('body', window.parent.document).on({
  8005. mouseenter: function () {
  8006. var highlighted_item = jQuery(this).text(),
  8007. group = jQuery(this).parent().parent().attr("aria-label"),
  8008. head = jQuery("head", window.parent.document),
  8009. url = 'https://fonts.googleapis.com/css?family=' + highlighted_item,
  8010. link_id = highlighted_item.toLowerCase().replaceAll( ' ', '-' ),
  8011. parent_id = $(this).parent().parent().parent().attr('id');
  8012. if ( ! parent_id ) {
  8013. return false;
  8014. }
  8015. if( parent_id.indexOf('typographyfont_family') < 0 ) {
  8016. return false;
  8017. }
  8018. if ( 'Google' === group ) {
  8019. linkElement = "<link id='" + link_id + "' rel='stylesheet' href='" + url + "' type='text/css' media='screen'>";
  8020. jQuery(this).css('font-family', '"' + highlighted_item + '"' );
  8021. if ( jQuery( '#' + link_id ).length > 0 ) {
  8022. return false;
  8023. }
  8024. head.append(linkElement);
  8025. }
  8026. }
  8027. }, '.select2-results__option.select2-results__option--highlighted');
  8028. font.on( 'change', function(){
  8029. FLBuilder._getFontWeights( font );
  8030. } );
  8031. if ( value.indexOf( 'family' ) > -1 ) {
  8032. var value = FLBuilder._jsonParse( value );
  8033. var valid = false;
  8034. fonts = FLBuilderFontFamilies;
  8035. Object.keys(fonts.system).forEach(function(name){
  8036. if ( name === value.family ) {
  8037. valid = true
  8038. }
  8039. });
  8040. Object.keys(fonts.google).forEach(function(name){
  8041. if ( name === value.family ) {
  8042. valid = true
  8043. }
  8044. });
  8045. if ( ! valid && 'Default' !== value.family ) {
  8046. value = {
  8047. 'family' : 'Default',
  8048. 'weight' : '400'
  8049. };
  8050. }
  8051. font.val( value.family );
  8052. font.trigger( 'change' );
  8053. if ( weight.find( 'option[value=' + value.weight + ']' ).length ) {
  8054. weight.val( value.weight );
  8055. }
  8056. }
  8057. },
  8058. /**
  8059. * Renders the correct weights list for a respective font.
  8060. *
  8061. * @since 1.6.3
  8062. * @acces private
  8063. * @method _getFontWeights
  8064. * @param {Object} currentFont The font field element.
  8065. */
  8066. _getFontWeights: function( currentFont ){
  8067. var selectWeight = currentFont.closest( '.fl-font-field' ).find( '.fl-font-field-weight' ),
  8068. font = currentFont.val(),
  8069. weight = selectWeight.val(),
  8070. weightMap = FLBuilderConfig.FontWeights,
  8071. weights = {},
  8072. recentList = currentFont.closest( '.fl-font-field' ).find( '.recent-fonts option' )
  8073. selectWeight.html( '' );
  8074. if( recentList.length > 0 ) {
  8075. var exists = $(recentList)
  8076. .filter(function (i, o) { return o.value === font; })
  8077. .length > 0;
  8078. if ( false === exists && 'Default' !== font ) {
  8079. currentFont.closest( '.fl-font-field' ).find( '.recent-fonts' ).append( $('<option>', {
  8080. value: font,
  8081. text: font
  8082. }));
  8083. }
  8084. }
  8085. if ( 'undefined' != typeof FLBuilderFontFamilies.system[ font ] ) {
  8086. weights = FLBuilderFontFamilies.system[ font ].weights;
  8087. } else if ( 'undefined' != typeof FLBuilderFontFamilies.google[ font ] ) {
  8088. weights = FLBuilderFontFamilies.google[ font ];
  8089. } else {
  8090. weights = FLBuilderFontFamilies.default[ font ];
  8091. }
  8092. $.each( weights, function( key, value ){
  8093. var selected = weight === value ? ' selected' : '';
  8094. selectWeight.append( '<option value="' + value + '"' + selected + '>' + weightMap[ value ] + '</option>' );
  8095. } );
  8096. },
  8097. /* Editor Fields
  8098. ----------------------------------------------------------*/
  8099. /**
  8100. * InitializeS TinyMCE when the builder is first loaded.
  8101. *
  8102. * @since 2.0
  8103. * @access private
  8104. * @method _initEditorFields
  8105. */
  8106. _initTinyMCE: function()
  8107. {
  8108. if ( typeof tinymce === 'object' && typeof tinymce.ui.FloatPanel !== 'undefined' ) {
  8109. tinymce.ui.FloatPanel.zIndex = 100100; // Fix zIndex issue in wp 4.8.1
  8110. }
  8111. $( '.fl-builder-hidden-editor', window.parent.document ).each( function() {
  8112. FLBuilder._initEditorField.call( this, window.parent );
  8113. } );
  8114. if ( FLBuilder.UIIFrame.isEnabled() ) {
  8115. $( '.fl-builder-hidden-editor' ).each( function() {
  8116. FLBuilder._initEditorField.call( this, window );
  8117. } );
  8118. }
  8119. if ( 'undefined' !== typeof window.parent.acf ) {
  8120. window.parent.acf.add_filter( 'wysiwyg_tinymce_settings', function( mceInit, id ) {
  8121. return $.extend( {}, tinyMCEPreInit.mceInit.flbuildereditor, mceInit );
  8122. } )
  8123. }
  8124. },
  8125. /**
  8126. * Initialize all TinyMCE editor fields.
  8127. *
  8128. * @since 1.10
  8129. * @access private
  8130. * @method _initEditorFields
  8131. */
  8132. _initEditorFields: function()
  8133. {
  8134. $( '.fl-builder-settings:visible .fl-editor-field', window.parent.document )
  8135. .each( function() {
  8136. FLBuilder._initEditorField.call( this, window.parent );
  8137. } );
  8138. },
  8139. /**
  8140. * Initialize a single TinyMCE editor field.
  8141. *
  8142. * @since 2.0
  8143. * @method _initEditorField
  8144. * @param {Object} The window object to init on.
  8145. */
  8146. _initEditorField: function( win )
  8147. {
  8148. var field = $( this ),
  8149. textarea = field.find( 'textarea' ),
  8150. name = field.attr( 'data-name' ),
  8151. editorId = 'flrich' + new Date().getTime() + '_' + name,
  8152. html = FLBuilderConfig.wp_editor,
  8153. config = win.tinyMCEPreInit,
  8154. buttons = Number( field.attr( 'data-buttons' ) ),
  8155. rows = field.attr( 'data-rows' ),
  8156. init = null,
  8157. wrap = null;
  8158. html = html.replace( /flbuildereditor/g , editorId );
  8159. config = FLBuilder._jsonParse( JSON.stringify( config ).replace( /flbuildereditor/g , editorId ) );
  8160. config = JSONfn.parse( JSONfn.stringify( config ).replace( /flbuildereditor/g , editorId ) );
  8161. textarea.after( html ).remove();
  8162. $( 'textarea#' + editorId, win.document ).val( textarea.val() )
  8163. if ( undefined !== typeof win.tinymce && undefined !== config.mceInit[ editorId ] ) {
  8164. init = config.mceInit[ editorId ];
  8165. init.setup = function (editor) {
  8166. editor.on('SaveContent', function (e) {
  8167. e.content = e.content.replace(/<a href="(\.\.\/){1,2}/g, '<a href="' + FLBuilderConfig.homeUrl + '/' );
  8168. e.content = e.content.replace(/src="(\.\.\/){1,2}/g, 'src="' + FLBuilderConfig.homeUrl + '/' );
  8169. });
  8170. }
  8171. wrap = $( '#wp-' + editorId + '-wrap', win.document );
  8172. wrap.find( 'textarea' ).attr( 'rows', rows );
  8173. wrap.find( 'textarea' ).attr( 'contentEditable', true );
  8174. if ( ! buttons ) {
  8175. wrap.find( '.wp-media-buttons' ).remove();
  8176. }
  8177. if ( ( wrap.hasClass( 'tmce-active' ) || ! config.qtInit.hasOwnProperty( editorId ) ) && ! init.wp_skip_init ) {
  8178. win.tinymce.init( init );
  8179. }
  8180. }
  8181. if ( undefined !== typeof win.quicktags ) {
  8182. win.quicktags( config.qtInit[ editorId ] );
  8183. }
  8184. win.wpActiveEditor = editorId;
  8185. },
  8186. /**
  8187. * Reinitialize all TinyMCE editor fields.
  8188. *
  8189. * @since 2.0
  8190. * @access private
  8191. * @method _reinitEditorFields
  8192. */
  8193. _reinitEditorFields: function()
  8194. {
  8195. if ( ! $( '.fl-lightbox-resizable:visible', window.parent.document ).length ) {
  8196. return;
  8197. }
  8198. // Do this on a timeout so TinyMCE doesn't hold up other operations.
  8199. setTimeout( function() {
  8200. var i, id;
  8201. if ( 'undefined' === typeof window.parent.tinymce ) {
  8202. return;
  8203. }
  8204. for ( i = window.parent.tinymce.editors.length - 1; i > -1 ; i-- ) {
  8205. if ( ! window.parent.tinymce.editors[ i ].inline ) {
  8206. id = window.parent.tinymce.editors[ i ].id;
  8207. window.parent.tinyMCE.execCommand( 'mceRemoveEditor', true, id );
  8208. window.parent.tinyMCE.execCommand( 'mceAddEditor', true, id );
  8209. }
  8210. }
  8211. if ( FLBuilder.preview ) {
  8212. var fields = $( '.fl-field[data-type="editor"]', window.parent.document );
  8213. FLBuilder.preview._initDefaultFieldPreviews( fields );
  8214. }
  8215. }, 1 );
  8216. },
  8217. /**
  8218. * Destroy all TinyMCE editors.
  8219. *
  8220. * @since 1.10.8
  8221. * @method _destroyEditorFields
  8222. */
  8223. _destroyEditorFields: function()
  8224. {
  8225. var i, id;
  8226. if ( 'undefined' === typeof window.parent.tinymce ) {
  8227. return;
  8228. }
  8229. for ( i = window.parent.tinymce.editors.length - 1; i > -1 ; i-- ) {
  8230. if ( ! window.parent.tinymce.editors[ i ].inline ) {
  8231. window.parent.tinyMCE.execCommand( 'mceRemoveEditor', true, window.parent.tinymce.editors[ i ].id );
  8232. }
  8233. }
  8234. $( '.wplink-autocomplete', window.parent.document ).remove();
  8235. $( '.ui-helper-hidden-accessible', window.parent.document ).remove();
  8236. },
  8237. /**
  8238. * Updates all editor fields within a settings form.
  8239. *
  8240. * @since 1.0
  8241. * @access private
  8242. * @method _updateEditorFields
  8243. */
  8244. _updateEditorFields: function()
  8245. {
  8246. var wpEditors = $('.fl-builder-settings:visible textarea.wp-editor-area', window.parent.document);
  8247. wpEditors.each(FLBuilder._updateEditorField);
  8248. },
  8249. /**
  8250. * Updates a single editor field within a settings form.
  8251. * Creates a hidden textarea with the editor content so
  8252. * this field can be saved.
  8253. *
  8254. * @since 1.0
  8255. * @access private
  8256. * @method _updateEditorField
  8257. */
  8258. _updateEditorField: function()
  8259. {
  8260. var textarea = $( this ),
  8261. field = textarea.closest( '.fl-editor-field' ),
  8262. form = textarea.closest( '.fl-builder-settings' ),
  8263. wrap = textarea.closest( '.wp-editor-wrap' ),
  8264. id = textarea.attr( 'id' ),
  8265. setting = field.attr( 'data-name' ),
  8266. editor = typeof window.parent.tinymce == 'undefined' ? false : window.parent.tinymce.get( id ),
  8267. hidden = textarea.siblings( 'textarea[name="' + setting + '"]' ),
  8268. wpautop = field.data( 'wpautop' );
  8269. // Add a hidden textarea if we don't have one.
  8270. if ( 0 === hidden.length ) {
  8271. hidden = $( '<textarea name="' + setting + '"></textarea>', window.parent.document ).hide();
  8272. textarea.after( hidden );
  8273. }
  8274. // Save editor content.
  8275. if ( wpautop ) {
  8276. if ( editor && wrap.hasClass( 'tmce-active' ) ) {
  8277. hidden.val( editor.getContent() );
  8278. }
  8279. else if ( 'undefined' != typeof window.parent.switchEditors ) {
  8280. hidden.val( window.parent.switchEditors.wpautop( textarea.val() ) );
  8281. }
  8282. else {
  8283. hidden.val( textarea.val() );
  8284. }
  8285. }
  8286. else {
  8287. if ( editor && wrap.hasClass( 'tmce-active' ) ) {
  8288. editor.save();
  8289. }
  8290. hidden.val( textarea.val() );
  8291. }
  8292. },
  8293. /* Loop Settings Fields
  8294. ----------------------------------------------------------*/
  8295. /**
  8296. * Initializes all post type fields in a settings form.
  8297. *
  8298. * @since 2.6
  8299. * @access private
  8300. * @method _initPostTypeFields
  8301. */
  8302. _initPostTypeFields: function(){
  8303. $('.fl-builder-settings:visible #fl-field-post_type').each( FLBuilder._initPostTypeField );
  8304. },
  8305. /**
  8306. * @since 2.6
  8307. */
  8308. _initPostTypeField: function() {
  8309. var wrap = $( this ),
  8310. postType = wrap.find('select');
  8311. if ( FLBuilderConfig.select2Enabled ) {
  8312. postType.select2({width:'50%'});
  8313. }
  8314. },
  8315. /**
  8316. * Callback for the data source of loop settings changes.
  8317. *
  8318. * @since 1.10
  8319. * @access private
  8320. * @method _loopDataSourceChange
  8321. */
  8322. _loopDataSourceChange: function()
  8323. {
  8324. var val = $( this ).val();
  8325. $('.fl-loop-data-source', window.parent.document).hide();
  8326. $('.fl-loop-data-source[data-source="' + val + '"]', window.parent.document).show();
  8327. },
  8328. /**
  8329. * Callback for when the post types of a custom query changes.
  8330. *
  8331. * @since 2.6.2
  8332. * @access private
  8333. * @method _customQueryPostTypesChange
  8334. */
  8335. _customQueryPostTypesChange: function()
  8336. {
  8337. var postTypes = $(this).val();
  8338. $('.fl-custom-query-filter').hide();
  8339. for ( val of postTypes ) {
  8340. $('.fl-custom-query-' + val + '-filter').show();
  8341. }
  8342. },
  8343. /**
  8344. * Callback for when the post type of a custom query changes.
  8345. *
  8346. * @since 1.2.3
  8347. * @access private
  8348. * @method _customQueryPostTypeChange
  8349. */
  8350. _customQueryPostTypeChange: function()
  8351. {
  8352. var val = $(this).val();
  8353. $('.fl-custom-query-filter', window.parent.document).hide();
  8354. $('.fl-custom-query-' + val + '-filter', window.parent.document).show();
  8355. },
  8356. /* Ordering Fields
  8357. ----------------------------------------------------------*/
  8358. /**
  8359. * Initializes all ordering fields in a settings form.
  8360. *
  8361. * @since 1.10
  8362. * @access private
  8363. * @method _initOrderingFields
  8364. */
  8365. _initOrderingFields: function()
  8366. {
  8367. $( '.fl-builder-settings:visible .fl-ordering-field-options', window.parent.document )
  8368. .each( FLBuilder._initOrderingField );
  8369. },
  8370. /**
  8371. * Initializes a single ordering field in a settings form.
  8372. *
  8373. * @since 1.10
  8374. * @access private
  8375. * @method _initOrderingField
  8376. */
  8377. _initOrderingField: function()
  8378. {
  8379. $( this ).sortable( {
  8380. items: '.fl-ordering-field-option',
  8381. containment: 'parent',
  8382. tolerance: 'pointer',
  8383. stop: FLBuilder._updateOrderingField
  8384. } );
  8385. },
  8386. /**
  8387. * Updates an ordering field when dragging stops.
  8388. *
  8389. * @since 1.10
  8390. * @access private
  8391. * @method _updateOrderingField
  8392. * @param {Object} e The event object.
  8393. */
  8394. _updateOrderingField: function( e )
  8395. {
  8396. var options = $( e.target ),
  8397. input = options.siblings( 'input[type=hidden]' ),
  8398. value = [];
  8399. options.find( '.fl-ordering-field-option' ).each( function() {
  8400. value.push( $( this ).attr( 'data-key' ) );
  8401. } );
  8402. input.val( JSON.stringify( value ) ).trigger( 'change' );
  8403. },
  8404. /* Text Fields - Add Predefined Value Selector
  8405. ----------------------------------------------------------*/
  8406. /**
  8407. * Callback for when "add value" selectors for text fields changes.
  8408. *
  8409. * @since 1.6.5
  8410. * @access private
  8411. * @method _textFieldAddValueSelectChange
  8412. */
  8413. _textFieldAddValueSelectChange: function()
  8414. {
  8415. var dropdown = $( this ),
  8416. textField = $( 'input[name="' + dropdown.data( 'target' ) + '"]', window.parent.document ),
  8417. currentValue = textField.val(),
  8418. addingValue = dropdown.val(),
  8419. newValue = '';
  8420. // Adding selected value to target text field only once
  8421. if ( -1 == currentValue.indexOf( addingValue ) ) {
  8422. newValue = ( currentValue.trim() + ' ' + addingValue.trim() ).trim();
  8423. textField
  8424. .val( newValue )
  8425. .trigger( 'change' )
  8426. .trigger( 'keyup' );
  8427. }
  8428. // Resetting the selector
  8429. dropdown.val( '' );
  8430. },
  8431. /* Number Fields
  8432. ----------------------------------------------------------*/
  8433. /**
  8434. * @since 2.0
  8435. * @access private
  8436. * @method _onNumberFieldFocus
  8437. */
  8438. _onNumberFieldFocus: function(e) {
  8439. var $input = $(e.currentTarget);
  8440. $input.addClass('mousetrap');
  8441. Mousetrap.bind('up', function() {
  8442. $input.attr('step', 1);
  8443. });
  8444. Mousetrap.bind('down', function() {
  8445. $input.attr('step', 1);
  8446. });
  8447. Mousetrap.bind('shift+up', function() {
  8448. $input.attr('step', 10);
  8449. });
  8450. Mousetrap.bind('shift+down', function() {
  8451. $input.attr('step', 10);
  8452. });
  8453. },
  8454. /**
  8455. * @since 2.0
  8456. * @access private
  8457. * @method _onNumberFieldBlur
  8458. */
  8459. _onNumberFieldBlur: function(e) {
  8460. var $input = $(e.currentTarget);
  8461. $input.attr('step', 'any').removeClass('mousetrap');
  8462. },
  8463. /* Timezone Fields
  8464. ----------------------------------------------------------*/
  8465. /**
  8466. * @since 2.0
  8467. * @access private
  8468. * @method _initTimezoneFields
  8469. */
  8470. _initTimezoneFields: function() {
  8471. $( '.fl-builder-settings:visible .fl-field[data-type=timezone]', window.parent.document )
  8472. .each( FLBuilder._initTimezoneField );
  8473. },
  8474. /**
  8475. * @since 2.0
  8476. * @access private
  8477. * @method _initTimezoneField
  8478. */
  8479. _initTimezoneField: function() {
  8480. var select = $( this ).find( 'select' ),
  8481. value = select.attr( 'data-value' );
  8482. select.find( 'option[value="' + value + '"]' ).prop('selected', true);
  8483. },
  8484. /* Dimension Fields
  8485. ----------------------------------------------------------*/
  8486. /**
  8487. * Initializes all dimension fields in a form.
  8488. *
  8489. * @since 2.2
  8490. * @access private
  8491. * @method _initDimensionFields
  8492. */
  8493. _initDimensionFields: function() {
  8494. var form = $( '.fl-builder-settings:visible', window.parent.document );
  8495. form.find( '.fl-field[data-type=dimension]' ).each( FLBuilder._initDimensionField );
  8496. form.find( '.fl-dimension-field-link' ).on( 'click', FLBuilder._dimensionFieldLinkClicked );
  8497. FLBuilder.addHook( 'responsive-editing-switched', this._initResponsiveDimensionFieldLinking );
  8498. form.find( '.fl-compound-field-setting' ).has( '.fl-dimension-field-link' ).each( FLBuilder._initDimensionFieldLinking );
  8499. },
  8500. /**
  8501. * Initializes a single dimension field.
  8502. *
  8503. * @since 2.2
  8504. * @access private
  8505. * @method _initDimensionField
  8506. */
  8507. _initDimensionField: function() {
  8508. var field = $( this ),
  8509. label = field.find( '.fl-field-label label' ),
  8510. wrap = field.find( '.fl-field-control-wrapper' ),
  8511. icon = '<i class="fl-dimension-field-link fl-tip dashicons dashicons-admin-links" title="Link Values"></i>';
  8512. label.append( icon );
  8513. wrap.prepend( icon );
  8514. FLBuilder._initTipTips();
  8515. FLBuilder._initDimensionFieldLinking.apply( this );
  8516. },
  8517. /**
  8518. * Initializes input linking for a dimension field by
  8519. * linking inputs if they all have the same value.
  8520. *
  8521. * @since 2.2
  8522. * @access private
  8523. * @method _initDimensionFieldLinking
  8524. */
  8525. _initDimensionFieldLinking: function() {
  8526. var field = $( this ),
  8527. icon = field.find( '.fl-dimension-field-link' ),
  8528. inputs = FLBuilder._getDimensionFieldLinkingInputs( field ),
  8529. equal = FLBuilder._dimensionFieldInputsAreEqual( inputs );
  8530. if ( equal ) {
  8531. icon.removeClass( 'dashicons-admin-links' );
  8532. icon.addClass( 'dashicons-editor-unlink' );
  8533. inputs.off( 'input', FLBuilder._dimensionFieldLinkedValueChange );
  8534. inputs.on( 'input', FLBuilder._dimensionFieldLinkedValueChange );
  8535. } else {
  8536. icon.addClass( 'dashicons-admin-links' );
  8537. icon.removeClass( 'dashicons-editor-unlink' );
  8538. }
  8539. },
  8540. /**
  8541. * Initializes input linking for responsive dimension fields
  8542. * when the responsive mode is switched.
  8543. *
  8544. * @since 2.2
  8545. * @access private
  8546. * @method _initDimensionFieldLinking
  8547. */
  8548. _initResponsiveDimensionFieldLinking: function() {
  8549. var form = $( '.fl-builder-settings:visible', window.parent.document );
  8550. form.find( '.fl-field[data-type=dimension]' ).each( FLBuilder._initDimensionFieldLinking );
  8551. },
  8552. /**
  8553. * Handles logic for when dimension fields are linked
  8554. * or unlinked from each other.
  8555. *
  8556. * @since 2.2
  8557. * @access private
  8558. * @method _dimensionFieldLinkClicked
  8559. */
  8560. _dimensionFieldLinkClicked: function() {
  8561. var target = $( this ),
  8562. compound = target.closest( '.fl-compound-field-setting' ),
  8563. field = compound.length ? compound : target.closest( '.fl-field' ),
  8564. icon = field.find( '.fl-dimension-field-link' ),
  8565. linked = icon.hasClass( 'dashicons-editor-unlink' ),
  8566. inputs = FLBuilder._getDimensionFieldLinkingInputs( field );
  8567. icon.toggleClass( 'dashicons-admin-links' );
  8568. icon.toggleClass( 'dashicons-editor-unlink' );
  8569. if ( linked ) {
  8570. inputs.off( 'input', FLBuilder._dimensionFieldLinkedValueChange );
  8571. } else {
  8572. inputs.val( inputs.eq( 0 ).val() ).trigger( 'input' );
  8573. inputs.on( 'input', FLBuilder._dimensionFieldLinkedValueChange );
  8574. }
  8575. },
  8576. /**
  8577. * Updates dimension inputs when a linked input changes.
  8578. *
  8579. * @since 2.2
  8580. * @access private
  8581. * @method _dimensionFieldLinkedValueChange
  8582. */
  8583. _dimensionFieldLinkedValueChange: function() {
  8584. var input = $( this ),
  8585. name = input.attr( 'name' ),
  8586. wrap = input.closest( '.fl-dimension-field-units' ),
  8587. inputs = wrap.find( 'input:not([name="' + name + '"])' );
  8588. inputs.off( 'input', FLBuilder._dimensionFieldLinkedValueChange );
  8589. inputs.val( input.val() ).trigger( 'input' );
  8590. inputs.on( 'input', FLBuilder._dimensionFieldLinkedValueChange );
  8591. },
  8592. /**
  8593. * Returns the inputs for dimension field linking. If this field
  8594. * is responsive, then only returns inputs for the current mode.
  8595. *
  8596. * @since 2.2
  8597. * @access private
  8598. * @method _getDimensionFieldLinkingInputs
  8599. * @param {Object} field
  8600. * @return {Object}
  8601. */
  8602. _getDimensionFieldLinkingInputs: function( field ) {
  8603. var responsive = field.find( '.fl-field-responsive-setting' ).length ? true : false,
  8604. mode = FLBuilderResponsiveEditing._mode,
  8605. inputs = null;
  8606. if ( responsive ) {
  8607. inputs = field.find( '.fl-field-responsive-setting-' + mode + ' input' );
  8608. } else {
  8609. inputs = field.find( '.fl-dimension-field-unit input' );
  8610. }
  8611. return inputs;
  8612. },
  8613. /**
  8614. * Checks to see if all inputs for a dimension field have
  8615. * the same value or not.
  8616. *
  8617. * @since 2.2
  8618. * @access private
  8619. * @method _dimensionFieldInputsAreEqual
  8620. * @param {Object} inputs
  8621. * @return {Boolean}
  8622. */
  8623. _dimensionFieldInputsAreEqual: function( inputs ) {
  8624. var first = inputs.eq( 0 ).val();
  8625. if ( '' === first ) {
  8626. return false;
  8627. }
  8628. for ( var i = 1; i < 4; i++ ) {
  8629. if ( inputs.eq( i ).val() !== first ) {
  8630. return false;
  8631. }
  8632. }
  8633. return true;
  8634. },
  8635. /* Field Popup Sliders
  8636. ----------------------------------------------------------*/
  8637. /**
  8638. * Initializes unit and dimension field popup slider controls.
  8639. *
  8640. * @since 2.2
  8641. * @access private
  8642. * @method _initFieldPopupSliders
  8643. */
  8644. _initFieldPopupSliders: function() {
  8645. var form = $( '.fl-builder-settings:visible', window.parent.document ),
  8646. sliders = form.find( '.fl-field-popup-slider' );
  8647. sliders.each( FLBuilder._initFieldPopupSlider );
  8648. },
  8649. /**
  8650. * Initializes a single popup slider control.
  8651. *
  8652. * @since 2.2
  8653. * @access private
  8654. * @method _initFieldPopupSlider
  8655. */
  8656. _initFieldPopupSlider: function() {
  8657. var body = $( 'body', window.parent.document ),
  8658. wrapper = $( this ),
  8659. slider = wrapper.find( '.fl-field-popup-slider-input' ),
  8660. arrow = wrapper.find( '.fl-field-popup-slider-arrow' ),
  8661. name = wrapper.data( 'input' ),
  8662. input = $( 'input[name="' + name + '"]', window.parent.document );
  8663. input.on( 'click', function() {
  8664. if ( ! slider.hasClass( 'fl-field-popup-slider-init' ) ) {
  8665. slider.slider( {
  8666. value: input.val(),
  8667. slide: function( e, ui ) {
  8668. input.val( ui.value ).trigger( 'input' );
  8669. },
  8670. } );
  8671. input.on( 'input', function() {
  8672. slider.slider( 'value', $( this ).val() );
  8673. } );
  8674. slider.addClass( 'fl-field-popup-slider-init' );
  8675. slider.find( '.ui-slider-handle' ).removeAttr( 'tabindex' );
  8676. }
  8677. FLBuilder._setFieldPopupSliderMinMax( slider );
  8678. FLBuilder._hideFieldPopupSliders();
  8679. body.on( 'mousedown', FLBuilder._hideFieldPopupSliders );
  8680. input.addClass( 'fl-field-popup-slider-focus' );
  8681. wrapper.show();
  8682. var tab = $( '.fl-builder-settings:visible .fl-builder-settings-tab.fl-active', window.parent.document ),
  8683. tabOffset = tab.offset(),
  8684. inputOffset = input.offset(),
  8685. inputWidth = input.width(),
  8686. wrapperOffset = wrapper.offset();
  8687. if ( wrapperOffset.top + wrapper.outerHeight() > tabOffset.top + tab.outerHeight() ) {
  8688. wrapper.addClass( 'fl-field-popup-slider-top' );
  8689. }
  8690. arrow.css( 'left', ( 2 + inputOffset.left - wrapperOffset.left + inputWidth / 2 ) + 'px' );
  8691. } );
  8692. input.on( 'focus', function() {
  8693. FLBuilder._hideFieldPopupSliders();
  8694. } );
  8695. },
  8696. /**
  8697. * Hides all single slider controls.
  8698. *
  8699. * @since 2.2
  8700. * @access private
  8701. * @param {Object} e
  8702. * @method _hideFieldPopupSliders
  8703. */
  8704. _hideFieldPopupSliders: function( e ) {
  8705. var target = e ? $( e.target ) : null,
  8706. body = $( 'body', window.parent.document ),
  8707. sliders = $( '.fl-field-popup-slider:visible', window.parent.document ),
  8708. inputs = $( '.fl-field-popup-slider-focus', window.parent.document );
  8709. if ( target ) {
  8710. if ( target.closest( '.fl-field-popup-slider' ).length ) {
  8711. return;
  8712. } else if ( target.closest( '.fl-field-popup-slider-focus' ).length ) {
  8713. return;
  8714. }
  8715. }
  8716. body.off( 'mousedown', FLBuilder._hideFieldPopupSliders );
  8717. inputs.removeClass( 'fl-field-popup-slider-focus' );
  8718. sliders.hide();
  8719. },
  8720. /**
  8721. * Sets the min/max/step config for a popup slider.
  8722. *
  8723. * @since 2.2
  8724. * @access private
  8725. * @method _setFieldPopupSliderMinMax
  8726. * @param {Object} slider
  8727. */
  8728. _setFieldPopupSliderMinMax: function( slider ) {
  8729. var wrapper = slider.parent(),
  8730. parent = wrapper.parent().parent(),
  8731. select = parent.find( 'select.fl-field-unit-select' ),
  8732. unit = select.val(),
  8733. data = wrapper.data( 'slider' ),
  8734. min = 0,
  8735. max = 100,
  8736. step = 1;
  8737. if ( '' === unit || 'em' === unit || 'rem' === unit ) {
  8738. max = 10;
  8739. step = .1;
  8740. }
  8741. if ( 'object' === typeof data ) {
  8742. min = data.min ? parseFloat( data.min ) : min;
  8743. max = data.max ? parseFloat( data.max ) : max;
  8744. step = data.step ? parseFloat( data.step ) : step;
  8745. if ( select.length && data[ unit ] ) {
  8746. min = data[ unit ].min ? parseFloat( data[ unit ].min ) : min;
  8747. max = data[ unit ].max ? parseFloat( data[ unit ].max ) : max;
  8748. step = data[ unit ].step ? parseFloat( data[ unit ].step ) : step;
  8749. }
  8750. }
  8751. slider.slider( {
  8752. min: min,
  8753. max: max,
  8754. step: step,
  8755. } );
  8756. },
  8757. /* Preset Fields
  8758. ---------------------------------------------------- */
  8759. _initPresetFields: function() {
  8760. var form = $( '.fl-builder-settings:visible', window.parent.document ),
  8761. fields = form.find( '.fl-preset-select-controls' );
  8762. fields.each( FLBuilder._initPresetField );
  8763. },
  8764. _initPresetField: function() {
  8765. var field = $( this ),
  8766. select = field.find('select'),
  8767. presetType = field.data('presets'),
  8768. prefix = field.data('prefix');
  8769. select.on( 'change', FLBuilder._setFormPreset.bind( this, presetType, prefix ) );
  8770. },
  8771. _setFormPreset: function( type, prefix, e ) {
  8772. var value = $( e.currentTarget ).val();
  8773. presetLists = FLBuilderConfig.presets,
  8774. presets = presetLists[type],
  8775. form = $( '.fl-builder-settings:visible', window.parent.document );
  8776. if ( 'undefined' !== presets && 'undefined' !== presets[value] ) {
  8777. var settings = presets[value].settings;
  8778. for( var name in settings ) {
  8779. var value = settings[name],
  8780. input;
  8781. if ( 'undefined' !== typeof prefix && '' !== prefix ) {
  8782. // Prefix setting name
  8783. input = form.find('[name="' + prefix + name + '"]');
  8784. } else {
  8785. input = form.find('[name="' + name + '"]');
  8786. }
  8787. input.val(value).trigger('change').trigger('input');
  8788. }
  8789. }
  8790. },
  8791. /* AJAX
  8792. ----------------------------------------------------------*/
  8793. /**
  8794. * Frontend AJAX for the builder interface.
  8795. *
  8796. * @since 1.0
  8797. * @method ajax
  8798. * @param {Object} data The data for the AJAX request.
  8799. * @param {Function} callback A function to call when the request completes.
  8800. */
  8801. ajax: function(data, callback)
  8802. {
  8803. var prop;
  8804. // Queue this request if one is already in progress.
  8805. if ( FLBuilder._ajaxRequest ) {
  8806. FLBuilder._ajaxQueue.push( {
  8807. data: data,
  8808. callback: callback,
  8809. } );
  8810. return;
  8811. }
  8812. FLBuilder.triggerHook('didBeginAJAX', data );
  8813. // Undefined props don't get sent to the server, so make them null.
  8814. for ( prop in data ) {
  8815. if ( 'undefined' == typeof data[ prop ] ) {
  8816. data[ prop ] = null;
  8817. }
  8818. }
  8819. // Add the ajax nonce to the data.
  8820. data._wpnonce = FLBuilderConfig.ajaxNonce;
  8821. // Send the post id to the server.
  8822. data.post_id = FLBuilderConfig.postId;
  8823. // Tell the server that the builder is active.
  8824. data.fl_builder = 1;
  8825. data.safemode = FLBuilderConfig.safemode
  8826. // Append the builder namespace to the action.
  8827. data.fl_action = data.action;
  8828. // Prevent ModSecurity false positives if our fix is enabled.
  8829. if ( 'undefined' != typeof data.settings ) {
  8830. data.settings = FLBuilder._ajaxModSecFix( $.extend( true, {}, data.settings ) );
  8831. data.settings = FLBuilder._inputVarsCheck( data.settings );
  8832. }
  8833. if ( 'undefined' != typeof data.node_settings ) {
  8834. data.node_settings = FLBuilder._ajaxModSecFix( $.extend( true, {}, data.node_settings ) );
  8835. data.node_settings = FLBuilder._inputVarsCheck( data.node_settings );
  8836. }
  8837. if ( 'undefined' != typeof data.node_preview ) {
  8838. data.node_preview = FLBuilder._ajaxModSecFix( $.extend( true, {}, data.node_preview ) );
  8839. data.node_preview = FLBuilder._inputVarsCheck( data.node_preview );
  8840. }
  8841. if ( 'error' === data.settings || 'error' === data.node_settings || 'error' === data.node_preview ) {
  8842. return 0;
  8843. }
  8844. // Store the data in a single variable to avoid conflicts.
  8845. data = { fl_builder_data: data };
  8846. // Do the ajax call.
  8847. FLBuilder._ajaxRequest = $.post(FLBuilder._ajaxUrl(), data, function(response) {
  8848. if(typeof callback !== 'undefined') {
  8849. callback.call(this, response);
  8850. }
  8851. FLBuilder.triggerHook('didCompleteAJAX', data );
  8852. })
  8853. .always( FLBuilder._ajaxComplete )
  8854. .fail( function( xhr, status, error ){
  8855. msg = false;
  8856. switch(xhr.status) {
  8857. case 403:
  8858. case 409:
  8859. case 418: // Dreamhost trying to be funny
  8860. msg = 'Something you entered has triggered a ' + xhr.status + ' error.<br /><br />This is nearly always due to mod_security settings from your hosting provider.'
  8861. if ( ! window.crash_vars.white_label ) {
  8862. msg += '<br /><br />See this <a target="_blank" style="color: #428bca;font-size:inherit" href="https://docs.wpbeaverbuilder.com/beaver-builder/troubleshooting/common-issues/403-forbidden-or-blocked-error">Knowledge Base</a> article for more info.</br />'
  8863. }
  8864. break;
  8865. }
  8866. if ( msg ) {
  8867. console.log(xhr)
  8868. console.log(error)
  8869. FLBuilder.alert(msg)
  8870. }
  8871. })
  8872. return FLBuilder._ajaxRequest;
  8873. },
  8874. _inputVarsCheck: function( o ) {
  8875. let maxInput = FLBuilderConfig.MaxInputVars || 0;
  8876. let debug = FLBuilderConfig.debug || false;
  8877. let data = JSON.stringify(o).match(/[^\\]":/g) || {};
  8878. let count = data.length;
  8879. if ( 'undefined' != typeof count ) {
  8880. if ( debug ) {
  8881. console.log('Debug: Input Vars ' + count + '/' + maxInput );
  8882. }
  8883. if ( count > maxInput ) {
  8884. FLBuilder.alert( '<h1 style="font-size:2em;text-align:center">Critical Issue</h1><br />The number of settings being saved (' + count + ') exceeds the PHP Max Input Vars setting (' + maxInput + ').<br />Please contact your host to have this value increased, the default is 1000.' );
  8885. console.log( 'Vars Count: ' + count );
  8886. console.log( 'Max Input: ' + maxInput );
  8887. return 'error';
  8888. }
  8889. }
  8890. return o;
  8891. },
  8892. /**
  8893. * Callback for when an AJAX request is complete.
  8894. *
  8895. * @since 1.0
  8896. * @access private
  8897. * @method _ajaxComplete
  8898. */
  8899. _ajaxComplete: function()
  8900. {
  8901. FLBuilder._ajaxRequest = null;
  8902. FLBuilder.hideAjaxLoader();
  8903. if ( FLBuilder._ajaxQueue.length ) {
  8904. var item = FLBuilder._ajaxQueue.shift();
  8905. FLBuilder.ajax( item.data, item.callback );
  8906. }
  8907. },
  8908. /**
  8909. * Returns a URL for an AJAX request.
  8910. *
  8911. * @since 1.0
  8912. * @access private
  8913. * @method _ajaxUrl
  8914. * @param {Object} params An object with key/value pairs for the AJAX query string.
  8915. * @return {String} The AJAX URL.
  8916. */
  8917. _ajaxUrl: function(params)
  8918. {
  8919. var config = FLBuilderConfig,
  8920. url = config.shortlink,
  8921. param = null;
  8922. if(typeof params !== 'undefined') {
  8923. for(param in params) {
  8924. url += url.indexOf('?') > -1 ? '&' : '?';
  8925. url += param + '=' + params[param];
  8926. }
  8927. }
  8928. return url;
  8929. },
  8930. /**
  8931. * Shows the AJAX loading overlay.
  8932. *
  8933. * @since 1.0
  8934. * @method showAjaxLoader
  8935. */
  8936. showAjaxLoader: function()
  8937. {
  8938. if( 0 === $( '.fl-builder-lightbox-loading', window.parent.document ).length ) {
  8939. $( '.fl-builder-loading', window.parent.document ).show();
  8940. }
  8941. },
  8942. /**
  8943. * Hides the AJAX loading overlay.
  8944. *
  8945. * @since 1.0
  8946. * @method hideAjaxLoader
  8947. */
  8948. hideAjaxLoader: function()
  8949. {
  8950. $( '.fl-builder-loading', window.parent.document ).hide();
  8951. },
  8952. /**
  8953. * Fades a node when it is being loaded.
  8954. *
  8955. * @since 1.10
  8956. * @access private
  8957. * @param {String} nodeId
  8958. * @method _showNodeLoading
  8959. */
  8960. _showNodeLoading: function( nodeId )
  8961. {
  8962. var node = $( '.fl-node-' + nodeId );
  8963. node.addClass( 'fl-builder-node-loading' );
  8964. FLBuilder._removeAllOverlays();
  8965. FLBuilder.triggerHook( 'didStartNodeLoading', node );
  8966. },
  8967. /**
  8968. * Brings a node back to 100% opacity when it's done loading.
  8969. *
  8970. * @since 2.0
  8971. * @access private
  8972. * @param {String} nodeId
  8973. * @method _hideNodeLoading
  8974. */
  8975. _hideNodeLoading: function( nodeId )
  8976. {
  8977. var node = $( '.fl-node-' + nodeId );
  8978. node.removeClass( 'fl-builder-node-loading' );
  8979. },
  8980. /**
  8981. * Inserts a placeholder in place of where a node will be
  8982. * that is currently loading.
  8983. *
  8984. * @since 1.10
  8985. * @access private
  8986. * @param {Object} parent
  8987. * @param {Number} position
  8988. * @method _showNodeLoadingPlaceholder
  8989. */
  8990. _showNodeLoadingPlaceholder: function( parent, position )
  8991. {
  8992. var placeholder = $( '<div class="fl-builder-node-loading-placeholder"></div>' );
  8993. // Make sure we only have one placeholder at a time.
  8994. $( '.fl-builder-node-loading-placeholder' ).remove();
  8995. // Get sibling rows.
  8996. if ( parent.hasClass( 'fl-builder-content' ) ) {
  8997. siblings = parent.find( ' > .fl-row' );
  8998. }
  8999. // Get sibling column groups.
  9000. else if ( parent.hasClass( 'fl-row-content' ) ) {
  9001. siblings = parent.find( ' > .fl-col-group, > .fl-module' );
  9002. }
  9003. // Get sibling columns.
  9004. else if ( parent.hasClass( 'fl-col-group' ) ) {
  9005. parent.addClass( 'fl-col-group-has-child-loading' );
  9006. siblings = parent.find( ' > .fl-col' );
  9007. }
  9008. // Get sibling modules.
  9009. else {
  9010. siblings = parent.find( ' > .fl-col-group, > .fl-module' );
  9011. }
  9012. // Add the placeholder.
  9013. if ( 0 === siblings.length || siblings.length == position) {
  9014. parent.append( placeholder );
  9015. }
  9016. else {
  9017. siblings.eq( position ).before( placeholder );
  9018. }
  9019. },
  9020. /**
  9021. * Removes the node loading placeholder for a node.
  9022. *
  9023. * @since 1.10
  9024. * @access private
  9025. * @param {Object} node
  9026. * @method _removeNodeLoadingPlaceholder
  9027. */
  9028. _removeNodeLoadingPlaceholder: function( node )
  9029. {
  9030. var prev = node.prev( '.fl-builder-node-loading-placeholder' ),
  9031. next = node.next( '.fl-builder-node-loading-placeholder' );
  9032. if ( prev.length ) {
  9033. prev.remove();
  9034. } else {
  9035. next.remove();
  9036. }
  9037. },
  9038. /**
  9039. * Base64 encode settings to prevent ModSecurity false
  9040. * positives if our fix is enabled.
  9041. *
  9042. * @since 1.8.4
  9043. * @access private
  9044. * @method _ajaxModSecFix
  9045. */
  9046. _ajaxModSecFix: function( settings )
  9047. {
  9048. var prop;
  9049. if ( FLBuilderConfig.modSecFix && 'undefined' != typeof btoa ) {
  9050. if ( 'string' == typeof settings ) {
  9051. settings = FLBuilder._btoa( settings );
  9052. }
  9053. else {
  9054. for ( prop in settings ) {
  9055. type = typeof settings[ prop ]
  9056. if ( 'string' == type || 'number' == type ) {
  9057. settings[ prop ] = FLBuilder._btoa( settings[ prop ] );
  9058. }
  9059. else if( 'object' == type ) {
  9060. settings[ prop ] = FLBuilder._ajaxModSecFix( settings[ prop ] );
  9061. }
  9062. }
  9063. }
  9064. }
  9065. return settings;
  9066. },
  9067. /**
  9068. * Helper function for _ajaxModSecFix
  9069. * btoa() does not handle utf8/16 characters
  9070. * See: https://stackoverflow.com/questions/30106476/using-javascripts-atob-to-decode-base64-doesnt-properly-decode-utf-8-strings
  9071. *
  9072. * @since 1.10.7
  9073. * @access private
  9074. * @method _btoa
  9075. */
  9076. _btoa: function(str) {
  9077. return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function(match, p1) {
  9078. return String.fromCharCode('0x' + p1);
  9079. }));
  9080. },
  9081. /**
  9082. * @since 1.10.8
  9083. * @access private
  9084. * @method _wpmedia_reset_errors
  9085. */
  9086. _wpmedia_reset_errors: function() {
  9087. $( '.upload-error', window.parent.document ).remove()
  9088. $( '.media-uploader-status', window.parent.document ).removeClass( 'errors' ).hide()
  9089. },
  9090. /* Lightboxes
  9091. ----------------------------------------------------------*/
  9092. /**
  9093. * Initializes the lightboxes for the builder interface.
  9094. *
  9095. * @since 1.0
  9096. * @access private
  9097. * @method _initLightboxes
  9098. */
  9099. _initLightboxes: function()
  9100. {
  9101. /* Main builder lightbox */
  9102. FLBuilder._lightbox = new FLLightbox({
  9103. className: 'fl-builder-lightbox fl-builder-settings-lightbox',
  9104. resizable: true
  9105. });
  9106. FLBuilder._lightbox.on('resized', FLBuilder._calculateSettingsTabsOverflow);
  9107. FLBuilder._lightbox.on('close', FLBuilder._lightboxClosed);
  9108. FLBuilder._lightbox.on('beforeCloseLightbox', FLBuilder._destroyEditorFields);
  9109. /* Actions lightbox */
  9110. FLBuilder._actionsLightbox = new FLLightbox({
  9111. className: 'fl-builder-actions-lightbox'
  9112. });
  9113. },
  9114. /**
  9115. * Shows the settings lightbox.
  9116. *
  9117. * @since 1.0
  9118. * @access private
  9119. * @method _showLightbox
  9120. */
  9121. _showLightbox: function( content )
  9122. {
  9123. if ( ! content ) {
  9124. content = '<div class="fl-builder-lightbox-loading"></div>';
  9125. }
  9126. FLBuilder._lightbox.open( content );
  9127. FLBuilder._initLightboxScrollbars();
  9128. },
  9129. /**
  9130. * Set the content for the settings lightbox.
  9131. *
  9132. * @since 1.0
  9133. * @access private
  9134. * @method _setLightboxContent
  9135. * @param {String} content The HTML content for the lightbox.
  9136. */
  9137. _setLightboxContent: function(content)
  9138. {
  9139. FLBuilder._lightbox.setContent(content);
  9140. },
  9141. /**
  9142. * Initializes the scrollbars for the settings lightbox.
  9143. *
  9144. * @since 1.0
  9145. * @access private
  9146. * @method _initLightboxScrollbars
  9147. */
  9148. _initLightboxScrollbars: function()
  9149. {
  9150. FLBuilder._initScrollbars();
  9151. clearTimeout( FLBuilder._lightboxScrollbarTimeout );
  9152. FLBuilder._lightboxScrollbarTimeout = setTimeout(FLBuilder._initLightboxScrollbars, 500);
  9153. },
  9154. /**
  9155. * Callback to clean things up when the settings lightbox
  9156. * is closed.
  9157. *
  9158. * @since 1.0
  9159. * @access private
  9160. * @method _lightboxClosed
  9161. */
  9162. _lightboxClosed: function()
  9163. {
  9164. FL.Builder.data.getOutlinePanelActions().setActiveNode( false );
  9165. FLBuilder.triggerHook( 'settings-lightbox-closed' );
  9166. FLBuilder._lightbox.empty();
  9167. clearTimeout( FLBuilder._lightboxScrollbarTimeout );
  9168. FLBuilder._lightboxScrollbarTimeout = null;
  9169. },
  9170. /**
  9171. * Shows the actions lightbox.
  9172. *
  9173. * @since 1.0
  9174. * @access private
  9175. * @method _showActionsLightbox
  9176. * @param {Object} settings An object with settings for the lightbox buttons.
  9177. */
  9178. _showActionsLightbox: function(settings)
  9179. {
  9180. var template = wp.template( 'fl-actions-lightbox' );
  9181. // Allow extensions to modify the settings object.
  9182. FLBuilder.triggerHook( 'actions-lightbox-settings', settings );
  9183. // Open the lightbox.
  9184. FLBuilder._actionsLightbox.open( template( settings ) );
  9185. },
  9186. /* Alert Lightboxes
  9187. ----------------------------------------------------------*/
  9188. _checkEnv: function() {
  9189. if ( 'svg' === FLBuilderConfig.fontAwesome ) {
  9190. FLBuilder.alert( FLBuilderStrings.fontAwesome)
  9191. }
  9192. },
  9193. /**
  9194. * Shows the alert lightbox with a message.
  9195. *
  9196. * @since 1.0
  9197. * @method alert
  9198. * @param {String} message The message to show.
  9199. */
  9200. alert: function(message)
  9201. {
  9202. var alert = new FLLightbox({
  9203. className: 'fl-builder-alert-lightbox',
  9204. destroyOnClose: true
  9205. }),
  9206. template = wp.template( 'fl-alert-lightbox' );
  9207. alert.open( template( { message : message } ) );
  9208. },
  9209. crashMessage: function(debug)
  9210. {
  9211. FLLightbox.closeAll();
  9212. var alert = new FLLightbox({
  9213. className: 'fl-builder-alert-lightbox fl-builder-crash-lightbox',
  9214. destroyOnClose: true
  9215. }),
  9216. template = wp.template( 'fl-crash-lightbox' ),
  9217. product = window.crash_vars.product,
  9218. labeled = window.crash_vars.white_label,
  9219. label_txt = window.crash_vars.labeled_txt,
  9220. info = '';
  9221. message = product + ' ' + window.crash_vars.strings.intro;
  9222. info += "<h3 style='font-size:26px;line-height:26px;'>" + window.crash_vars.strings.try + "</h3>";
  9223. info += "<p>" + window.crash_vars.strings.troubleshoot + "</p>";
  9224. info += "<h3 style='font-size:22px;line-height:22px;padding-top:20px;border-top:1px solid black;'>" + window.crash_vars.strings.hand + "</h3>";
  9225. info += "<h3 style='font-size:18px;line-height:18px;'>" + window.crash_vars.strings.step_one + "</h3>";
  9226. info += "<p>" + window.crash_vars.strings.if_contact + "</p>";
  9227. info +="<div><div style='width:49%;float:left;'>"
  9228. info +="<p>MacOS Users:<br />Chrome: View > Developer > JavaScript Console<br />Firefox: Tools > Web Developer > Browser Console<br />Safari: Develop > Show JavaScript console</p>"
  9229. info +="</div>"
  9230. info +="<div style='width:49%;float:right;'>"
  9231. info +="<p>Windows Users:<br />Chrome: Settings > More Tools > Developer > Console<br />Firefox: Menu/Settings > Web Developer > Web Console<br />Edge: Settings and More > More Tools > Console</p>"
  9232. info +="</div></div>"
  9233. info += "<h3 style='font-size:18px;line-height:18px;display:inline-block'>" + window.crash_vars.strings.step_two + "</h3>";
  9234. info +="<p style='display:inline-block;'>" + window.crash_vars.strings.contact + "</p>"
  9235. if ( FLBuilderConfig.MaxInputVars <= 3000 ) {
  9236. info += '<br /><br />The PHP config value max_input_vars is only set to ' + FLBuilderConfig.MaxInputVars + '. If you are using 3rd party addons this could very likely be the cause of this error. [<a class="link" href="https://docs.wpbeaverbuilder.com/beaver-builder/troubleshooting/common-issues/exceeds-php-max-input-vars">doc link</a>].'
  9237. }
  9238. debug = false
  9239. if ( labeled ) {
  9240. info = label_txt
  9241. }
  9242. alert.open( template( { message : message, info: info, debug: debug } ) );
  9243. },
  9244. /**
  9245. * Closes the alert lightbox when a child element is clicked.
  9246. *
  9247. * @since 1.0
  9248. * @access private
  9249. * @method _alertClose
  9250. */
  9251. _alertClose: function()
  9252. {
  9253. FLLightbox.closeParent(this);
  9254. },
  9255. /**
  9256. * Shows the confirm lightbox with a message.
  9257. *
  9258. * @since 1.10
  9259. * @method confirm
  9260. * @param {Object} o The config object that overrides the defaults.
  9261. */
  9262. confirm: function( o )
  9263. {
  9264. var defaults = {
  9265. cssClass: '',
  9266. message : '',
  9267. ok : function(){},
  9268. cancel : function(){},
  9269. strings : {
  9270. 'ok' : FLBuilderStrings.ok,
  9271. 'cancel' : FLBuilderStrings.cancel
  9272. }
  9273. },
  9274. config = $.extend( {}, defaults, ( 'undefined' == typeof o ? {} : o ) )
  9275. lightbox = new FLLightbox({
  9276. className: 'fl-builder-confirm-lightbox fl-builder-alert-lightbox' + config.cssClass,
  9277. destroyOnClose: true
  9278. }),
  9279. template = wp.template( 'fl-confirm-lightbox' );
  9280. lightbox.open( template( config ) );
  9281. lightbox._node.find( '.fl-builder-confirm-ok' ).on( 'click', config.ok );
  9282. lightbox._node.find( '.fl-builder-confirm-cancel' ).on( 'click', config.cancel );
  9283. },
  9284. /* Simple JS hooks similar to WordPress PHP hooks.
  9285. ----------------------------------------------------------*/
  9286. /**
  9287. * Trigger a hook.
  9288. *
  9289. * @since 1.8
  9290. * @method triggerHook
  9291. * @param {String} hook The hook to trigger.
  9292. * @param {Array} args An array of args to pass to the hook.
  9293. */
  9294. triggerHook: function( hook, args )
  9295. {
  9296. $( 'body' ).trigger( 'fl-builder.' + hook, args );
  9297. },
  9298. /**
  9299. * Add a hook.
  9300. *
  9301. * @since 1.8
  9302. * @method addHook
  9303. * @param {String} hook The hook to add.
  9304. * @param {Function} callback A function to call when the hook is triggered.
  9305. */
  9306. addHook: function( hook, callback )
  9307. {
  9308. $( 'body' ).on( 'fl-builder.' + hook, callback );
  9309. },
  9310. /**
  9311. * Remove a hook.
  9312. *
  9313. * @since 1.8
  9314. * @method removeHook
  9315. * @param {String} hook The hook to remove.
  9316. * @param {Function} callback The callback function to remove.
  9317. */
  9318. removeHook: function( hook, callback )
  9319. {
  9320. $( 'body' ).off( 'fl-builder.' + hook, callback );
  9321. },
  9322. /* Console Logging
  9323. ----------------------------------------------------------*/
  9324. /**
  9325. * Logs a message in the console if the console is available.
  9326. *
  9327. * @since 1.4.6
  9328. * @method log
  9329. * @param {String} message The message to log.
  9330. */
  9331. log: function( message )
  9332. {
  9333. if ( 'undefined' == typeof window.console || 'undefined' == typeof window.console.log ) {
  9334. return;
  9335. }
  9336. console.log( message );
  9337. },
  9338. /**
  9339. * Logs an error in the console if the console is available.
  9340. *
  9341. * @since 1.4.6
  9342. * @method logError
  9343. * @param {String} error The error to log.
  9344. */
  9345. logError: function( error, data )
  9346. {
  9347. var message = null;
  9348. if ( 'undefined' == typeof error ) {
  9349. return;
  9350. }
  9351. else if ( 'undefined' != typeof error.stack ) {
  9352. message = error.stack;
  9353. }
  9354. else if ( 'undefined' != typeof error.message ) {
  9355. message = error.message;
  9356. }
  9357. if ( message ) {
  9358. FLBuilder.log( '************************************************************************' );
  9359. FLBuilder.log( FLBuilderStrings.errorMessage );
  9360. FLBuilder.log( message );
  9361. if ( 'undefined' != typeof data && data ) {
  9362. FLBuilder.log( "Debug Info" );
  9363. console.log( data );
  9364. }
  9365. // Show debug data in console.
  9366. $.each( window.crash_vars.vars, function(i,t) {
  9367. console.log(i + ': ' + t)
  9368. })
  9369. FLBuilder.log( '************************************************************************' );
  9370. if ( 'undefined' != typeof data && data ) {
  9371. message = data + "\n" + message
  9372. }
  9373. FLBuilder.crashMessage(message)
  9374. }
  9375. },
  9376. /**
  9377. * Logs a global error in the console if the console is available.
  9378. *
  9379. * @since 1.4.6
  9380. * @method logGlobalError
  9381. * @param {String} message
  9382. * @param {String} file
  9383. * @param {String} line
  9384. * @param {String} col
  9385. * @param {String} error
  9386. */
  9387. logGlobalError: function( message, file, line, col, error )
  9388. {
  9389. FLBuilder.log( '************************************************************************' );
  9390. FLBuilder.log( FLBuilderStrings.errorMessage );
  9391. FLBuilder.log( FLBuilderStrings.globalErrorMessage.replace( '{message}', message ).replace( '{line}', line ).replace( '{file}', file ) );
  9392. if ( 'undefined' != typeof error && 'undefined' != typeof error.stack ) {
  9393. FLBuilder.log( error.stack );
  9394. }
  9395. FLBuilder.log( '************************************************************************' );
  9396. },
  9397. /**
  9398. * Parse JSON with try/catch and print useful debug info on error.
  9399. * @since 2.2.2
  9400. * @param {string} data JSON data
  9401. */
  9402. _jsonParse: function( data ) {
  9403. try {
  9404. data = JSON.parse( data );
  9405. } catch (e) {
  9406. FLBuilder.logError( e, FLBuilder._parseError( data ) );
  9407. }
  9408. return data;
  9409. },
  9410. /**
  9411. * Parse data for php error on 1st line.
  9412. * @since 2.2.2
  9413. * @param {string} data the JSON containing error(s)
  9414. */
  9415. _parseError: function( data ) {
  9416. if( data.indexOf('</head>') ) {
  9417. return 'AJAX returned HTML page instead of data. (Possible 404 or max_input_vars)';
  9418. }
  9419. php = data.match(/^<.*/gm) || false;
  9420. if ( php && php.length > 0 ) {
  9421. var txt = '';
  9422. $.each( php, function(i,t) {
  9423. txt += t
  9424. })
  9425. return $(txt).text();
  9426. }
  9427. return false;
  9428. },
  9429. /**
  9430. * Helper taken from lodash
  9431. * @since 2.2.2
  9432. */
  9433. isUndefined: function(obj) {
  9434. return obj === void 0;
  9435. },
  9436. /**
  9437. * Helper taken from lodash
  9438. * @since 2.2.2
  9439. */
  9440. isBoolean: function(value) {
  9441. return value === true || value === false
  9442. },
  9443. /**
  9444. * Check if the operating system is set to dark mode.
  9445. *
  9446. * @since 2.6
  9447. * @return bool
  9448. */
  9449. _isSystemColorSchemeDark: function() {
  9450. return window.matchMedia && window.matchMedia( '( prefers-color-scheme: dark )' ).matches
  9451. },
  9452. /**
  9453. * Get the static color scheme value even if matching the operating system.
  9454. *
  9455. * @since 2.6
  9456. * @return string (light|dark)
  9457. */
  9458. _getComputedColorScheme: function() {
  9459. const colorScheme = FL.Builder.data.getSystemState().colorScheme
  9460. if ( 'auto' === colorScheme ) {
  9461. return FLBuilder._isSystemColorSchemeDark() ? 'dark' : 'light'
  9462. }
  9463. return colorScheme
  9464. },
  9465. /**
  9466. * Setup color scheme handling
  9467. *
  9468. * @since 2.6
  9469. * @param {string} key
  9470. * @param {any} data
  9471. * @return void
  9472. */
  9473. _initColorScheme: function() {
  9474. FLBuilder._setColorSchemeBodyClasses( FLBuilder._getComputedColorScheme() )
  9475. // Listen for system color scheme changes
  9476. window.matchMedia( '(prefers-color-scheme: dark)' )
  9477. .addEventListener( 'change', FLBuilder._systemColorSchemeChanged )
  9478. },
  9479. /**
  9480. * Listen for changes to the operating system color scheme value.
  9481. *
  9482. * @since 2.6
  9483. * @param MediaQueryListEvent e
  9484. * @param {any} data
  9485. * @return void
  9486. */
  9487. _systemColorSchemeChanged: function( e ) {
  9488. const colorScheme = FL.Builder.data.getSystemState().colorScheme
  9489. if ( 'auto' !== colorScheme ) {
  9490. return
  9491. }
  9492. if ( e.matches ) {
  9493. FLBuilder._setColorSchemeBodyClasses( 'dark' )
  9494. } else {
  9495. FLBuilder._setColorSchemeBodyClasses( 'light' )
  9496. }
  9497. },
  9498. /**
  9499. * Add/Remove appropriate color scheme body classes.
  9500. *
  9501. * @since 2.6
  9502. * @param String name (light|dark|auto)
  9503. * @return void
  9504. */
  9505. _setColorSchemeBodyClasses: function( name ) {
  9506. const parentClasses = window.parent.document.body.classList
  9507. const childClasses = document.body.classList
  9508. let add = name
  9509. // Handle 'auto' value
  9510. if ( 'auto' === name ) {
  9511. add = FLBuilder._getComputedColorScheme()
  9512. }
  9513. const remove = 'dark' === add ? 'light' : 'dark'
  9514. parentClasses.remove( `fl-builder-ui-skin--${remove}`, `fluid-color-scheme-${remove}` );
  9515. childClasses.remove( `fl-builder-ui-skin--${remove}`, `fluid-color-scheme-${remove}` );
  9516. parentClasses.add( `fl-builder-ui-skin--${add}`, `fluid-color-scheme-${add}` );
  9517. childClasses.add( `fl-builder-ui-skin--${add}`, `fluid-color-scheme-${add}` );
  9518. },
  9519. /**
  9520. * Get sandbox data.
  9521. * @since 2.6
  9522. * @param {string} data the JSON containing error(s)
  9523. */
  9524. getSandbox: function (key) {
  9525. if (key in this._sandbox) {
  9526. return this._sandbox[key];
  9527. }
  9528. return false;
  9529. },
  9530. /**
  9531. * Set sandbox data.
  9532. * @since 2.6
  9533. * @param {string} key
  9534. * @param {any} data
  9535. * @return {void}
  9536. */
  9537. setSandbox: function (key, data) {
  9538. this._sandbox[key] = data;
  9539. },
  9540. /**
  9541. * Delete sandbox data.
  9542. * @since 2.6
  9543. * @param {string} key
  9544. * @param {any} data
  9545. * @return {void}
  9546. */
  9547. deleteSandbox: function (key) {
  9548. delete this._sandbox[key];
  9549. },
  9550. };
  9551. /* Start the party!!! */
  9552. $(function(){
  9553. FLBuilder._init();
  9554. });
  9555. })(jQuery);