fl-builder.js 249 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389739073917392739373947395739673977398739974007401740274037404740574067407740874097410741174127413741474157416741774187419742074217422742374247425742674277428742974307431743274337434743574367437743874397440744174427443744474457446744774487449745074517452745374547455745674577458745974607461746274637464746574667467746874697470747174727473747474757476747774787479748074817482748374847485748674877488748974907491749274937494749574967497749874997500750175027503750475057506750775087509751075117512751375147515751675177518751975207521752275237524752575267527752875297530753175327533753475357536753775387539754075417542754375447545754675477548754975507551755275537554755575567557755875597560756175627563756475657566756775687569757075717572757375747575757675777578757975807581758275837584758575867587758875897590759175927593759475957596759775987599760076017602760376047605760676077608760976107611761276137614761576167617761876197620762176227623762476257626762776287629763076317632763376347635763676377638763976407641764276437644764576467647764876497650765176527653765476557656765776587659766076617662766376647665766676677668766976707671767276737674767576767677767876797680768176827683768476857686768776887689769076917692769376947695769676977698769977007701770277037704770577067707770877097710771177127713771477157716771777187719772077217722772377247725772677277728772977307731773277337734773577367737773877397740774177427743774477457746774777487749775077517752775377547755775677577758775977607761776277637764776577667767776877697770777177727773777477757776777777787779778077817782778377847785778677877788778977907791779277937794779577967797779877997800780178027803780478057806780778087809781078117812781378147815781678177818781978207821782278237824782578267827782878297830783178327833783478357836783778387839784078417842784378447845784678477848784978507851785278537854785578567857785878597860786178627863786478657866786778687869787078717872787378747875787678777878787978807881788278837884788578867887788878897890789178927893789478957896789778987899790079017902790379047905790679077908790979107911791279137914791579167917791879197920792179227923792479257926792779287929793079317932793379347935793679377938793979407941794279437944794579467947794879497950795179527953795479557956795779587959796079617962796379647965796679677968796979707971797279737974797579767977797879797980798179827983798479857986798779887989799079917992799379947995799679977998799980008001800280038004800580068007800880098010801180128013801480158016801780188019802080218022802380248025802680278028802980308031803280338034803580368037803880398040804180428043804480458046804780488049805080518052805380548055805680578058805980608061806280638064806580668067806880698070807180728073807480758076807780788079808080818082808380848085808680878088808980908091809280938094809580968097809880998100810181028103810481058106810781088109811081118112811381148115811681178118811981208121812281238124812581268127812881298130813181328133813481358136813781388139814081418142814381448145814681478148814981508151815281538154815581568157815881598160816181628163816481658166816781688169817081718172817381748175817681778178817981808181818281838184818581868187818881898190819181928193819481958196819781988199820082018202820382048205820682078208820982108211821282138214821582168217821882198220822182228223822482258226822782288229823082318232823382348235823682378238823982408241824282438244824582468247824882498250825182528253825482558256825782588259826082618262826382648265826682678268826982708271827282738274827582768277827882798280828182828283828482858286828782888289829082918292829382948295829682978298829983008301830283038304830583068307830883098310831183128313831483158316831783188319832083218322832383248325832683278328832983308331833283338334833583368337833883398340834183428343834483458346834783488349835083518352835383548355835683578358835983608361836283638364836583668367836883698370837183728373837483758376837783788379838083818382838383848385838683878388838983908391839283938394839583968397839883998400840184028403840484058406840784088409841084118412841384148415841684178418841984208421842284238424842584268427842884298430843184328433843484358436843784388439844084418442844384448445844684478448844984508451845284538454845584568457845884598460846184628463846484658466846784688469847084718472847384748475847684778478847984808481848284838484848584868487848884898490849184928493849484958496849784988499850085018502850385048505850685078508850985108511851285138514851585168517851885198520852185228523852485258526852785288529853085318532853385348535853685378538853985408541854285438544854585468547854885498550855185528553855485558556855785588559856085618562856385648565856685678568856985708571857285738574857585768577857885798580858185828583858485858586858785888589859085918592859385948595859685978598859986008601860286038604860586068607860886098610861186128613861486158616861786188619862086218622862386248625862686278628862986308631863286338634863586368637863886398640864186428643864486458646864786488649865086518652865386548655865686578658865986608661866286638664866586668667866886698670867186728673867486758676867786788679868086818682868386848685868686878688868986908691869286938694869586968697869886998700870187028703870487058706870787088709871087118712871387148715871687178718871987208721872287238724872587268727872887298730873187328733873487358736873787388739874087418742874387448745874687478748874987508751875287538754875587568757875887598760876187628763876487658766876787688769877087718772877387748775877687778778877987808781878287838784878587868787878887898790879187928793879487958796879787988799880088018802880388048805880688078808880988108811881288138814881588168817881888198820882188228823882488258826882788288829883088318832883388348835883688378838883988408841884288438844884588468847884888498850885188528853885488558856885788588859886088618862886388648865886688678868886988708871887288738874887588768877887888798880888188828883888488858886888788888889889088918892889388948895889688978898889989008901890289038904890589068907890889098910891189128913891489158916891789188919892089218922892389248925892689278928892989308931893289338934893589368937893889398940894189428943894489458946894789488949895089518952895389548955895689578958895989608961896289638964896589668967896889698970897189728973897489758976897789788979898089818982898389848985898689878988898989908991899289938994899589968997899889999000900190029003900490059006900790089009901090119012901390149015901690179018901990209021902290239024902590269027902890299030903190329033903490359036903790389039904090419042904390449045904690479048904990509051905290539054905590569057905890599060906190629063906490659066
  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. * A jQuery reference to a module element that should be
  28. * added to a new node after it has been rendered.
  29. *
  30. * @since 1.0
  31. * @access private
  32. * @property {Object} _addModuleAfterNodeRender
  33. */
  34. _addModuleAfterNodeRender : null,
  35. /**
  36. * An object that holds data for column resizing.
  37. *
  38. * @since 1.6.4
  39. * @access private
  40. * @property {Object} _colResizeData
  41. */
  42. _colResizeData : null,
  43. /**
  44. * A flag for whether a column is being resized or not.
  45. *
  46. * @since 1.6.4
  47. * @access private
  48. * @property {Boolean} _colResizing
  49. */
  50. _colResizing : false,
  51. /**
  52. * The CSS class of the main content wrapper for the
  53. * current layout that is being worked on.
  54. *
  55. * @since 1.0
  56. * @access private
  57. * @property {String} _contentClass
  58. */
  59. _contentClass : false,
  60. /**
  61. * Whether dragging has been enabled or not.
  62. *
  63. * @since 1.0
  64. * @access private
  65. * @property {Boolean} _dragEnabled
  66. */
  67. _dragEnabled : false,
  68. /**
  69. * Whether an element is currently being dragged or not.
  70. *
  71. * @since 1.0
  72. * @access private
  73. * @property {Boolean} _dragging
  74. */
  75. _dragging : false,
  76. /**
  77. * The initial scroll top of the window when a drag starts.
  78. * Used to reset the scroll top when a drag is cancelled.
  79. *
  80. * @since 1.0
  81. * @access private
  82. * @property {Boolean} _dragging
  83. */
  84. _dragInitialScrollTop : 0,
  85. /**
  86. * The URL to redirect to when a user leaves the builder.
  87. *
  88. * @since 1.0
  89. * @access private
  90. * @property {String} _exitUrl
  91. */
  92. _exitUrl : null,
  93. /**
  94. * An instance of FLBuilderAJAXLayout for rendering
  95. * the layout via AJAX.
  96. *
  97. * @since 1.7
  98. * @property {FLBuilderAJAXLayout} _layout
  99. */
  100. _layout : null,
  101. /**
  102. * A cached copy of custom layout CSS that is used to
  103. * revert changes if the cancel button is clicked.
  104. *
  105. * @since 1.7
  106. * @property {String} _layoutSettingsCSSCache
  107. */
  108. _layoutSettingsCSSCache : null,
  109. /**
  110. * A timeout for throttling custom layout CSS changes.
  111. *
  112. * @since 1.7
  113. * @property {Object} _layoutSettingsCSSTimeout
  114. */
  115. _layoutSettingsCSSTimeout : null,
  116. /**
  117. * An instance of FLLightbox for displaying settings.
  118. *
  119. * @since 1.0
  120. * @access private
  121. * @property {FLLightbox} _lightbox
  122. */
  123. _lightbox : null,
  124. /**
  125. * A timeout for refreshing the height of lightbox scrollbars
  126. * in case the content changes from dynamic settings.
  127. *
  128. * @since 1.0
  129. * @access private
  130. * @property {Object} _lightboxScrollbarTimeout
  131. */
  132. _lightboxScrollbarTimeout : null,
  133. /**
  134. * An array that's used to cache which module settings
  135. * CSS and JS assets have already been loaded so they
  136. * are only loaded once.
  137. *
  138. * @since 1.0
  139. * @access private
  140. * @property {Array} _loadedModuleAssets
  141. */
  142. _loadedModuleAssets : [],
  143. /**
  144. * An object used to store module settings helpers.
  145. *
  146. * @since 1.0
  147. * @access private
  148. * @property {Object} _moduleHelpers
  149. */
  150. _moduleHelpers : {},
  151. /**
  152. * An instance of wp.media used to select multiple photos.
  153. *
  154. * @since 1.0
  155. * @access private
  156. * @property {Object} _multiplePhotoSelector
  157. */
  158. _multiplePhotoSelector : null,
  159. /**
  160. * A jQuery reference to a group that a new column
  161. * should be added to once it's finished rendering.
  162. *
  163. * @since 2.0
  164. * @access private
  165. * @property {Object} _newColParent
  166. */
  167. _newColParent : null,
  168. /**
  169. * The position a column should be added to within
  170. * a group once it finishes rendering.
  171. *
  172. * @since 2.0
  173. * @access private
  174. * @property {Number} _newColPosition
  175. */
  176. _newColPosition : 0,
  177. /**
  178. * A jQuery reference to a row that a new column group
  179. * should be added to once it's finished rendering.
  180. *
  181. * @since 1.0
  182. * @access private
  183. * @property {Object} _newColGroupParent
  184. */
  185. _newColGroupParent : null,
  186. /**
  187. * The position a column group should be added to within
  188. * a row once it finishes rendering.
  189. *
  190. * @since 1.0
  191. * @access private
  192. * @property {Number} _newColGroupPosition
  193. */
  194. _newColGroupPosition : 0,
  195. /**
  196. * A jQuery reference to a new module's parent.
  197. *
  198. * @since 1.7
  199. * @access private
  200. * @property {Object} _newModuleParent
  201. */
  202. _newModuleParent : null,
  203. /**
  204. * The position a new module should be added at once
  205. * it finishes rendering.
  206. *
  207. * @since 1.7
  208. * @access private
  209. * @property {Number} _newModulePosition
  210. */
  211. _newModulePosition : 0,
  212. /**
  213. * The position a row should be added to within
  214. * the layout once it finishes rendering.
  215. *
  216. * @since 1.0
  217. * @access private
  218. * @property {Number} _newRowPosition
  219. */
  220. _newRowPosition : 0,
  221. /**
  222. * The ID of a template that the user has selected.
  223. *
  224. * @since 1.0
  225. * @access private
  226. * @property {Number} _selectedTemplateId
  227. */
  228. _selectedTemplateId : null,
  229. /**
  230. * The type of template that the user has selected.
  231. * Possible values are "core" or "user".
  232. *
  233. * @since 1.0
  234. * @access private
  235. * @property {String} _selectedTemplateType
  236. */
  237. _selectedTemplateType : null,
  238. /**
  239. * An instance of wp.media used to select a single photo.
  240. *
  241. * @since 1.0
  242. * @access private
  243. * @property {Object} _singlePhotoSelector
  244. */
  245. _singlePhotoSelector : null,
  246. /**
  247. * An instance of wp.media used to select a single video.
  248. *
  249. * @since 1.0
  250. * @access private
  251. * @property {Object} _singleVideoSelector
  252. */
  253. _singleVideoSelector : null,
  254. /**
  255. * An instance of wp.media used to select a multiple audio.
  256. *
  257. * @since 1.0
  258. * @access private
  259. * @property {Object} _multipleAudiosSelector
  260. */
  261. _multipleAudiosSelector : null,
  262. /**
  263. * Initializes the builder interface.
  264. *
  265. * @since 1.0
  266. * @access private
  267. * @method _init
  268. */
  269. _init: function()
  270. {
  271. FLBuilder._initJQueryReadyFix();
  272. FLBuilder._initGlobalErrorHandling();
  273. FLBuilder._initPostLock();
  274. FLBuilder._initClassNames();
  275. FLBuilder._initMediaUploader();
  276. FLBuilder._initOverflowFix();
  277. FLBuilder._initScrollbars();
  278. FLBuilder._initLightboxes();
  279. FLBuilder._initDropTargets();
  280. FLBuilder._initSortables();
  281. FLBuilder._initStrings();
  282. FLBuilder._initTipTips();
  283. FLBuilder._initTinyMCE();
  284. FLBuilder._bindEvents();
  285. FLBuilder._bindOverlayEvents();
  286. FLBuilder._setupEmptyLayout();
  287. FLBuilder._highlightEmptyCols();
  288. FLBuilder.addHook('didInitUI', FLBuilder._showTourOrTemplates.bind(FLBuilder) );
  289. FLBuilder.triggerHook('init');
  290. },
  291. /**
  292. * Prevent errors thrown in jQuery's ready function
  293. * from breaking subsequent ready calls.
  294. *
  295. * @since 1.4.6
  296. * @access private
  297. * @method _initJQueryReadyFix
  298. */
  299. _initJQueryReadyFix: function()
  300. {
  301. if ( FLBuilderConfig.debug ) {
  302. return;
  303. }
  304. jQuery.fn.oldReady = jQuery.fn.ready;
  305. jQuery.fn.ready = function( fn ) {
  306. return jQuery.fn.oldReady( function() {
  307. try {
  308. if ( 'function' == typeof fn ) {
  309. fn( $ );
  310. }
  311. }
  312. catch ( e ){
  313. FLBuilder.logError( e );
  314. }
  315. });
  316. };
  317. },
  318. /**
  319. * Try to prevent errors from third party plugins
  320. * from breaking the builder.
  321. *
  322. * @since 1.4.6
  323. * @access private
  324. * @method _initGlobalErrorHandling
  325. */
  326. _initGlobalErrorHandling: function()
  327. {
  328. if ( FLBuilderConfig.debug ) {
  329. return;
  330. }
  331. window.onerror = function( message, file, line, col, error ) {
  332. FLBuilder.logGlobalError( message, file, line, col, error );
  333. return true;
  334. };
  335. },
  336. /**
  337. * Send a wp.heartbeat request to lock editing of this
  338. * post so it can only be edited by the current user.
  339. *
  340. * @since 1.0.6
  341. * @access private
  342. * @method _initPostLock
  343. */
  344. _initPostLock: function()
  345. {
  346. if(typeof wp.heartbeat != 'undefined') {
  347. wp.heartbeat.interval(30);
  348. wp.heartbeat.enqueue('fl_builder_post_lock', {
  349. post_id: FLBuilderConfig.postId
  350. });
  351. }
  352. },
  353. /**
  354. * Initializes html and body classes as well as the
  355. * builder content class for this post.
  356. *
  357. * @since 1.0
  358. * @access private
  359. * @method _initClassNames
  360. */
  361. _initClassNames: function()
  362. {
  363. $('html').addClass('fl-builder-edit');
  364. $('body').addClass('fl-builder');
  365. if(FLBuilderConfig.simpleUi) {
  366. $('body').addClass('fl-builder-simple');
  367. }
  368. FLBuilder._contentClass = '.fl-builder-content-' + FLBuilderConfig.postId;
  369. $( FLBuilder._contentClass ).addClass( 'fl-builder-content-editing' );
  370. },
  371. /**
  372. * Initializes the WordPress media uploader so any files
  373. * uploaded will be attached to the current post.
  374. *
  375. * @since 1.2.2
  376. * @access private
  377. * @method _initMediaUploader
  378. */
  379. _initMediaUploader: function()
  380. {
  381. wp.media.model.settings.post.id = FLBuilderConfig.postId;
  382. },
  383. /**
  384. * Third party themes that set their content wrappers to
  385. * overflow:hidden break builder overlays. We set them
  386. * to overflow:visible while editing.
  387. *
  388. * @since 1.0
  389. * @access private
  390. * @method _initOverflowFix
  391. */
  392. _initOverflowFix: function()
  393. {
  394. $(FLBuilder._contentClass).parents().css('overflow', 'visible');
  395. },
  396. /**
  397. * Initializes Nano Scroller scrollbars for the
  398. * builder interface.
  399. *
  400. * @since 1.0
  401. * @access private
  402. * @method _initScrollbars
  403. */
  404. _initScrollbars: function()
  405. {
  406. $('.fl-nanoscroller').nanoScroller({
  407. alwaysVisible: true,
  408. preventPageScrolling: true,
  409. paneClass: 'fl-nanoscroller-pane',
  410. sliderClass: 'fl-nanoscroller-slider',
  411. contentClass: 'fl-nanoscroller-content'
  412. });
  413. },
  414. /**
  415. * Initializes jQuery sortables for drag and drop.
  416. *
  417. * @since 1.0
  418. * @access private
  419. * @method _initSortables
  420. */
  421. _initSortables: function()
  422. {
  423. var defaults = {
  424. appendTo: 'body',
  425. cursor: 'move',
  426. cursorAt: {
  427. left: 85,
  428. top: 20
  429. },
  430. distance: 1,
  431. helper: FLBuilder._blockDragHelper,
  432. start : FLBuilder._blockDragStart,
  433. sort: FLBuilder._blockDragSort,
  434. change: FLBuilder._blockDragChange,
  435. stop: FLBuilder._blockDragStop,
  436. placeholder: 'fl-builder-drop-zone',
  437. tolerance: 'intersect'
  438. },
  439. rowConnections = '',
  440. columnConnections = '',
  441. moduleConnections = '';
  442. // Module Connections.
  443. if ( 'row' == FLBuilderConfig.userTemplateType || 'column' == FLBuilderConfig.userTemplateType ) {
  444. moduleConnections = FLBuilder._contentClass + ' .fl-col-group-drop-target, ' +
  445. FLBuilder._contentClass + ' .fl-col-drop-target, ' +
  446. FLBuilder._contentClass + ' .fl-col-content';
  447. }
  448. else {
  449. moduleConnections = FLBuilder._contentClass + ' .fl-row-drop-target, ' +
  450. FLBuilder._contentClass + ' .fl-col-group-drop-target, ' +
  451. FLBuilder._contentClass + ' .fl-col-drop-target, ' +
  452. FLBuilder._contentClass + ' .fl-col:not(.fl-node-global) .fl-col-content';
  453. }
  454. // Column Connections.
  455. if ( 'row' == FLBuilderConfig.userTemplateType ) {
  456. columnConnections = FLBuilder._contentClass + ' .fl-col-group-drop-target, ' +
  457. FLBuilder._contentClass + ' .fl-col-drop-target';
  458. }
  459. else {
  460. columnConnections = FLBuilder._contentClass + ' .fl-row-drop-target, ' +
  461. FLBuilder._contentClass + ' .fl-col-group-drop-target, ' +
  462. FLBuilder._contentClass + ' .fl-col-drop-target';
  463. }
  464. // Row Connections.
  465. if ( FLBuilderConfig.nestedColumns ) {
  466. rowConnections = moduleConnections;
  467. }
  468. else if ( 'row' == FLBuilderConfig.userTemplateType ) {
  469. rowConnections = FLBuilder._contentClass + ' .fl-col-group-drop-target, ' +
  470. FLBuilder._contentClass + ' .fl-col-drop-target';
  471. }
  472. else {
  473. rowConnections = FLBuilder._contentClass + ' .fl-row-drop-target, ' +
  474. FLBuilder._contentClass + ' .fl-col-group-drop-target, ' +
  475. FLBuilder._contentClass + ' .fl-col-drop-target';
  476. }
  477. // Row layouts from the builder panel.
  478. $('.fl-builder-rows').sortable($.extend({}, defaults, {
  479. connectWith: rowConnections,
  480. items: '.fl-builder-block-row',
  481. stop: FLBuilder._rowDragStop
  482. }));
  483. // Row templates from the builder panel.
  484. $('.fl-builder-row-templates').sortable($.extend({}, defaults, {
  485. connectWith: FLBuilder._contentClass + ' .fl-row-drop-target',
  486. items: '.fl-builder-block-row-template',
  487. stop: FLBuilder._nodeTemplateDragStop
  488. }));
  489. // Saved rows from the builder panel.
  490. $('.fl-builder-saved-rows').sortable($.extend({}, defaults, {
  491. cancel: '.fl-builder-node-template-actions, .fl-builder-node-template-edit, .fl-builder-node-template-delete',
  492. connectWith: FLBuilder._contentClass + ' .fl-row-drop-target',
  493. items: '.fl-builder-block-saved-row',
  494. stop: FLBuilder._nodeTemplateDragStop
  495. }));
  496. // Saved columns from the builder panel.
  497. $('.fl-builder-saved-columns').sortable($.extend({}, defaults, {
  498. cancel: '.fl-builder-node-template-actions, .fl-builder-node-template-edit, .fl-builder-node-template-delete',
  499. connectWith: columnConnections,
  500. items: '.fl-builder-block-saved-column',
  501. stop: FLBuilder._nodeTemplateDragStop
  502. }));
  503. // Modules from the builder panel.
  504. $('.fl-builder-modules, .fl-builder-widgets').sortable($.extend({}, defaults, {
  505. connectWith: moduleConnections,
  506. items: '.fl-builder-block-module',
  507. stop: FLBuilder._moduleDragStop
  508. }));
  509. // Module templates from the builder panel.
  510. $('.fl-builder-module-templates').sortable($.extend({}, defaults, {
  511. connectWith: moduleConnections,
  512. items: '.fl-builder-block-module-template',
  513. stop: FLBuilder._nodeTemplateDragStop
  514. }));
  515. // Saved modules from the builder panel.
  516. $('.fl-builder-saved-modules').sortable($.extend({}, defaults, {
  517. cancel: '.fl-builder-node-template-actions, .fl-builder-node-template-edit, .fl-builder-node-template-delete',
  518. connectWith: moduleConnections,
  519. items: '.fl-builder-block-saved-module',
  520. stop: FLBuilder._nodeTemplateDragStop
  521. }));
  522. // Rows
  523. $('.fl-row-sortable-proxy').sortable($.extend({}, defaults, {
  524. connectWith: FLBuilder._contentClass + ' .fl-row-drop-target',
  525. helper: FLBuilder._rowDragHelper,
  526. start: FLBuilder._rowDragStart,
  527. stop: FLBuilder._rowDragStop
  528. }));
  529. // Columns
  530. $('.fl-col-sortable-proxy').sortable($.extend({}, defaults, {
  531. connectWith: moduleConnections,
  532. helper: FLBuilder._colDragHelper,
  533. start: FLBuilder._colDragStart,
  534. stop: FLBuilder._colDragStop
  535. }));
  536. // Modules
  537. $(FLBuilder._contentClass + ' .fl-col-content').sortable($.extend({}, defaults, {
  538. connectWith: moduleConnections,
  539. handle: '.fl-module-overlay .fl-block-overlay-actions .fl-block-move',
  540. helper: FLBuilder._moduleDragHelper,
  541. items: '.fl-module, .fl-col-group',
  542. start: FLBuilder._moduleDragStart,
  543. stop: FLBuilder._moduleDragStop
  544. }));
  545. // Drop targets
  546. $(FLBuilder._contentClass + ' .fl-row-drop-target').sortable( defaults );
  547. $(FLBuilder._contentClass + ' .fl-col-group-drop-target').sortable( defaults );
  548. $(FLBuilder._contentClass + ' .fl-col-drop-target').sortable( defaults );
  549. },
  550. /**
  551. * Initializes text translation
  552. *
  553. * @since 1.0
  554. * @access private
  555. * @method _initStrings
  556. */
  557. _initStrings: function()
  558. {
  559. $.validator.messages.required = FLBuilderStrings.validateRequiredMessage;
  560. },
  561. /**
  562. * Binds most of the events for the builder interface.
  563. *
  564. * @since 1.0
  565. * @access private
  566. * @method _bindEvents
  567. */
  568. _bindEvents: function()
  569. {
  570. /* Links */
  571. $excludedLinks = $('.fl-builder-bar a, .fl-builder--content-library-panel a, .fl-page-nav .nav a'); // links in ui shouldn't be disabled.
  572. $('a').not($excludedLinks).on('click', FLBuilder._preventDefault);
  573. $('.fl-page-nav .nav a').on('click', FLBuilder._headerLinkClicked);
  574. $('body').delegate('.fl-builder-content a', 'click', FLBuilder._preventDefault);
  575. $('body').delegate('button.fl-builder-button', 'mouseup', this._buttonMouseUp.bind(this) );
  576. /* Heartbeat */
  577. $(document).on('heartbeat-tick', FLBuilder._initPostLock);
  578. /* Unload Warning */
  579. $(window).on('beforeunload', FLBuilder._warnBeforeUnload);
  580. /* Submenus */
  581. $('body').delegate('.fl-builder-has-submenu', 'click', FLBuilder._submenuParentClicked);
  582. $('body').delegate('.fl-builder-has-submenu a', 'click', FLBuilder._submenuChildClicked);
  583. $('body').delegate('.fl-builder-submenu', 'mouseenter', FLBuilder._submenuMouseenter);
  584. $('body').delegate('.fl-builder-submenu', 'mouseleave', FLBuilder._submenuMouseleave);
  585. $('body').delegate('.fl-builder-submenu .fl-builder-has-submenu', 'mouseenter', FLBuilder._submenuNestedParentMouseenter);
  586. /* Panel */
  587. $('.fl-builder-panel-actions .fl-builder-panel-close').on('click', FLBuilder._closePanel);
  588. $('.fl-builder-blocks-section-title').on('click', FLBuilder._blockSectionTitleClicked);
  589. $('body').delegate('.fl-builder-node-template-actions', 'mousedown', FLBuilder._stopPropagation);
  590. $('body').delegate('.fl-builder-node-template-edit', 'mousedown', FLBuilder._stopPropagation);
  591. $('body').delegate('.fl-builder-node-template-delete', 'mousedown', FLBuilder._stopPropagation);
  592. $('body').delegate('.fl-builder-node-template-edit', 'click', FLBuilder._editNodeTemplateClicked);
  593. $('body').delegate('.fl-builder-node-template-delete', 'click', FLBuilder._deleteNodeTemplateClicked);
  594. $('body').delegate('.fl-builder-block', 'mousedown', FLBuilder._blockDragInit );
  595. $('body').on('mouseup', FLBuilder._blockDragCancel);
  596. /* Actions Lightbox */
  597. $('body').delegate('.fl-builder-actions .fl-builder-cancel-button', 'click', FLBuilder._cancelButtonClicked);
  598. /* Tools Actions */
  599. $('body').delegate('.fl-builder-save-user-template-button', 'click', FLBuilder._saveUserTemplateClicked);
  600. $('body').delegate('.fl-builder-duplicate-layout-button', 'click', FLBuilder._duplicateLayoutClicked);
  601. $('body').delegate('.fl-builder-layout-settings-button', 'click', FLBuilder._layoutSettingsClicked);
  602. $('body').delegate('.fl-builder-layout-settings .fl-builder-settings-save', 'click', FLBuilder._saveLayoutSettingsClicked);
  603. $('body').delegate('.fl-builder-layout-settings .fl-builder-settings-cancel', 'click', FLBuilder._cancelLayoutSettingsClicked);
  604. $('body').delegate('.fl-builder-global-settings-button', 'click', FLBuilder._globalSettingsClicked);
  605. $('body').delegate('.fl-builder-global-settings .fl-builder-settings-save', 'click', FLBuilder._saveGlobalSettingsClicked);
  606. $('body').delegate('.fl-builder-global-settings .fl-builder-settings-cancel', 'click', FLBuilder._cancelLayoutSettingsClicked);
  607. /* Template Panel Tab */
  608. $('body').delegate('.fl-user-template', 'click', FLBuilder._userTemplateClicked);
  609. $('body').delegate('.fl-user-template-edit', 'click', FLBuilder._editUserTemplateClicked);
  610. $('body').delegate('.fl-user-template-delete', 'click', FLBuilder._deleteUserTemplateClicked);
  611. $('body').delegate('.fl-builder-template-replace-button', 'click', FLBuilder._templateReplaceClicked);
  612. $('body').delegate('.fl-builder-template-append-button', 'click', FLBuilder._templateAppendClicked);
  613. $('body').delegate('.fl-builder-template-actions .fl-builder-cancel-button', 'click', FLBuilder._templateCancelClicked);
  614. /* User Template Settings */
  615. $('body').delegate('.fl-builder-user-template-settings .fl-builder-settings-save', 'click', FLBuilder._saveUserTemplateSettings);
  616. /* Help Actions */
  617. $('body').delegate('.fl-builder-help-tour-button', 'click', FLBuilder._startHelpTour);
  618. $('body').delegate('.fl-builder-knowledge-base-button', 'click', FLBuilder._viewKnowledgeBaseClicked);
  619. $('body').delegate('.fl-builder-forums-button', 'click', FLBuilder._visitForumsClicked);
  620. /* Welcome Actions */
  621. $('body').delegate('.fl-builder-no-tour-button', 'click', FLBuilder._noTourButtonClicked);
  622. $('body').delegate('.fl-builder-yes-tour-button', 'click', FLBuilder._yesTourButtonClicked);
  623. /* Alert Lightbox */
  624. $('body').delegate('.fl-builder-alert-close', 'click', FLBuilder._alertClose);
  625. /* General Overlays */
  626. $('body').delegate('.fl-block-overlay', 'contextmenu', FLBuilder._onContextmenu);
  627. /* Rows */
  628. $('body').delegate('.fl-row-overlay .fl-block-remove', 'click', FLBuilder._deleteRowClicked);
  629. $('body').delegate('.fl-row-overlay .fl-block-copy', 'click', FLBuilder._rowCopyClicked);
  630. $('body').delegate('.fl-row-overlay .fl-block-move', 'mousedown', FLBuilder._rowDragInit);
  631. $('body').delegate('.fl-row-overlay .fl-block-settings', 'click', FLBuilder._rowSettingsClicked);
  632. $('body').delegate('.fl-row-overlay', 'click', FLBuilder._rowSettingsClicked);
  633. $('body').delegate('.fl-builder-row-settings .fl-builder-settings-save', 'click', FLBuilder._saveSettings);
  634. /* Rows Submenu */
  635. $('body').delegate('.fl-block-col-submenu .fl-block-row-reset', 'click', FLBuilder._resetRowWidthClicked);
  636. /* Columns */
  637. $('body').delegate('.fl-col-overlay .fl-block-move', 'mousedown', FLBuilder._colDragInit);
  638. $('body').delegate('.fl-block-col-copy', 'click', FLBuilder._copyColClicked);
  639. $('body').delegate('.fl-col-overlay .fl-block-remove', 'click', FLBuilder._deleteColClicked);
  640. $('body').delegate('.fl-col-overlay .fl-block-settings', 'click', FLBuilder._colSettingsClicked);
  641. $('body').delegate('.fl-col-overlay', 'click', FLBuilder._colSettingsClicked);
  642. $('body').delegate('.fl-builder-col-settings .fl-builder-settings-save', 'click', FLBuilder._saveSettings);
  643. /* Columns Submenu */
  644. $('body').delegate('.fl-block-col-submenu .fl-block-col-move', 'mousedown', FLBuilder._colDragInit);
  645. $('body').delegate('.fl-block-col-submenu .fl-block-col-edit', 'click', FLBuilder._colSettingsClicked);
  646. $('body').delegate('.fl-block-col-submenu .fl-block-col-delete', 'click', FLBuilder._deleteColClicked);
  647. $('body').delegate('.fl-block-col-submenu .fl-block-col-reset', 'click', FLBuilder._resetColumnWidthsClicked);
  648. $('body').delegate('.fl-block-col-submenu li', 'mouseenter', FLBuilder._showColHighlightGuide);
  649. $('body').delegate('.fl-block-col-submenu li', 'mouseleave', FLBuilder._removeColHighlightGuides);
  650. /* Columns Submenu (Parent Column) */
  651. $('body').delegate('.fl-block-col-submenu .fl-block-col-move-parent', 'mousedown', FLBuilder._colDragInit);
  652. $('body').delegate('.fl-block-col-submenu .fl-block-col-edit-parent', 'click', FLBuilder._colSettingsClicked);
  653. /* Modules */
  654. $('body').delegate('.fl-module-overlay .fl-block-remove', 'click', FLBuilder._deleteModuleClicked);
  655. $('body').delegate('.fl-module-overlay .fl-block-copy', 'click', FLBuilder._moduleCopyClicked);
  656. $('body').delegate('.fl-module-overlay .fl-block-move', 'mousedown', FLBuilder._blockDragInit);
  657. $('body').delegate('.fl-module-overlay .fl-block-settings', 'click', FLBuilder._moduleSettingsClicked);
  658. $('body').delegate('.fl-module-overlay', 'click', FLBuilder._moduleSettingsClicked);
  659. $('body').delegate('.fl-builder-module-settings .fl-builder-settings-save', 'click', FLBuilder._saveModuleClicked);
  660. $('body').delegate('.fl-module-overlay .fl-block-col-settings', 'click', FLBuilder._colSettingsClicked);
  661. /* Node Templates */
  662. $('body').delegate('.fl-builder-settings-save-as', 'click', FLBuilder._showNodeTemplateSettings);
  663. $('body').delegate('.fl-builder-node-template-settings .fl-builder-settings-save', 'click', FLBuilder._saveNodeTemplate);
  664. /* Settings */
  665. $('body').delegate('.fl-builder-settings-tabs a', 'click', FLBuilder._settingsTabClicked);
  666. $('body').delegate('.fl-builder-settings-tabs a', 'show', FLBuilder._calculateSettingsTabsOverflow);
  667. $('body').delegate('.fl-builder-settings-tabs a', 'hide', FLBuilder._calculateSettingsTabsOverflow);
  668. $('body').delegate('.fl-builder-settings-cancel', 'click', FLBuilder._settingsCancelClicked);
  669. /* Settings Tabs Overflow menu */
  670. $('body').delegate('.fl-builder-settings-tabs-overflow-menu > a', 'click', FLBuilder._settingsTabsToOverflowMenuItemClicked.bind(this));
  671. $('body').delegate('.fl-builder-settings-tabs-more', 'click', FLBuilder._toggleTabsOverflowMenu.bind(this) );
  672. $('body').delegate('.fl-builder-settings-tabs-overflow-click-mask', 'click', FLBuilder._hideTabsOverflowMenu.bind(this));
  673. /* Tooltips */
  674. $('body').delegate('.fl-help-tooltip-icon', 'mouseover', FLBuilder._showHelpTooltip);
  675. $('body').delegate('.fl-help-tooltip-icon', 'mouseout', FLBuilder._hideHelpTooltip);
  676. /* Multiple Fields */
  677. $('body').delegate('.fl-builder-field-add', 'click', FLBuilder._addFieldClicked);
  678. $('body').delegate('.fl-builder-field-copy', 'click', FLBuilder._copyFieldClicked);
  679. $('body').delegate('.fl-builder-field-delete', 'click', FLBuilder._deleteFieldClicked);
  680. /* Select Fields */
  681. $('body').delegate('.fl-builder-settings-fields select', 'change', FLBuilder._settingsSelectChanged);
  682. /* Photo Fields */
  683. $('body').delegate('.fl-photo-field .fl-photo-select', 'click', FLBuilder._selectSinglePhoto);
  684. $('body').delegate('.fl-photo-field .fl-photo-edit', 'click', FLBuilder._selectSinglePhoto);
  685. $('body').delegate('.fl-photo-field .fl-photo-replace', 'click', FLBuilder._selectSinglePhoto);
  686. $('body').delegate('.fl-photo-field .fl-photo-remove', 'click', FLBuilder._singlePhotoRemoved);
  687. /* Multiple Photo Fields */
  688. $('body').delegate('.fl-multiple-photos-field .fl-multiple-photos-select', 'click', FLBuilder._selectMultiplePhotos);
  689. $('body').delegate('.fl-multiple-photos-field .fl-multiple-photos-edit', 'click', FLBuilder._selectMultiplePhotos);
  690. $('body').delegate('.fl-multiple-photos-field .fl-multiple-photos-add', 'click', FLBuilder._selectMultiplePhotos);
  691. /* Video Fields */
  692. $('body').delegate('.fl-video-field .fl-video-select', 'click', FLBuilder._selectSingleVideo);
  693. $('body').delegate('.fl-video-field .fl-video-replace', 'click', FLBuilder._selectSingleVideo);
  694. $('body').delegate('.fl-video-field .fl-video-remove', 'click', FLBuilder._singleVideoRemoved);
  695. /* Multiple Audio Fields */
  696. $('body').delegate('.fl-multiple-audios-field .fl-multiple-audios-select', 'click', FLBuilder._selectMultipleAudios);
  697. $('body').delegate('.fl-multiple-audios-field .fl-multiple-audios-edit', 'click', FLBuilder._selectMultipleAudios);
  698. $('body').delegate('.fl-multiple-audios-field .fl-multiple-audios-add', 'click', FLBuilder._selectMultipleAudios);
  699. /* Icon Fields */
  700. $('body').delegate('.fl-icon-field .fl-icon-select', 'click', FLBuilder._selectIcon);
  701. $('body').delegate('.fl-icon-field .fl-icon-replace', 'click', FLBuilder._selectIcon);
  702. $('body').delegate('.fl-icon-field .fl-icon-remove', 'click', FLBuilder._removeIcon);
  703. /* Settings Form Fields */
  704. $('body').delegate('.fl-form-field .fl-form-field-edit', 'click', FLBuilder._formFieldClicked);
  705. $('body').delegate('.fl-form-field-settings .fl-builder-settings-save', 'click', FLBuilder._saveFormFieldClicked);
  706. /* Layout Fields */
  707. $('body').delegate('.fl-layout-field-option', 'click', FLBuilder._layoutFieldClicked);
  708. /* Links Fields */
  709. $('body').delegate('.fl-link-field-select', 'click', FLBuilder._linkFieldSelectClicked);
  710. $('body').delegate('.fl-link-field-search-cancel', 'click', FLBuilder._linkFieldSelectCancelClicked);
  711. /* Loop Settings Fields */
  712. $('body').delegate('.fl-loop-data-source-select select[name=data_source]', 'change', FLBuilder._loopDataSourceChange);
  713. $('body').delegate('.fl-custom-query select[name=post_type]', 'change', FLBuilder._customQueryPostTypeChange);
  714. /* Text Fields - Add Predefined Value Selector */
  715. $('body').delegate('.fl-text-field-add-value', 'change', FLBuilder._textFieldAddValueSelectChange);
  716. /* Number Fields */
  717. $('body').delegate('.fl-field input[type=number]', 'focus', FLBuilder._onNumberFieldFocus );
  718. $('body').delegate('.fl-field input[type=number]', 'blur', FLBuilder._onNumberFieldBlur );
  719. // Live Preview
  720. FLBuilder.addHook( 'didCompleteAJAX', FLBuilder._refreshSettingsPreviewReference );
  721. FLBuilder.addHook( 'didRenderLayoutComplete', FLBuilder._refreshSettingsPreviewReference );
  722. },
  723. /**
  724. * Remove events when ending the edit session
  725. * @since 2.0
  726. * @access private
  727. */
  728. _unbindEvents: function() {
  729. $('a').off('click', FLBuilder._preventDefault);
  730. $('.fl-page-nav .nav a').off('click', FLBuilder._headerLinkClicked);
  731. $('body').undelegate('.fl-builder-content a', 'click', FLBuilder._preventDefault);
  732. },
  733. /**
  734. * Rebind events when restarting the edit session
  735. * @since 2.1.2.3
  736. * @access private
  737. */
  738. _rebindEvents: function() {
  739. $('a').on('click', FLBuilder._preventDefault);
  740. $('.fl-page-nav .nav a').on('click', FLBuilder._headerLinkClicked);
  741. $('body').delegate('.fl-builder-content a', 'click', FLBuilder._preventDefault);
  742. },
  743. /**
  744. * Binds the events for overlays that appear when
  745. * mousing over a row, column or module.
  746. *
  747. * @since 1.0
  748. * @access private
  749. * @method _bindOverlayEvents
  750. */
  751. _bindOverlayEvents: function()
  752. {
  753. var content = $(FLBuilder._contentClass);
  754. content.delegate('.fl-row', 'mouseenter', FLBuilder._rowMouseenter);
  755. content.delegate('.fl-row', 'mouseleave', FLBuilder._rowMouseleave);
  756. content.delegate('.fl-row-overlay', 'mouseleave', FLBuilder._rowMouseleave);
  757. content.delegate('.fl-col', 'mouseenter', FLBuilder._colMouseenter);
  758. content.delegate('.fl-col', 'mouseleave', FLBuilder._colMouseleave);
  759. content.delegate('.fl-module', 'mouseenter', FLBuilder._moduleMouseenter);
  760. content.delegate('.fl-module', 'mouseleave', FLBuilder._moduleMouseleave);
  761. },
  762. /**
  763. * Unbinds the events for overlays that appear when
  764. * mousing over a row, column or module.
  765. *
  766. * @since 1.0
  767. * @access private
  768. * @method _destroyOverlayEvents
  769. */
  770. _destroyOverlayEvents: function()
  771. {
  772. var content = $(FLBuilder._contentClass);
  773. content.undelegate('.fl-row', 'mouseenter', FLBuilder._rowMouseenter);
  774. content.undelegate('.fl-row', 'mouseleave', FLBuilder._rowMouseleave);
  775. content.undelegate('.fl-row-overlay', 'mouseleave', FLBuilder._rowMouseleave);
  776. content.undelegate('.fl-col', 'mouseenter', FLBuilder._colMouseenter);
  777. content.undelegate('.fl-col', 'mouseleave', FLBuilder._colMouseleave);
  778. content.undelegate('.fl-module', 'mouseenter', FLBuilder._moduleMouseenter);
  779. content.undelegate('.fl-module', 'mouseleave', FLBuilder._moduleMouseleave);
  780. },
  781. /**
  782. * Hides overlays when the contextmenu event is fired on them.
  783. * This allows us to inspect the actual node in the console
  784. * instead of getting the overlay.
  785. *
  786. * @since 2.2
  787. * @access private
  788. * @method _onContextmenu
  789. * @param {Object} e The event object.
  790. */
  791. _onContextmenu: function( e )
  792. {
  793. $( this ).hide();
  794. },
  795. /**
  796. * Prevents the default action for an event.
  797. *
  798. * @since 1.6.3
  799. * @access private
  800. * @method _preventDefault
  801. * @param {Object} e The event object.
  802. */
  803. _preventDefault: function( e )
  804. {
  805. e.preventDefault();
  806. },
  807. /**
  808. * Prevents propagation of an event.
  809. *
  810. * @since 1.6.3
  811. * @access private
  812. * @method _stopPropagation
  813. * @param {Object} e The event object.
  814. */
  815. _stopPropagation: function( e )
  816. {
  817. e.stopPropagation();
  818. },
  819. /**
  820. * Launches the builder for another page if a link in the
  821. * builder theme header is clicked.
  822. *
  823. * @since 1.3.9
  824. * @access private
  825. * @method _headerLinkClicked
  826. * @param {Object} e The event object.
  827. */
  828. _headerLinkClicked: function(e)
  829. {
  830. var link = $(this),
  831. href = link.attr('href');
  832. // ignore links with a #hash
  833. if( this.hash ) {
  834. return;
  835. }
  836. e.preventDefault();
  837. if ( FLBuilderConfig.isUserTemplate ) {
  838. return;
  839. }
  840. FLBuilder._exitUrl = href.indexOf('?') > -1 ? href : href + '?fl_builder';
  841. FLBuilder.triggerHook('triggerDone');
  842. },
  843. /**
  844. * Warns the user that their changes won't be saved if
  845. * they leave the page while editing settings.
  846. *
  847. * @since 1.0.6
  848. * @access private
  849. * @method _warnBeforeUnload
  850. * @return {String} The warning message.
  851. */
  852. _warnBeforeUnload: function()
  853. {
  854. var rowSettings = $('.fl-builder-row-settings').length > 0,
  855. colSettings = $('.fl-builder-col-settings').length > 0,
  856. moduleSettings = $('.fl-builder-module-settings').length > 0;
  857. if(rowSettings || colSettings || moduleSettings) {
  858. return FLBuilderStrings.unloadWarning;
  859. }
  860. },
  861. /* TipTips
  862. ----------------------------------------------------------*/
  863. /**
  864. * Initializes tooltip help messages.
  865. *
  866. * @since 1.1.9
  867. * @access private
  868. * @method _initTipTips
  869. */
  870. _initTipTips: function()
  871. {
  872. $('.fl-tip:not(.fl-has-tip)').each( function(){
  873. var ele = $( this );
  874. ele.addClass( 'fl-has-tip' );
  875. if ( undefined == ele.attr( 'data-title' ) ) {
  876. ele.attr( 'data-title', ele.attr( 'title' ) );
  877. }
  878. } ).tipTip( {
  879. defaultPosition : 'top',
  880. delay : 1000
  881. } );
  882. },
  883. /**
  884. * Removes all tooltip help messages from the screen.
  885. *
  886. * @since 1.1.9
  887. * @access private
  888. * @method _hideTipTips
  889. */
  890. _hideTipTips: function()
  891. {
  892. $('#tiptip_holder').stop().hide();
  893. },
  894. /* Submenus
  895. ----------------------------------------------------------*/
  896. /**
  897. * Callback for when the parent of a submenu is clicked.
  898. *
  899. * @since 1.6.4
  900. * @access private
  901. * @method _submenuParentClicked
  902. * @param {Object} e The event object.
  903. */
  904. _submenuParentClicked: function( e )
  905. {
  906. var body = $( 'body' ),
  907. parent = $( this ),
  908. submenu = parent.find( '.fl-builder-submenu' );
  909. if ( parent.hasClass( 'fl-builder-submenu-open' ) ) {
  910. body.removeClass( 'fl-builder-submenu-open' );
  911. parent.removeClass( 'fl-builder-submenu-open' );
  912. parent.removeClass( 'fl-builder-submenu-right' );
  913. }
  914. else {
  915. if( parent.offset().left + submenu.width() > $( window ).width() ) {
  916. parent.addClass( 'fl-builder-submenu-right' );
  917. }
  918. body.addClass( 'fl-builder-submenu-open' );
  919. parent.addClass( 'fl-builder-submenu-open' );
  920. }
  921. submenu.closest('.fl-row-overlay').addClass('fl-row-menu-active');
  922. FLBuilder._hideTipTips();
  923. e.preventDefault();
  924. e.stopPropagation();
  925. },
  926. /**
  927. * Callback for when the child of a submenu is clicked.
  928. *
  929. * @since 1.6.4
  930. * @access private
  931. * @method _submenuChildClicked
  932. * @param {Object} e The event object.
  933. */
  934. _submenuChildClicked: function( e )
  935. {
  936. var body = $( 'body' ),
  937. parent = $( this ).parents( '.fl-builder-has-submenu' );
  938. if ( ! parent.parents( '.fl-builder-has-submenu' ).length ) {
  939. body.removeClass( 'fl-builder-submenu-open' );
  940. parent.removeClass( 'fl-builder-submenu-open' );
  941. }
  942. },
  943. /**
  944. * Callback for when the mouse enters a submenu.
  945. *
  946. * @since 1.6.4
  947. * @access private
  948. * @method _submenuMouseenter
  949. * @param {Object} e The event object.
  950. */
  951. _submenuMouseenter: function( e )
  952. {
  953. var menu = $( this ),
  954. timeout = menu.data( 'timeout' );
  955. if ( 'undefined' != typeof timeout ) {
  956. clearTimeout( timeout );
  957. }
  958. },
  959. /**
  960. * Callback for when the mouse leaves a submenu.
  961. *
  962. * @since 1.6.4
  963. * @access private
  964. * @method _submenuMouseleave
  965. * @param {Object} e The event object.
  966. */
  967. _submenuMouseleave: function( e )
  968. {
  969. var body = $( 'body' ),
  970. menu = $( this ),
  971. timeout = setTimeout( function() {
  972. body.removeClass( 'fl-builder-submenu-open' );
  973. menu.closest( '.fl-builder-has-submenu' ).removeClass( 'fl-builder-submenu-open' );
  974. }, 500 );
  975. menu.closest('.fl-row-overlay').removeClass('fl-row-menu-active');
  976. menu.data( 'timeout', timeout );
  977. },
  978. /**
  979. * Callback for when the mouse enters the parent
  980. * of a nested submenu.
  981. *
  982. * @since 1.9
  983. * @access private
  984. * @method _submenuNestedParentMouseenter
  985. * @param {Object} e The event object.
  986. */
  987. _submenuNestedParentMouseenter: function( e )
  988. {
  989. var parent = $( this ),
  990. submenu = parent.find( '.fl-builder-submenu' );
  991. if( parent.width() + parent.offset().left + submenu.width() > $( window ).width() ) {
  992. parent.addClass( 'fl-builder-submenu-right' );
  993. }
  994. },
  995. /**
  996. * Closes all open submenus.
  997. *
  998. * @since 1.9
  999. * @access private
  1000. * @method _closeAllSubmenus
  1001. */
  1002. _closeAllSubmenus: function()
  1003. {
  1004. $( '.fl-builder-submenu-open' ).removeClass( 'fl-builder-submenu-open' );
  1005. },
  1006. /* Bar
  1007. ----------------------------------------------------------*/
  1008. /**
  1009. * Opens a new window with the upgrade URL when the
  1010. * upgrade button is clicked.
  1011. *
  1012. * @since 1.0
  1013. * @access private
  1014. * @method _upgradeClicked
  1015. */
  1016. _upgradeClicked: function()
  1017. {
  1018. window.open(FLBuilderConfig.upgradeUrl);
  1019. },
  1020. /**
  1021. * Fires blur on mouse up to avoid focus ring when clicked with mouse.
  1022. *
  1023. * @since 2.0
  1024. * @access private
  1025. * @method _buttonMouseUp
  1026. * @param {Event} e
  1027. * @return void
  1028. */
  1029. _buttonMouseUp: function(e) {
  1030. $(e.currentTarget).blur();
  1031. },
  1032. /* Panel
  1033. ----------------------------------------------------------*/
  1034. /**
  1035. * Closes the builder's content panel.
  1036. *
  1037. * @since 1.0
  1038. * @access private
  1039. * @method _closePanel
  1040. */
  1041. _closePanel: function()
  1042. {
  1043. FLBuilder.triggerHook('hideContentPanel');
  1044. },
  1045. /**
  1046. * Opens the builder's content panel.
  1047. *
  1048. * @since 1.0
  1049. * @access private
  1050. * @method _showPanel
  1051. */
  1052. _showPanel: function()
  1053. {
  1054. FLBuilder.triggerHook('showContentPanel');
  1055. },
  1056. /**
  1057. * Toggle the panel open or closed.
  1058. *
  1059. * @since 2.0
  1060. * @access private
  1061. * @method _togglePanel
  1062. */
  1063. _togglePanel: function()
  1064. {
  1065. FLBuilder.triggerHook('toggleContentPanel');
  1066. },
  1067. /**
  1068. * Opens or closes a section in the builder's content panel
  1069. * when a section title is clicked.
  1070. *
  1071. * @since 1.0
  1072. * @access private
  1073. * @method _blockSectionTitleClicked
  1074. */
  1075. _blockSectionTitleClicked: function()
  1076. {
  1077. var title = $(this),
  1078. section = title.parent();
  1079. if(section.hasClass('fl-active')) {
  1080. section.removeClass('fl-active');
  1081. }
  1082. else {
  1083. $('.fl-builder-blocks-section').removeClass('fl-active');
  1084. section.addClass('fl-active');
  1085. }
  1086. FLBuilder._initScrollbars();
  1087. },
  1088. /* Save Actions
  1089. ----------------------------------------------------------*/
  1090. /**
  1091. * Publish the current layout
  1092. *
  1093. * @since 2.0
  1094. * @access private
  1095. * @method _publishLayout
  1096. * @param bool whether or not builder should exit after publish
  1097. * @return void
  1098. */
  1099. _publishLayout: function( shouldExit ) {
  1100. // Save existing settings first if any exist. Don't proceed if it fails.
  1101. if ( ! FLBuilder._triggerSettingsSave( false, true ) ) {
  1102. return;
  1103. }
  1104. if ( _.isUndefined( shouldExit ) ) {
  1105. var shouldExit = true;
  1106. }
  1107. FLBuilder.ajax( {
  1108. action: 'save_layout'
  1109. }, this._onPublishComplete.bind( this, shouldExit ) );
  1110. },
  1111. /**
  1112. * Publishes the layout when the publish button is clicked.
  1113. *
  1114. * @since 1.0
  1115. * @access private
  1116. * @param bool whether or not builder should exit after publish
  1117. * @method _publishButtonClicked
  1118. */
  1119. _publishButtonClicked: function( shouldExit )
  1120. {
  1121. FLBuilder._publishLayout( shouldExit );
  1122. },
  1123. /**
  1124. * Fires on successful ajax publish.
  1125. *
  1126. * @since 2.0
  1127. * @access private
  1128. * @param bool whether or not builder should exit after publish
  1129. * @return void
  1130. */
  1131. _onPublishComplete: function( shouldExit ) {
  1132. if ( shouldExit ) {
  1133. if ( FLBuilderConfig.shouldRefreshOnPublish ) {
  1134. FLBuilder._exit();
  1135. } else {
  1136. FLBuilder._exitWithoutRefresh();
  1137. }
  1138. }
  1139. // Change the admin bar status dot to green if it isn't already
  1140. $('#wp-admin-bar-fl-builder-frontend-edit-link .fl-builder-admin-bar-status-dot').css('color', '#6bc373');
  1141. FLBuilder.triggerHook( 'didPublishLayout' );
  1142. },
  1143. /**
  1144. * Exits the builder when the save draft button is clicked.
  1145. *
  1146. * @since 1.0
  1147. * @access private
  1148. * @method _draftButtonClicked
  1149. */
  1150. _draftButtonClicked: function()
  1151. {
  1152. FLBuilder.showAjaxLoader();
  1153. FLBuilder.ajax({
  1154. action: 'save_draft'
  1155. }, FLBuilder._exit);
  1156. },
  1157. /**
  1158. * Clears changes to the layout when the discard draft button
  1159. * is clicked.
  1160. *
  1161. * @since 1.0
  1162. * @access private
  1163. * @method _discardButtonClicked
  1164. */
  1165. _discardButtonClicked: function()
  1166. {
  1167. var result = confirm(FLBuilderStrings.discardMessage);
  1168. if(result) {
  1169. FLBuilder.showAjaxLoader();
  1170. FLBuilder.ajax({
  1171. action: 'clear_draft_layout'
  1172. }, FLBuilder._exit);
  1173. } else {
  1174. FLBuilder.triggerHook('didCancelDiscard');
  1175. }
  1176. },
  1177. /**
  1178. * Closes the actions lightbox when the cancel button is clicked.
  1179. *
  1180. * @since 1.0
  1181. * @access private
  1182. * @method _cancelButtonClicked
  1183. */
  1184. _cancelButtonClicked: function()
  1185. {
  1186. FLBuilder._exitUrl = null;
  1187. FLBuilder._actionsLightbox.close();
  1188. },
  1189. /**
  1190. * Redirects the user to the _exitUrl if defined, otherwise
  1191. * it redirects the user to the current post without the
  1192. * builder active.
  1193. *
  1194. * @since 1.0
  1195. * @since 1.5.7 Closes the window if we're in a child window.
  1196. * @access private
  1197. * @method _exit
  1198. */
  1199. _exit: function()
  1200. {
  1201. var href = window.location.href;
  1202. if ( FLBuilderConfig.isUserTemplate && typeof window.opener != 'undefined' && window.opener ) {
  1203. if ( typeof window.opener.FLBuilder != 'undefined' ) {
  1204. if ( 'undefined' === typeof FLBuilderGlobalNodeId ) {
  1205. window.opener.FLBuilder._updateLayout();
  1206. } else {
  1207. window.opener.FLBuilder._updateNode( FLBuilderGlobalNodeId );
  1208. }
  1209. }
  1210. window.close();
  1211. }
  1212. else {
  1213. if ( FLBuilder._exitUrl ) {
  1214. href = FLBuilder._exitUrl;
  1215. }
  1216. else {
  1217. href = href.replace( '?fl_builder&', '?' );
  1218. href = href.replace( '?fl_builder', '' );
  1219. href = href.replace( '&fl_builder', '' );
  1220. }
  1221. window.location.href = href;
  1222. }
  1223. },
  1224. /**
  1225. * Allow the editing session to end but don't redirect to any url.
  1226. *
  1227. * @since 2.0
  1228. * @return void
  1229. */
  1230. _exitWithoutRefresh: function() {
  1231. var href = window.location.href;
  1232. if ( FLBuilderConfig.isUserTemplate && typeof window.opener != 'undefined' && window.opener ) {
  1233. if ( typeof window.opener.FLBuilder != 'undefined' ) {
  1234. if ( 'undefined' === typeof FLBuilderGlobalNodeId ) {
  1235. window.opener.FLBuilder._updateLayout();
  1236. } else {
  1237. window.opener.FLBuilder._updateNode( FLBuilderGlobalNodeId );
  1238. }
  1239. }
  1240. window.close();
  1241. }
  1242. else {
  1243. FLBuilder.triggerHook('endEditingSession');
  1244. }
  1245. },
  1246. /* Tools Actions
  1247. ----------------------------------------------------------*/
  1248. /**
  1249. * Duplicates the current post and builder layout.
  1250. *
  1251. * @since 1.0
  1252. * @access private
  1253. * @method _duplicateLayoutClicked
  1254. */
  1255. _duplicateLayoutClicked: function()
  1256. {
  1257. FLBuilder.showAjaxLoader();
  1258. FLBuilder.ajax({
  1259. action: 'duplicate_post'
  1260. }, FLBuilder._duplicateLayoutComplete);
  1261. },
  1262. /**
  1263. * Redirects the user to the post edit screen of a
  1264. * duplicated post when duplication is complete.
  1265. *
  1266. * @since 1.0
  1267. * @access private
  1268. * @method _duplicatePageComplete
  1269. * @param {Number} The ID of the duplicated post.
  1270. */
  1271. _duplicateLayoutComplete: function(response)
  1272. {
  1273. var adminUrl = FLBuilderConfig.adminUrl;
  1274. window.location.href = adminUrl + 'post.php?post='+ response +'&action=edit';
  1275. },
  1276. /* Layout Settings
  1277. ----------------------------------------------------------*/
  1278. /**
  1279. * Shows the layout settings lightbox when the layout
  1280. * settings button is clicked.
  1281. *
  1282. * @since 1.7
  1283. * @access private
  1284. * @method _layoutSettingsClicked
  1285. */
  1286. _layoutSettingsClicked: function()
  1287. {
  1288. FLBuilderSettingsForms.render( {
  1289. id : 'layout',
  1290. className : 'fl-builder-layout-settings',
  1291. settings : FLBuilderSettingsConfig.settings.layout
  1292. }, function() {
  1293. FLBuilder._layoutSettingsInitCSS();
  1294. } );
  1295. },
  1296. /**
  1297. * Initializes custom layout CSS for live preview.
  1298. *
  1299. * @since 1.7
  1300. * @access private
  1301. * @method _layoutSettingsInitCSS
  1302. */
  1303. _layoutSettingsInitCSS: function()
  1304. {
  1305. var css = $( '.fl-builder-settings #fl-field-css textarea:not(.ace_text-input)' );
  1306. css.on( 'change', FLBuilder._layoutSettingsCSSChanged );
  1307. FLBuilder._layoutSettingsCSSCache = css.val();
  1308. },
  1309. /**
  1310. * Sets a timeout for throttling custom layout CSS changes.
  1311. *
  1312. * @since 1.7
  1313. * @access private
  1314. * @method _layoutSettingsCSSChanged
  1315. */
  1316. _layoutSettingsCSSChanged: function()
  1317. {
  1318. if ( FLBuilder._layoutSettingsCSSTimeout ) {
  1319. clearTimeout( FLBuilder._layoutSettingsCSSTimeout );
  1320. }
  1321. FLBuilder._layoutSettingsCSSTimeout = setTimeout( $.proxy( FLBuilder._layoutSettingsCSSDoChange, this ), 600 );
  1322. },
  1323. /**
  1324. * Updates the custom layout CSS when changes are made in the editor.
  1325. *
  1326. * @since 1.7
  1327. * @access private
  1328. * @method _layoutSettingsCSSDoChange
  1329. */
  1330. _layoutSettingsCSSDoChange: function()
  1331. {
  1332. var form = $( '.fl-builder-settings' ),
  1333. textarea = $( this ),
  1334. field = textarea.parents( '#fl-field-css' );
  1335. if ( field.find( '.ace_error' ).length > 0 ) {
  1336. return;
  1337. }
  1338. else if ( form.hasClass( 'fl-builder-layout-settings' ) ) {
  1339. $( '#fl-builder-layout-css' ).html( textarea.val() );
  1340. }
  1341. else {
  1342. $( '#fl-builder-global-css' ).html( textarea.val() );
  1343. }
  1344. FLBuilder._layoutSettingsCSSTimeout = null;
  1345. },
  1346. /**
  1347. * Saves the layout settings when the save button is clicked.
  1348. *
  1349. * @since 1.7
  1350. * @access private
  1351. * @method _saveLayoutSettingsClicked
  1352. */
  1353. _saveLayoutSettingsClicked: function()
  1354. {
  1355. var form = $( this ).closest( '.fl-builder-settings' ),
  1356. data = form.serializeArray(),
  1357. settings = {},
  1358. i = 0;
  1359. for( ; i < data.length; i++) {
  1360. settings[ data[ i ].name ] = data[ i ].value;
  1361. }
  1362. FLBuilder.showAjaxLoader();
  1363. FLBuilder._lightbox.close();
  1364. FLBuilder._layoutSettingsCSSCache = null;
  1365. FLBuilder.ajax( {
  1366. action: 'save_layout_settings',
  1367. settings: settings
  1368. }, function() {
  1369. FLBuilder.triggerHook( 'didSaveLayoutSettingsComplete', settings );
  1370. FLBuilder._updateLayout();
  1371. } );
  1372. },
  1373. /**
  1374. * Reverts changes made when the cancel button for the layout
  1375. * settings has been clicked.
  1376. *
  1377. * @since 1.7
  1378. * @access private
  1379. * @method _cancelLayoutSettingsClicked
  1380. */
  1381. _cancelLayoutSettingsClicked: function()
  1382. {
  1383. var form = $( '.fl-builder-settings' );
  1384. if ( form.hasClass( 'fl-builder-layout-settings' ) ) {
  1385. $( '#fl-builder-layout-css' ).html( FLBuilder._layoutSettingsCSSCache );
  1386. }
  1387. else {
  1388. $( '#fl-builder-global-css' ).html( FLBuilder._layoutSettingsCSSCache );
  1389. }
  1390. FLBuilder._layoutSettingsCSSCache = null;
  1391. },
  1392. /* Global Settings
  1393. ----------------------------------------------------------*/
  1394. /**
  1395. * Shows the global builder settings lightbox when the global
  1396. * settings button is clicked.
  1397. *
  1398. * @since 1.0
  1399. * @access private
  1400. * @method _globalSettingsClicked
  1401. */
  1402. _globalSettingsClicked: function()
  1403. {
  1404. FLBuilderSettingsForms.render( {
  1405. id : 'global',
  1406. className : 'fl-builder-global-settings',
  1407. settings : FLBuilderSettingsConfig.settings.global
  1408. }, function() {
  1409. FLBuilder._layoutSettingsInitCSS();
  1410. } );
  1411. },
  1412. /**
  1413. * Saves the global settings when the save button is clicked.
  1414. *
  1415. * @since 1.0
  1416. * @access private
  1417. * @method _saveGlobalSettingsClicked
  1418. */
  1419. _saveGlobalSettingsClicked: function()
  1420. {
  1421. var form = $(this).closest('.fl-builder-settings'),
  1422. valid = form.validate().form(),
  1423. settings = FLBuilder._getSettings( form );
  1424. if(valid) {
  1425. FLBuilder.showAjaxLoader();
  1426. FLBuilder._layoutSettingsCSSCache = null;
  1427. FLBuilder.ajax({
  1428. action: 'save_global_settings',
  1429. settings: settings
  1430. }, FLBuilder._saveGlobalSettingsComplete);
  1431. FLBuilder._lightbox.close();
  1432. }
  1433. },
  1434. /**
  1435. * Saves the global settings when the save button is clicked.
  1436. *
  1437. * @since 1.0
  1438. * @access private
  1439. * @method _saveGlobalSettingsComplete
  1440. * @param {String} response
  1441. */
  1442. _saveGlobalSettingsComplete: function( response )
  1443. {
  1444. FLBuilderConfig.global = JSON.parse( response );
  1445. FLBuilder.triggerHook( 'didSaveGlobalSettingsComplete', FLBuilderConfig.global );
  1446. FLBuilder._updateLayout();
  1447. },
  1448. /* Template Selector
  1449. ----------------------------------------------------------*/
  1450. /**
  1451. * Shows the template selector when the builder is launched
  1452. * if the current layout is empty.
  1453. *
  1454. * @since 1.0
  1455. * @access private
  1456. * @method _initTemplateSelector
  1457. */
  1458. _initTemplateSelector: function()
  1459. {
  1460. var rows = $(FLBuilder._contentClass).find('.fl-row'),
  1461. layoutHasContent = ( rows.length > 0 );
  1462. if( ! layoutHasContent ) {
  1463. FLBuilder.ContentPanel.show('modules');
  1464. }
  1465. },
  1466. /**
  1467. * Show options for inserting or appending a template when a template is selected.
  1468. * This logic was moved from `_templateClicked` to unbind it from the specific event.
  1469. *
  1470. * @since 2.0
  1471. * @access private
  1472. * @method _requestTemplateInsert
  1473. */
  1474. _requestTemplateInsert: function(index, type) {
  1475. // if there are existing rows in the layout
  1476. if( FLBuilder.layoutHasContent() ) {
  1477. // If the template is blank, no need to ask
  1478. if(index == 0) {
  1479. if(confirm(FLBuilderStrings.changeTemplateMessage)) {
  1480. FLBuilder._lightbox._node.hide();
  1481. FLBuilder._applyTemplate(0, false, type);
  1482. }
  1483. }
  1484. // present options Replace or Append
  1485. else {
  1486. FLBuilder._selectedTemplateId = index;
  1487. FLBuilder._selectedTemplateType = type;
  1488. FLBuilder._showTemplateActions();
  1489. FLBuilder._lightbox._node.hide();
  1490. }
  1491. }
  1492. // if there are no rows, just insert the template.
  1493. else {
  1494. FLBuilder._applyTemplate(index, false, type);
  1495. }
  1496. },
  1497. /**
  1498. * Shows the actions lightbox for replacing and appending templates.
  1499. *
  1500. * @since 1.1.9
  1501. * @access private
  1502. * @method _showTemplateActions
  1503. */
  1504. _showTemplateActions: function()
  1505. {
  1506. var buttons = [];
  1507. buttons[ 10 ] = {
  1508. 'key': 'template-replace',
  1509. 'label': FLBuilderStrings.templateReplace
  1510. };
  1511. buttons[ 20 ] = {
  1512. 'key': 'template-append',
  1513. 'label': FLBuilderStrings.templateAppend
  1514. };
  1515. FLBuilder._showActionsLightbox({
  1516. 'className': 'fl-builder-template-actions',
  1517. 'title': FLBuilderStrings.actionsLightboxTitle,
  1518. 'buttons': buttons
  1519. });
  1520. },
  1521. /**
  1522. * Replaces the current layout with a template when the replace
  1523. * button is clicked.
  1524. *
  1525. * @since 1.1.9
  1526. * @access private
  1527. * @method _templateReplaceClicked
  1528. */
  1529. _templateReplaceClicked: function()
  1530. {
  1531. if(confirm(FLBuilderStrings.changeTemplateMessage)) {
  1532. FLBuilder._actionsLightbox.close();
  1533. FLBuilder._applyTemplate(FLBuilder._selectedTemplateId, false, FLBuilder._selectedTemplateType);
  1534. }
  1535. },
  1536. /**
  1537. * Append a template to the current layout when the append
  1538. * button is clicked.
  1539. *
  1540. * @since 1.1.9
  1541. * @access private
  1542. * @method _templateAppendClicked
  1543. */
  1544. _templateAppendClicked: function()
  1545. {
  1546. FLBuilder._actionsLightbox.close();
  1547. FLBuilder._applyTemplate(FLBuilder._selectedTemplateId, true, FLBuilder._selectedTemplateType);
  1548. },
  1549. /**
  1550. * Shows the template selector when the cancel button of
  1551. * the template actions lightbox is clicked.
  1552. *
  1553. * @since 1.1.9
  1554. * @access private
  1555. * @method _templateCancelClicked
  1556. */
  1557. _templateCancelClicked: function()
  1558. {
  1559. FLBuilder.triggerHook( 'showContentPanel' );
  1560. },
  1561. /**
  1562. * Applys a template to the current layout by either appending
  1563. * it or replacing the current layout with it.
  1564. *
  1565. * @since 1.1.9
  1566. * @access private
  1567. * @method _applyTemplate
  1568. * @param {Number} id The template id.
  1569. * @param {Boolean} append Whether the new template should be appended or not.
  1570. * @param {String} type The type of template. Either core or user.
  1571. */
  1572. _applyTemplate: function(id, append, type)
  1573. {
  1574. append = typeof append === 'undefined' || !append ? '0' : '1';
  1575. type = typeof type === 'undefined' ? 'core' : type;
  1576. FLBuilder._lightbox.close();
  1577. FLBuilder.showAjaxLoader();
  1578. if(type == 'core') {
  1579. FLBuilder.ajax({
  1580. action: 'apply_template',
  1581. template_id: id,
  1582. append: append
  1583. }, FLBuilder._applyTemplateComplete);
  1584. }
  1585. else {
  1586. FLBuilder.ajax({
  1587. action: 'apply_user_template',
  1588. template_id: id,
  1589. append: append
  1590. }, FLBuilder._applyUserTemplateComplete);
  1591. }
  1592. FLBuilder.triggerHook('didApplyTemplate');
  1593. },
  1594. /**
  1595. * Callback for when applying a template completes.
  1596. * @since 2.0
  1597. * @access private
  1598. * @method _applyTemplateComplete
  1599. * @param {String} response
  1600. */
  1601. _applyTemplateComplete: function( response )
  1602. {
  1603. var data = JSON.parse( response );
  1604. FLBuilder._renderLayout( data.layout );
  1605. FLBuilder.triggerHook( 'didApplyTemplateComplete', data.config );
  1606. },
  1607. /**
  1608. * Callback for when applying a user template completes.
  1609. * @since 1.9.5
  1610. * @access private
  1611. * @method _applyUserTemplateComplete
  1612. * @param {string} response
  1613. */
  1614. _applyUserTemplateComplete: function( response )
  1615. {
  1616. var data = JSON.parse( response );
  1617. if ( null !== data.layout_css ) {
  1618. $( '#fl-builder-layout-css' ).html( data.layout_css );
  1619. }
  1620. FLBuilder._renderLayout( data.layout );
  1621. FLBuilder.triggerHook( 'didApplyTemplateComplete', data.config );
  1622. },
  1623. /* User Template Settings
  1624. ----------------------------------------------------------*/
  1625. /**
  1626. * Shows the settings for saving a user defined template
  1627. * when the save template button is clicked.
  1628. *
  1629. * @since 1.1.3
  1630. * @access private
  1631. * @method _saveUserTemplateClicked
  1632. */
  1633. _saveUserTemplateClicked: function()
  1634. {
  1635. FLBuilderSettingsForms.render( {
  1636. id : 'user_template',
  1637. className : 'fl-builder-user-template-settings',
  1638. rules : {
  1639. name: {
  1640. required: true
  1641. }
  1642. }
  1643. } );
  1644. },
  1645. /**
  1646. * Saves user template settings when the save button is clicked.
  1647. *
  1648. * @since 1.1.9
  1649. * @access private
  1650. * @method _saveUserTemplateSettings
  1651. */
  1652. _saveUserTemplateSettings: function()
  1653. {
  1654. var form = $(this).closest('.fl-builder-settings'),
  1655. valid = form.validate().form(),
  1656. settings = FLBuilder._getSettings(form);
  1657. if(valid) {
  1658. FLBuilder.ajax({
  1659. action: 'save_user_template',
  1660. settings: settings
  1661. }, FLBuilder._saveUserTemplateSettingsComplete);
  1662. FLBuilder._lightbox.close();
  1663. }
  1664. },
  1665. /**
  1666. * Shows a success alert when user template settings have saved.
  1667. *
  1668. * @since 1.1.9
  1669. * @access private
  1670. * @method _saveUserTemplateSettingsComplete
  1671. */
  1672. _saveUserTemplateSettingsComplete: function(data)
  1673. {
  1674. if ( !data ) return;
  1675. var data = JSON.parse(data);
  1676. FLBuilderConfig.contentItems.template.push(data);
  1677. FLBuilder.triggerHook('contentItemsChanged');
  1678. },
  1679. /**
  1680. * Callback for when a user clicks a user defined template in
  1681. * the template selector.
  1682. *
  1683. * @since 1.1.3
  1684. * @access private
  1685. * @method _userTemplateClicked
  1686. */
  1687. _userTemplateClicked: function()
  1688. {
  1689. var id = $(this).attr('data-id');
  1690. if($(FLBuilder._contentClass).children('.fl-row').length > 0) {
  1691. if(id == 'blank') {
  1692. if(confirm(FLBuilderStrings.changeTemplateMessage)) {
  1693. FLBuilder._lightbox._node.hide();
  1694. FLBuilder._applyTemplate('blank', false, 'user');
  1695. }
  1696. }
  1697. else {
  1698. FLBuilder._selectedTemplateId = id;
  1699. FLBuilder._selectedTemplateType = 'user';
  1700. FLBuilder._showTemplateActions();
  1701. FLBuilder._lightbox._node.hide();
  1702. }
  1703. }
  1704. else {
  1705. FLBuilder._applyTemplate(id, false, 'user');
  1706. }
  1707. },
  1708. /**
  1709. * Launches the builder in a new tab to edit a user
  1710. * defined template when the edit link is clicked.
  1711. *
  1712. * @since 1.1.3
  1713. * @access private
  1714. * @method _editUserTemplateClicked
  1715. * @param {Object} e The event object.
  1716. */
  1717. _editUserTemplateClicked: function(e)
  1718. {
  1719. e.preventDefault();
  1720. e.stopPropagation();
  1721. window.open($(this).attr('href'));
  1722. },
  1723. /**
  1724. * Deletes a user defined template when the delete link is clicked.
  1725. *
  1726. * @since 1.1.3
  1727. * @access private
  1728. * @method _deleteUserTemplateClicked
  1729. * @param {Object} e The event object.
  1730. */
  1731. _deleteUserTemplateClicked: function(e)
  1732. {
  1733. var template = $( this ).closest( '.fl-user-template' ),
  1734. id = template.attr( 'data-id' ),
  1735. all = $( '.fl-user-template[data-id=' + id + ']' ),
  1736. parent = null,
  1737. index = null,
  1738. i = null,
  1739. item = null;
  1740. if ( confirm( FLBuilderStrings.deleteTemplate ) ) {
  1741. FLBuilder.ajax( {
  1742. action: 'delete_user_template',
  1743. template_id: id
  1744. } );
  1745. // Remove the item from library
  1746. for(i in FLBuilderConfig.contentItems.template) {
  1747. item = FLBuilderConfig.contentItems.template[i];
  1748. if (item.postId == id) {
  1749. index = i;
  1750. }
  1751. }
  1752. if (!_.isNull(index)) {
  1753. FLBuilderConfig.contentItems.template.splice(index, 1);
  1754. FLBuilder.triggerHook('contentItemsChanged');
  1755. }
  1756. }
  1757. e.stopPropagation();
  1758. },
  1759. /* Help Actions
  1760. ----------------------------------------------------------*/
  1761. /**
  1762. * Opens a new window with the knowledge base URL when the
  1763. * view knowledge base button is clicked.
  1764. *
  1765. * @since 1.4.9
  1766. * @access private
  1767. * @method _viewKnowledgeBaseClicked
  1768. */
  1769. _viewKnowledgeBaseClicked: function()
  1770. {
  1771. window.open( FLBuilderConfig.help.knowledge_base_url );
  1772. },
  1773. /**
  1774. * Opens a new window with the forums URL when the
  1775. * visit forums button is clicked.
  1776. *
  1777. * @since 1.4.9
  1778. * @access private
  1779. * @method _visitForumsClicked
  1780. */
  1781. _visitForumsClicked: function()
  1782. {
  1783. window.open( FLBuilderConfig.help.forums_url );
  1784. },
  1785. /* Help Tour
  1786. ----------------------------------------------------------*/
  1787. /**
  1788. * Shows the help tour or template selector when the builder
  1789. * is launched.
  1790. *
  1791. * @since 1.4.9
  1792. * @access private
  1793. * @method _showTourOrTemplates
  1794. */
  1795. _showTourOrTemplates: function()
  1796. {
  1797. if ( ! FLBuilderConfig.simpleUi && ! FLBuilderConfig.isUserTemplate ) {
  1798. if ( FLBuilderConfig.help.tour && FLBuilderConfig.newUser ) {
  1799. FLBuilder._showTourLightbox();
  1800. }
  1801. else {
  1802. FLBuilder._initTemplateSelector();
  1803. }
  1804. }
  1805. },
  1806. /**
  1807. * Shows the actions lightbox with a welcome message for new
  1808. * users asking if they would like to take the tour.
  1809. *
  1810. * @since 1.4.9
  1811. * @access private
  1812. * @method _showTourLightbox
  1813. */
  1814. _showTourLightbox: function()
  1815. {
  1816. var template = wp.template( 'fl-tour-lightbox' );
  1817. FLBuilder._actionsLightbox.open( template() );
  1818. },
  1819. /**
  1820. * Closes the actions lightbox and shows the template selector
  1821. * if a new user declines the tour.
  1822. *
  1823. * @since 1.4.9
  1824. * @access private
  1825. * @method _noTourButtonClicked
  1826. */
  1827. _noTourButtonClicked: function()
  1828. {
  1829. FLBuilder._actionsLightbox.close();
  1830. FLBuilder._initTemplateSelector();
  1831. },
  1832. /**
  1833. * Closes the actions lightbox and starts the tour when a new user
  1834. * decides to take the tour.
  1835. *
  1836. * @since 1.4.9
  1837. * @access private
  1838. * @method _yesTourButtonClicked
  1839. */
  1840. _yesTourButtonClicked: function()
  1841. {
  1842. FLBuilder._actionsLightbox.close();
  1843. FLBuilderTour.start();
  1844. },
  1845. /**
  1846. * Starts the help tour.
  1847. *
  1848. * @since 1.4.9
  1849. * @access private
  1850. * @method _startHelpTour
  1851. */
  1852. _startHelpTour: function()
  1853. {
  1854. FLBuilder._actionsLightbox.close();
  1855. FLBuilderTour.start();
  1856. },
  1857. /* Layout
  1858. ----------------------------------------------------------*/
  1859. /**
  1860. * Shows a message to drop a row or module to get started
  1861. * if the layout is empty.
  1862. *
  1863. * @since 1.0
  1864. * @access private
  1865. * @method _setupEmptyLayout
  1866. */
  1867. _setupEmptyLayout: function()
  1868. {
  1869. var content = $(FLBuilder._contentClass);
  1870. if ( FLBuilderConfig.isUserTemplate && 'module' == FLBuilderConfig.userTemplateType ) {
  1871. return;
  1872. }
  1873. else if ( FLBuilderConfig.isUserTemplate && 'column' == FLBuilderConfig.userTemplateType ) {
  1874. return;
  1875. }
  1876. else {
  1877. content.removeClass('fl-builder-empty');
  1878. content.find('.fl-builder-empty-message').remove();
  1879. if ( ! content.find( '.fl-row, .fl-builder-block' ).length ) {
  1880. content.addClass('fl-builder-empty');
  1881. content.append('<span class="fl-builder-empty-message">'+ FLBuilderStrings.emptyMessage +'</span>');
  1882. FLBuilder._initSortables();
  1883. }
  1884. }
  1885. },
  1886. /**
  1887. * Sends an AJAX request to re-render a single node.
  1888. *
  1889. * @since 2.0
  1890. * @access private
  1891. * @method _updateNode
  1892. * @param {String} nodeId
  1893. * @param {Function} callback
  1894. */
  1895. _updateNode: function( nodeId, callback )
  1896. {
  1897. if ( ! $( '.fl-node-' + nodeId ).length ) {
  1898. return;
  1899. }
  1900. FLBuilder._showNodeLoading( nodeId );
  1901. FLBuilder.ajax( {
  1902. action : 'render_node',
  1903. node_id : nodeId
  1904. }, function( response ) {
  1905. FLBuilder._renderLayout( JSON.parse( response ), callback );
  1906. }.bind( this ) );
  1907. },
  1908. /**
  1909. * Sends an AJAX request to render the layout and is typically
  1910. * used as a callback to many of the builder's save operations.
  1911. *
  1912. * @since 1.0
  1913. * @access private
  1914. * @method _updateLayout
  1915. */
  1916. _updateLayout: function()
  1917. {
  1918. FLBuilder.showAjaxLoader();
  1919. FLBuilder.ajax({
  1920. action: 'render_layout'
  1921. }, FLBuilder._renderLayout);
  1922. },
  1923. /**
  1924. * Removes the current layout and renders a new layout using
  1925. * the provided data. Will render a node instead of the layout
  1926. * if data.partial is true.
  1927. *
  1928. * @since 1.0
  1929. * @access private
  1930. * @method _renderLayout
  1931. * @param {Object} data The layout data. May also be a JSON encoded string.
  1932. * @param {Function} callback A function to call when the layout has finished rendering.
  1933. */
  1934. _renderLayout: function( data, callback )
  1935. {
  1936. FLBuilder._layout = new FLBuilderAJAXLayout( data, callback );
  1937. },
  1938. /**
  1939. * Called by the layout's JavaScript file once it's loaded
  1940. * to finish rendering the layout.
  1941. *
  1942. * @since 1.0
  1943. * @access private
  1944. * @method _renderLayoutComplete
  1945. */
  1946. _renderLayoutComplete: function()
  1947. {
  1948. if ( FLBuilder._layout ) {
  1949. FLBuilder._layout._complete();
  1950. FLBuilder._layout = null;
  1951. }
  1952. },
  1953. /**
  1954. * Trigger the resize event on the window so elements
  1955. * in the layout that rely on JavaScript know to resize.
  1956. *
  1957. * @since 1.0
  1958. * @access private
  1959. * @method _resizeLayout
  1960. */
  1961. _resizeLayout: function()
  1962. {
  1963. $(window).trigger('resize');
  1964. if(typeof YUI !== 'undefined') {
  1965. YUI().use('node-event-simulate', function(Y) {
  1966. Y.one(window).simulate("resize");
  1967. });
  1968. }
  1969. },
  1970. /**
  1971. * Checks to see if any rows exist in the layout, or if it is blank.
  1972. *
  1973. * @since 2.0
  1974. * @method layoutHasContent
  1975. * @return {Boolean}
  1976. */
  1977. layoutHasContent: function()
  1978. {
  1979. if( $(FLBuilder._contentClass).children('.fl-row').length > 0) {
  1980. return true;
  1981. } else {
  1982. return false;
  1983. }
  1984. },
  1985. /**
  1986. * Initializes MediaElements.js audio and video players.
  1987. *
  1988. * @since 1.0
  1989. * @access private
  1990. * @method _initMediaElements
  1991. */
  1992. _initMediaElements: function()
  1993. {
  1994. var settings = {};
  1995. if(typeof $.fn.mediaelementplayer != 'undefined') {
  1996. if(typeof _wpmejsSettings !== 'undefined') {
  1997. settings.pluginPath = _wpmejsSettings.pluginPath;
  1998. }
  1999. $('.wp-audio-shortcode, .wp-video-shortcode').not('.mejs-container').mediaelementplayer(settings);
  2000. }
  2001. },
  2002. /* Generic Drag and Drop
  2003. ----------------------------------------------------------*/
  2004. /**
  2005. * Inserts drop targets for nodes such as rows, columns
  2006. * and column groups since making those all sortables
  2007. * makes sorting really jumpy.
  2008. *
  2009. * @since 1.9
  2010. * @access private
  2011. * @method _initDropTargets
  2012. */
  2013. _initDropTargets: function()
  2014. {
  2015. var notGlobal = 'row' == FLBuilderConfig.userTemplateType ? '' : ':not(.fl-node-global)',
  2016. rows = $( FLBuilder._contentClass + ' .fl-row' ),
  2017. row = null,
  2018. groups = $( FLBuilder._contentClass + ' .fl-row' + notGlobal ).find( '.fl-col-group' ),
  2019. group = null,
  2020. cols = null,
  2021. rootCol = 'column' == FLBuilderConfig.userTemplateType ? $( FLBuilder._contentClass + '> .fl-col' ).eq(0) : null;
  2022. i = 0;
  2023. // Remove old drop targets.
  2024. $( '.fl-col-drop-target' ).remove();
  2025. $( '.fl-col-group-drop-target' ).remove();
  2026. $( '.fl-row-drop-target' ).remove();
  2027. // Row drop targets.
  2028. $( FLBuilder._contentClass ).append( '<div class="fl-drop-target fl-row-drop-target"></div>' );
  2029. rows.prepend( '<div class="fl-drop-target fl-row-drop-target"></div>' );
  2030. rows.append( '<div class="fl-drop-target fl-drop-target-last fl-row-drop-target fl-row-drop-target-last"></div>' );
  2031. // Add group drop targets to empty rows.
  2032. for ( ; i < rows.length; i++ ) {
  2033. row = rows.eq( i );
  2034. if ( 0 === row.find( '.fl-col-group' ).length ) {
  2035. row.find( '.fl-row-content' ).prepend( '<div class="fl-drop-target fl-col-group-drop-target"></div>' );
  2036. }
  2037. }
  2038. // Add drop target to root parent column.
  2039. if ( rootCol && 0 === groups.length ) {
  2040. groups = rootCol.find( '.fl-col-group' );
  2041. rootCol.append( '<div class="fl-drop-target fl-col-drop-target"></div>' );
  2042. rootCol.append( '<div class="fl-drop-target fl-drop-target-last fl-col-drop-target fl-col-drop-target-last"></div>' );
  2043. }
  2044. // Loop through the column groups.
  2045. for ( i = 0; i < groups.length; i++ ) {
  2046. group = groups.eq( i );
  2047. cols = group.find( '> .fl-col' );
  2048. // Column group drop targets.
  2049. if ( ! group.hasClass( 'fl-col-group-nested' ) ) {
  2050. group.append( '<div class="fl-drop-target fl-col-group-drop-target"></div>' );
  2051. group.append( '<div class="fl-drop-target fl-drop-target-last fl-col-group-drop-target fl-col-group-drop-target-last"></div>' );
  2052. }
  2053. // Column drop targets.
  2054. cols.append( '<div class="fl-drop-target fl-col-drop-target"></div>' );
  2055. cols.append( '<div class="fl-drop-target fl-drop-target-last fl-col-drop-target fl-col-drop-target-last"></div>' );
  2056. }
  2057. },
  2058. /**
  2059. * Returns a helper element for a drag operation.
  2060. *
  2061. * @since 1.0
  2062. * @access private
  2063. * @method _blockDragHelper
  2064. * @param {Object} e The event object.
  2065. * @param {Object} item The item being dragged.
  2066. * @return {Object} The helper element.
  2067. */
  2068. _blockDragHelper: function (e, item)
  2069. {
  2070. var helper = item.clone();
  2071. item.clone().insertAfter(item);
  2072. helper.addClass('fl-builder-block-drag-helper');
  2073. return helper;
  2074. },
  2075. /**
  2076. * Initializes a drag operation.
  2077. *
  2078. * @since 1.0
  2079. * @access private
  2080. * @method _blockDragInit
  2081. * @param {Object} e The event object.
  2082. */
  2083. _blockDragInit: function( e )
  2084. {
  2085. var target = $( e.currentTarget ),
  2086. node = null,
  2087. scrollTop = $( window ).scrollTop(),
  2088. initialPos = 0;
  2089. // Set the _dragEnabled flag.
  2090. FLBuilder._dragEnabled = true;
  2091. // Save the initial scroll position.
  2092. FLBuilder._dragInitialScrollTop = scrollTop;
  2093. // Get the node to scroll to once the node highlights have affected the body height.
  2094. if ( target.closest( '[data-node]' ).length > 0 ) {
  2095. // Set the node to a node instance being dragged.
  2096. node = target.closest( '[data-node]' );
  2097. // Mark this node as initialized for dragging.
  2098. node.addClass( 'fl-node-drag-init' );
  2099. }
  2100. else if ( target.hasClass( 'fl-builder-block' ) ) {
  2101. // Set the node to the first visible row instance.
  2102. $( '.fl-row' ).each( function() {
  2103. if ( node === null && $( this ).offset().top - scrollTop > 0 ) {
  2104. node = $( this );
  2105. }
  2106. } );
  2107. }
  2108. // Get the initial scroll position of the node.
  2109. if ( node !== null ) {
  2110. initialPos = node.offset().top - scrollTop;
  2111. }
  2112. // Setup the UI for dragging.
  2113. FLBuilder._highlightRowsAndColsForDrag( target );
  2114. FLBuilder._adjustColHeightsForDrag();
  2115. FLBuilder._disableGlobalRows();
  2116. FLBuilder._disableGlobalCols();
  2117. FLBuilder._destroyOverlayEvents();
  2118. FLBuilder._removeAllOverlays();
  2119. FLBuilder._initSortables();
  2120. $( 'body' ).addClass( 'fl-builder-dragging' );
  2121. $( '.fl-builder-empty-message' ).hide();
  2122. $( '.fl-sortable-disabled' ).removeClass( 'fl-sortable-disabled' );
  2123. // Scroll to the node that is dragging.
  2124. if ( initialPos > 0 ) {
  2125. scrollTo( 0, node.offset().top - initialPos );
  2126. }
  2127. FLBuilder.triggerHook('didInitDrag');
  2128. },
  2129. /**
  2130. * Callback that fires when dragging starts.
  2131. *
  2132. * @since 1.0
  2133. * @access private
  2134. * @method _blockDragStart
  2135. * @param {Object} e The event object.
  2136. * @param {Object} ui An object with additional info for the drag.
  2137. */
  2138. _blockDragStart: function(e, ui)
  2139. {
  2140. // Let the builder know dragging has started.
  2141. FLBuilder._dragging = true;
  2142. // Removed the drag init class as we're done with that.
  2143. $( '.fl-node-drag-init' ).removeClass( 'fl-node-drag-init' );
  2144. FLBuilder.triggerHook('didStartDrag');
  2145. },
  2146. /**
  2147. * Callback that fires when an element that is being
  2148. * dragged is sorted.
  2149. *
  2150. * @since 1.0
  2151. * @access private
  2152. * @method _blockDragSort
  2153. * @param {Object} e The event object.
  2154. * @param {Object} ui An object with additional info for the drag.
  2155. */
  2156. _blockDragSort: function(e, ui)
  2157. {
  2158. var parent = ui.placeholder.parent(),
  2159. title = FLBuilderStrings.insert;
  2160. // Prevent sorting?
  2161. if ( FLBuilder._blockPreventSort( ui.item, parent ) ) {
  2162. return;
  2163. }
  2164. // Find the placeholder title.
  2165. if(parent.hasClass('fl-col-content')) {
  2166. if(ui.item.hasClass('fl-builder-block-row')) {
  2167. title = ui.item.find('.fl-builder-block-title').text();
  2168. }
  2169. else if(ui.item.hasClass('fl-col-sortable-proxy-item')) {
  2170. title = FLBuilderStrings.column;
  2171. }
  2172. else if(ui.item.hasClass('fl-builder-block-module')) {
  2173. title = ui.item.find('.fl-builder-block-title').text();
  2174. }
  2175. else if(ui.item.hasClass('fl-builder-block-saved-module') || ui.item.hasClass('fl-builder-block-module-template')) {
  2176. title = ui.item.find('.fl-builder-block-title').text();
  2177. }
  2178. else {
  2179. title = ui.item.attr('data-name');
  2180. }
  2181. }
  2182. else if(parent.hasClass('fl-col-drop-target')) {
  2183. title = '';
  2184. }
  2185. else if (parent.hasClass('fl-col-group-drop-target')) {
  2186. title = '';
  2187. }
  2188. else if(parent.hasClass('fl-row-drop-target')) {
  2189. if(ui.item.hasClass('fl-builder-block-row')) {
  2190. title = ui.item.find('.fl-builder-block-title').text();
  2191. }
  2192. else if(ui.item.hasClass('fl-builder-block-saved-row')) {
  2193. title = ui.item.find('.fl-builder-block-title').text();
  2194. }
  2195. else if(ui.item.hasClass('fl-builder-block-saved-column')) {
  2196. title = ui.item.find('.fl-builder-block-title').text();
  2197. }
  2198. else if(ui.item.hasClass('fl-row-sortable-proxy-item')) {
  2199. title = FLBuilderStrings.row;
  2200. }
  2201. else {
  2202. title = FLBuilderStrings.newRow;
  2203. }
  2204. }
  2205. // Set the placeholder title.
  2206. ui.placeholder.html(title);
  2207. // Add the global class?
  2208. if ( ui.item.hasClass( 'fl-node-global' ) ||
  2209. ui.item.hasClass( 'fl-builder-block-global' ) ||
  2210. $( '.fl-node-dragging' ).hasClass( 'fl-node-global' )
  2211. ) {
  2212. ui.placeholder.addClass( 'fl-builder-drop-zone-global' );
  2213. }
  2214. else {
  2215. ui.placeholder.removeClass( 'fl-builder-drop-zone-global' );
  2216. }
  2217. },
  2218. /**
  2219. * Callback that fires when an element that is being
  2220. * dragged position changes.
  2221. *
  2222. * What we're doing here keeps it from appearing jumpy when draging
  2223. * between columns. Without this you'd see the placeholder jump into
  2224. * a column position briefly when you didn't intend for it to.
  2225. *
  2226. * @since 1.9
  2227. * @access private
  2228. * @method _blockDragChange
  2229. * @param {Object} e The event object.
  2230. * @param {Object} ui An object with additional info for the drag.
  2231. */
  2232. _blockDragChange: function( e, ui )
  2233. {
  2234. ui.placeholder.css( 'opacity', '0' );
  2235. ui.placeholder.animate( { 'opacity': '1' }, 100 );
  2236. },
  2237. /**
  2238. * Prevents sorting of items that shouldn't be sorted into
  2239. * specific areas.
  2240. *
  2241. * @since 1.9
  2242. * @access private
  2243. * @method _blockPreventSort
  2244. * @param {Object} item The item being sorted.
  2245. * @param {Object} parent The new parent.
  2246. */
  2247. _blockPreventSort: function( item, parent )
  2248. {
  2249. var prevent = false,
  2250. isRowBlock = item.hasClass( 'fl-builder-block-row' ),
  2251. isCol = item.hasClass( 'fl-col-sortable-proxy-item' ),
  2252. isParentCol = parent.hasClass( 'fl-col-content' ),
  2253. isColTarget = parent.hasClass( 'fl-col-drop-target' ),
  2254. group = parent.parents( '.fl-col-group:not(.fl-col-group-nested)' ),
  2255. nestedGroup = parent.parents( '.fl-col-group-nested' );
  2256. // Prevent columns in nested columns.
  2257. if ( ( isRowBlock || isCol ) && isParentCol && nestedGroup.length > 0 ) {
  2258. prevent = true;
  2259. }
  2260. // Prevent 1 column from being nested in an empty column.
  2261. if ( isParentCol && ! parent.find( '.fl-module, .fl-col' ).length ) {
  2262. if ( isRowBlock && '1-col' == item.data( 'cols' ) ) {
  2263. prevent = true;
  2264. }
  2265. else if ( isCol ) {
  2266. prevent = true;
  2267. }
  2268. }
  2269. // Prevent 5 or 6 columns from being nested.
  2270. if ( isRowBlock && isParentCol && $.inArray( item.data( 'cols' ), [ '5-cols', '6-cols' ] ) > -1 ) {
  2271. prevent = true;
  2272. }
  2273. // Prevent columns with nested columns from being dropped in nested columns.
  2274. if ( isCol && $( '.fl-node-dragging' ).find( '.fl-col-group-nested' ).length > 0 ) {
  2275. if ( isParentCol || ( isColTarget && nestedGroup.length > 0 ) ) {
  2276. prevent = true;
  2277. }
  2278. }
  2279. // Prevent more than 12 columns.
  2280. if ( isColTarget && group.length > 0 && 0 === nestedGroup.length && group.find( '> .fl-col:visible' ).length > 11 ) {
  2281. prevent = true;
  2282. }
  2283. // Prevent more than 4 nested columns.
  2284. if ( isColTarget && nestedGroup.length > 0 && nestedGroup.find( '.fl-col:visible' ).length > 3 ) {
  2285. prevent = true;
  2286. }
  2287. // Add the disabled class if we are preventing a sort.
  2288. if ( prevent ) {
  2289. parent.addClass( 'fl-sortable-disabled' );
  2290. }
  2291. return prevent;
  2292. },
  2293. /**
  2294. * Cleans up when a drag operation has stopped.
  2295. *
  2296. * @since 1.0
  2297. * @access private
  2298. * @method _blockDragStop
  2299. * @param {Object} e The event object.
  2300. * @param {Object} ui An object with additional info for the drag.
  2301. */
  2302. _blockDragStop: function( e, ui )
  2303. {
  2304. var scrollTop = $( window ).scrollTop(),
  2305. parent = ui.item.parent(),
  2306. initialPos = null;
  2307. // Get the node to scroll to once removing the node highlights affects the body height.
  2308. if ( parent.hasClass( 'fl-drop-target' ) && parent.closest( '[data-node]' ).length ) {
  2309. parent = parent.closest( '[data-node]' );
  2310. initialPos = parent.offset().top - scrollTop;
  2311. }
  2312. else {
  2313. initialPos = parent.offset().top - scrollTop;
  2314. }
  2315. // Show the panel if a block was dropped back in.
  2316. if ( parent.hasClass( 'fl-builder-blocks-section-content' ) ) {
  2317. FLBuilder._showPanel();
  2318. }
  2319. // Finish dragging.
  2320. FLBuilder._dragEnabled = false;
  2321. FLBuilder._dragging = false;
  2322. FLBuilder._bindOverlayEvents();
  2323. FLBuilder._highlightEmptyCols();
  2324. FLBuilder._enableGlobalRows();
  2325. FLBuilder._enableGlobalCols();
  2326. FLBuilder._setupEmptyLayout();
  2327. $( 'body' ).removeClass( 'fl-builder-dragging' );
  2328. // Scroll the page back to where it was.
  2329. scrollTo( 0, parent.offset().top - initialPos );
  2330. FLBuilder.triggerHook('didStopDrag');
  2331. },
  2332. /**
  2333. * Cleans up when a drag operation has canceled.
  2334. *
  2335. * @since 1.0
  2336. * @access private
  2337. * @method _blockDragCancel
  2338. */
  2339. _blockDragCancel: function()
  2340. {
  2341. if ( FLBuilder._dragEnabled && ! FLBuilder._dragging ) {
  2342. FLBuilder._dragEnabled = false;
  2343. FLBuilder._dragging = false;
  2344. FLBuilder._bindOverlayEvents();
  2345. FLBuilder._highlightEmptyCols();
  2346. FLBuilder._enableGlobalRows();
  2347. FLBuilder._setupEmptyLayout();
  2348. $( 'body' ).removeClass( 'fl-builder-dragging' );
  2349. $( '.fl-node-drag-init' ).removeClass( 'fl-node-drag-init' );
  2350. $( '.fl-node-dragging' ).removeClass( 'fl-node-dragging' );
  2351. scrollTo( 0, FLBuilder._dragInitialScrollTop );
  2352. }
  2353. },
  2354. /**
  2355. * Reorders a node within its parent.
  2356. *
  2357. * @since 1.9
  2358. * @access private
  2359. * @method _reorderNode
  2360. * @param {String} nodeId The node ID of the node.
  2361. * @param {Number} position The new position.
  2362. */
  2363. _reorderNode: function( nodeId, position )
  2364. {
  2365. FLBuilder.ajax( {
  2366. action: 'reorder_node',
  2367. node_id: nodeId,
  2368. position: position
  2369. } );
  2370. },
  2371. /**
  2372. * Moves a node to a new parent.
  2373. *
  2374. * @since 1.9
  2375. * @access private
  2376. * @method _moveNode
  2377. * @param {String} newParent The node ID of the new parent.
  2378. * @param {String} nodeId The node ID of the node.
  2379. * @param {Number} position The new position.
  2380. */
  2381. _moveNode: function( newParent, nodeId, position )
  2382. {
  2383. FLBuilder.ajax({
  2384. action: 'move_node',
  2385. new_parent: newParent,
  2386. node_id: nodeId,
  2387. position: position
  2388. });
  2389. },
  2390. /**
  2391. * Removes all node overlays and hides any tooltip helpies.
  2392. *
  2393. * @since 1.0
  2394. * @access private
  2395. * @method _removeAllOverlays
  2396. */
  2397. _removeAllOverlays: function()
  2398. {
  2399. FLBuilder._removeRowOverlays();
  2400. FLBuilder._removeColOverlays();
  2401. FLBuilder._removeColHighlightGuides();
  2402. FLBuilder._removeModuleOverlays();
  2403. FLBuilder._hideTipTips();
  2404. FLBuilder._closeAllSubmenus();
  2405. },
  2406. /**
  2407. * Appends a node action overlay to the layout.
  2408. *
  2409. * @since 1.6.3.3
  2410. * @access private
  2411. * @method _appendOverlay
  2412. * @param {Object} node A jQuery reference to the node this overlay is associated with.
  2413. * @param {Object} template A rendered wp.template.
  2414. * @return {Object} The overlay element.
  2415. */
  2416. _appendOverlay: function( node, template )
  2417. {
  2418. var overlayPos = 0,
  2419. overlay = null,
  2420. isRow = node.hasClass( 'fl-row' ),
  2421. content = isRow ? node.find( '> .fl-row-content-wrap' ) : node.find( '> .fl-node-content' ),
  2422. margins = {
  2423. 'top' : parseInt( content.css( 'margin-top' ), 10 ),
  2424. 'bottom' : parseInt( content.css( 'margin-bottom' ), 10 )
  2425. };
  2426. // Append the template.
  2427. node.append( template );
  2428. // Add the active class to the node.
  2429. node.addClass( 'fl-block-overlay-active' );
  2430. // Init TipTips
  2431. FLBuilder._initTipTips();
  2432. // Get a reference to the overlay.
  2433. overlay = node.find( '> .fl-block-overlay' );
  2434. // Adjust the overlay positions to account for negative margins.
  2435. if ( margins.top < 0 ) {
  2436. overlayPos = parseInt( overlay.css( 'top' ), 10 );
  2437. overlayPos = isNaN( overlayPos ) ? 0 : overlayPos;
  2438. overlay.css( 'top', ( margins.top + overlayPos ) + 'px' );
  2439. }
  2440. if ( margins.bottom < 0 ) {
  2441. overlayPos = parseInt( overlay.css( 'bottom' ), 10 );
  2442. overlayPos = isNaN( overlayPos ) ? 0 : overlayPos;
  2443. overlay.css( 'bottom', ( margins.bottom + overlayPos ) + 'px' );
  2444. }
  2445. return overlay;
  2446. },
  2447. /**
  2448. * Builds the overflow menu for an overlay if necessary.
  2449. *
  2450. * @since 1.9
  2451. * @access private
  2452. * @method _buildOverlayOverflowMenu
  2453. * @param {Object} overlay The overlay object.
  2454. */
  2455. _buildOverlayOverflowMenu: function( overlay )
  2456. {
  2457. var header = overlay.find( '.fl-block-overlay-header' )
  2458. actions = overlay.find( '.fl-block-overlay-actions' ),
  2459. hasRules = overlay.find( '.fl-block-has-rules' ),
  2460. original = actions.data( 'original' ),
  2461. actionsWidth = 0,
  2462. items = null,
  2463. itemsWidth = 0,
  2464. item = null,
  2465. i = 0,
  2466. visibleItems = [],
  2467. overflowItems = [],
  2468. menuData = [],
  2469. template = wp.template( 'fl-overlay-overflow-menu' );
  2470. // Use the original copy if we have one.
  2471. if ( undefined != original ) {
  2472. actions.after( original );
  2473. actions.remove();
  2474. actions = original;
  2475. }
  2476. // Save a copy of the original actions.
  2477. actions.data( 'original', actions.clone() );
  2478. // Get the actions width and items. Subtract any padding plus 2px (8px)
  2479. actionsWidth = Math.floor(actions[0].getBoundingClientRect().width) - 8;
  2480. items = actions.find( ' > i, > span.fl-builder-has-submenu' );
  2481. // Add the width of the visibility rules indicator if there is one.
  2482. if ( hasRules.length && actionsWidth + hasRules.outerWidth() > header.outerWidth() ) {
  2483. itemsWidth += hasRules.outerWidth();
  2484. }
  2485. // Find visible and overflow items.
  2486. for( ; i < items.length; i++ ) {
  2487. item = items.eq( i );
  2488. itemsWidth += Math.floor(item[0].getBoundingClientRect().width);
  2489. if ( itemsWidth > actionsWidth ) {
  2490. overflowItems.push( item );
  2491. item.remove();
  2492. }
  2493. else {
  2494. visibleItems.push( item );
  2495. }
  2496. }
  2497. // Build the menu if we have overflow items.
  2498. if ( overflowItems.length > 0 ) {
  2499. if( visibleItems.length > 0 ) {
  2500. overflowItems.unshift( visibleItems.pop().remove() );
  2501. }
  2502. for( i = 0; i < overflowItems.length; i++ ) {
  2503. if ( overflowItems[ i ].is( '.fl-builder-has-submenu' ) ) {
  2504. menuData.push( {
  2505. type : 'submenu',
  2506. label : overflowItems[ i ].find( '.fa, .fas, .far' ).data( 'title' ),
  2507. submenu : overflowItems[ i ].find( '.fl-builder-submenu' )[0].outerHTML
  2508. } );
  2509. }
  2510. else {
  2511. menuData.push( {
  2512. type : 'action',
  2513. label : overflowItems[ i ].data( 'title' ),
  2514. className : overflowItems[ i ].removeClass( function( i, c ) {
  2515. return c.replace( /fl-block-([^\s]+)/, '' );
  2516. } ).attr( 'class' )
  2517. } );
  2518. }
  2519. }
  2520. actions.append( template( menuData ) );
  2521. FLBuilder._initTipTips();
  2522. }
  2523. },
  2524. /* Rows
  2525. ----------------------------------------------------------*/
  2526. /**
  2527. * Removes all row overlays from the page.
  2528. *
  2529. * @since 1.0
  2530. * @access private
  2531. * @method _removeRowOverlays
  2532. */
  2533. _removeRowOverlays: function()
  2534. {
  2535. $('.fl-row').removeClass('fl-block-overlay-active');
  2536. $('.fl-row-overlay').remove();
  2537. $('.fl-module').removeClass('fl-module-adjust-height');
  2538. $('body').removeClass( 'fl-builder-row-resizing' );
  2539. FLBuilder._closeAllSubmenus();
  2540. },
  2541. /**
  2542. * Removes all row overlays from the page.
  2543. *
  2544. * @since 1.0
  2545. * @access private
  2546. * @method _removeRowOverlays
  2547. */
  2548. _disableGlobalRows: function()
  2549. {
  2550. if ( 'row' == FLBuilderConfig.userTemplateType ) {
  2551. return;
  2552. }
  2553. $('.fl-row.fl-node-global').addClass( 'fl-node-disabled' );
  2554. },
  2555. /**
  2556. * Removes all global column overlays from the page.
  2557. *
  2558. * @since 2.1
  2559. * @access private
  2560. * @method _disableGlobalCols
  2561. */
  2562. _disableGlobalCols: function()
  2563. {
  2564. if ( 'column' == FLBuilderConfig.userTemplateType ) {
  2565. return;
  2566. }
  2567. $('.fl-row:not(.fl-node-global) .fl-col.fl-node-global').addClass( 'fl-node-disabled' );
  2568. },
  2569. /**
  2570. * Removes all row overlays from the page.
  2571. *
  2572. * @since 1.0
  2573. * @access private
  2574. * @method _removeRowOverlays
  2575. */
  2576. _enableGlobalRows: function()
  2577. {
  2578. if ( 'row' == FLBuilderConfig.userTemplateType ) {
  2579. return;
  2580. }
  2581. $( '.fl-node-disabled' ).removeClass( 'fl-node-disabled' );
  2582. },
  2583. /**
  2584. * Re-enable global column from the page.
  2585. *
  2586. * @since 2.1
  2587. * @access private
  2588. * @method _enableGlobalCols
  2589. */
  2590. _enableGlobalCols: function()
  2591. {
  2592. if ( 'column' == FLBuilderConfig.userTemplateType ) {
  2593. return;
  2594. }
  2595. $( '.fl-node-disabled' ).removeClass( 'fl-node-disabled' );
  2596. },
  2597. /**
  2598. * Shows an overlay with actions when the mouse enters a row.
  2599. *
  2600. * @since 1.0
  2601. * @access private
  2602. * @method _rowMouseenter
  2603. */
  2604. _rowMouseenter: function()
  2605. {
  2606. var row = $( this ),
  2607. rowTop = row.offset().top,
  2608. childTop = null,
  2609. overlay = null,
  2610. template = wp.template( 'fl-row-overlay' );
  2611. if ( row.closest( '.fl-builder-node-loading' ).length ) {
  2612. return;
  2613. }
  2614. else if ( ! row.hasClass( 'fl-block-overlay-active' ) ) {
  2615. // Append the overlay.
  2616. overlay = FLBuilder._appendOverlay( row, template( {
  2617. node : row.attr('data-node'),
  2618. global : row.hasClass( 'fl-node-global' ),
  2619. hasRules : row.hasClass( 'fl-node-has-rules' ),
  2620. } ) );
  2621. // Adjust the overlay position if covered by negative margin content.
  2622. row.find( '.fl-node-content:visible' ).each( function(){
  2623. var top = $( this ).offset().top;
  2624. childTop = ( null === childTop || childTop > top ) ? top : childTop;
  2625. } );
  2626. if ( null !== childTop && childTop < rowTop ) {
  2627. overlay.css( 'top', ( childTop - rowTop - 30 ) + 'px' );
  2628. }
  2629. // Put action headers on the bottom if they're hidden.
  2630. if ( overlay.offset().top < 43 ) {
  2631. overlay.addClass( 'fl-row-overlay-header-bottom' );
  2632. }
  2633. // Adjust the height of modules if needed.
  2634. row.find( '.fl-module' ).each( function(){
  2635. var module = $( this );
  2636. if ( module.outerHeight( true ) < 20 ) {
  2637. module.addClass( 'fl-module-adjust-height' );
  2638. }
  2639. } );
  2640. // Build the overlay overflow menu if needed.
  2641. FLBuilder._buildOverlayOverflowMenu( overlay );
  2642. }
  2643. },
  2644. /**
  2645. * Removes overlays when the mouse leaves a row.
  2646. *
  2647. * @since 1.0
  2648. * @access private
  2649. * @method _rowMouseleave
  2650. * @param {Object} e The event object.
  2651. */
  2652. _rowMouseleave: function(e)
  2653. {
  2654. var toElement = $(e.toElement) || $(e.relatedTarget),
  2655. isOverlay = toElement.hasClass('fl-row-overlay'),
  2656. isOverlayChild = toElement.closest('.fl-row-overlay').length > 0,
  2657. isTipTip = toElement.is('#tiptip_holder'),
  2658. isTipTipChild = toElement.closest('#tiptip_holder').length > 0;
  2659. if(isOverlay || isOverlayChild || isTipTip || isTipTipChild) {
  2660. return;
  2661. }
  2662. FLBuilder._removeRowOverlays();
  2663. },
  2664. /**
  2665. * Returns a helper element for row drag operations.
  2666. *
  2667. * @since 1.0
  2668. * @access private
  2669. * @method _rowDragHelper
  2670. * @return {Object} The helper element.
  2671. */
  2672. _rowDragHelper: function()
  2673. {
  2674. return $('<div class="fl-builder-block-drag-helper">' + FLBuilderStrings.row + '</div>');
  2675. },
  2676. /**
  2677. * Initializes dragging for row. Rows themselves aren't sortables
  2678. * as nesting that many sortables breaks down quickly and draggable by
  2679. * itself is slow. Instead, we are programmatically triggering the drag
  2680. * of our helper div that isn't a nested sortable but connected to the
  2681. * sortables in the main layout.
  2682. *
  2683. * @since 1.9
  2684. * @access private
  2685. * @method _rowDragInit
  2686. * @param {Object} e The event object.
  2687. */
  2688. _rowDragInit: function( e )
  2689. {
  2690. var handle = $( e.target ),
  2691. helper = $( '.fl-row-sortable-proxy-item' ),
  2692. row = handle.closest( '.fl-row' );
  2693. row.addClass( 'fl-node-dragging' );
  2694. FLBuilder._blockDragInit( e );
  2695. e.target = helper[ 0 ];
  2696. helper.trigger( e );
  2697. },
  2698. /**
  2699. * Callback that fires when dragging starts for a row.
  2700. *
  2701. * @since 1.9
  2702. * @access private
  2703. * @method _rowDragStart
  2704. * @param {Object} e The event object.
  2705. * @param {Object} ui An object with additional info for the drag.
  2706. */
  2707. _rowDragStart: function( e, ui )
  2708. {
  2709. var rows = $( FLBuilder._contentClass + ' .fl-row' ),
  2710. row = $( '.fl-node-dragging' );
  2711. if ( 1 === rows.length ) {
  2712. $( FLBuilder._contentClass ).addClass( 'fl-builder-empty' );
  2713. }
  2714. row.hide();
  2715. FLBuilder._blockDragStart( e, ui );
  2716. },
  2717. /**
  2718. * Callback for when a row drag operation completes.
  2719. *
  2720. * @since 1.0
  2721. * @access private
  2722. * @method _rowDragStop
  2723. * @param {Object} e The event object.
  2724. * @param {Object} ui An object with additional info for the drag.
  2725. */
  2726. _rowDragStop: function( e, ui )
  2727. {
  2728. var item = ui.item,
  2729. parent = item.parent(),
  2730. row = null,
  2731. group = null,
  2732. position = 0;
  2733. FLBuilder._blockDragStop( e, ui );
  2734. // A row was dropped back into the row list.
  2735. if ( parent.hasClass( 'fl-builder-rows' ) ) {
  2736. item.remove();
  2737. return;
  2738. }
  2739. // A row was dropped back into the sortable proxy.
  2740. else if ( parent.hasClass( 'fl-row-sortable-proxy' ) ) {
  2741. $( '.fl-node-dragging' ).removeClass( 'fl-node-dragging' ).show();
  2742. return;
  2743. }
  2744. // Add a new row.
  2745. else if ( item.hasClass( 'fl-builder-block' ) ) {
  2746. // Cancel the drop if the sortable is disabled?
  2747. if ( parent.hasClass( 'fl-sortable-disabled' ) ) {
  2748. item.remove();
  2749. FLBuilder._showPanel();
  2750. return;
  2751. }
  2752. // A new row was dropped into column.
  2753. else if ( parent.hasClass( 'fl-col-content' ) ) {
  2754. FLBuilder._addColGroup(
  2755. item.closest( '.fl-col' ).attr( 'data-node' ),
  2756. item.attr( 'data-cols' ),
  2757. parent.find( '> .fl-module, .fl-col-group, .fl-builder-block' ).index( item )
  2758. );
  2759. }
  2760. // A new row was dropped next to a column.
  2761. else if ( parent.hasClass( 'fl-col-drop-target' ) ) {
  2762. FLBuilder._addCols(
  2763. parent.closest( '.fl-col' ),
  2764. parent.hasClass( 'fl-col-drop-target-last' ) ? 'after' : 'before',
  2765. item.attr( 'data-cols' ),
  2766. parent.closest( '.fl-col-group-nested' ).length > 0
  2767. );
  2768. }
  2769. // A new row was dropped into a column group position.
  2770. else if ( parent.hasClass( 'fl-col-group-drop-target' ) ) {
  2771. group = item.closest( '.fl-col-group' );
  2772. position = item.closest( '.fl-row' ).find( '.fl-row-content > .fl-col-group' ).index( group );
  2773. FLBuilder._addColGroup(
  2774. item.closest( '.fl-row' ).attr( 'data-node' ),
  2775. item.attr( 'data-cols' ),
  2776. parent.hasClass( 'fl-drop-target-last' ) ? position + 1 : position
  2777. );
  2778. }
  2779. // A row was dropped into a row position.
  2780. else {
  2781. row = item.closest( '.fl-row' );
  2782. position = ! row.length ? 0 : $( FLBuilder._contentClass + ' > .fl-row' ).index( row );
  2783. FLBuilder._addRow(
  2784. item.attr('data-cols'),
  2785. parent.hasClass( 'fl-drop-target-last' ) ? position + 1 : position
  2786. );
  2787. }
  2788. // Remove the helper.
  2789. item.remove();
  2790. // Show the builder panel.
  2791. FLBuilder._showPanel();
  2792. // Show the module list.
  2793. $( '.fl-builder-modules' ).siblings( '.fl-builder-blocks-section-title' ).eq( 0 ).trigger( 'click' );
  2794. }
  2795. // Reorder a row.
  2796. else {
  2797. row = $( '.fl-node-dragging' ).removeClass( 'fl-node-dragging' ).show();
  2798. // Make sure a single row wasn't dropped back into the main layout.
  2799. if ( ! parent.parent().hasClass( 'fl-builder-content' ) ) {
  2800. // Move the row in the UI.
  2801. if ( parent.hasClass( 'fl-drop-target-last' ) ) {
  2802. parent.parent().after( row );
  2803. }
  2804. else {
  2805. parent.parent().before( row );
  2806. }
  2807. // Reorder the row.
  2808. FLBuilder._reorderNode(
  2809. row.attr('data-node'),
  2810. row.index()
  2811. );
  2812. }
  2813. // Revert the proxy to its parent.
  2814. $( '.fl-row-sortable-proxy' ).append( ui.item );
  2815. }
  2816. },
  2817. /**
  2818. * Adds a new row to the layout.
  2819. *
  2820. * @since 1.0
  2821. * @access private
  2822. * @method _addRow
  2823. * @param {String} cols The type of column layout to use.
  2824. * @param {Number} position The position of the new row.
  2825. */
  2826. _addRow: function(cols, position)
  2827. {
  2828. FLBuilder._showNodeLoadingPlaceholder( $( FLBuilder._contentClass ), position );
  2829. FLBuilder._newRowPosition = position;
  2830. FLBuilder.ajax({
  2831. action: 'render_new_row',
  2832. cols: cols,
  2833. position: position
  2834. }, FLBuilder._addRowComplete);
  2835. },
  2836. /**
  2837. * Adds the HTML for a new row to the layout when the AJAX
  2838. * add operation is complete. Adds a module if one is queued
  2839. * to go in the new row.
  2840. *
  2841. * @since 1.0
  2842. * @access private
  2843. * @method _addRowComplete
  2844. * @param {String} response The JSON response with the HTML for the new row.
  2845. */
  2846. _addRowComplete: function(response)
  2847. {
  2848. var data = 'object' === typeof response ? response : JSON.parse(response),
  2849. content = $(FLBuilder._contentClass),
  2850. rowId = $(data.html).data('node'),
  2851. module = FLBuilder._addModuleAfterNodeRender;
  2852. // Add new row info to the data.
  2853. data.nodeParent = content;
  2854. data.nodePosition = FLBuilder._newRowPosition;
  2855. // Render the layout.
  2856. FLBuilder._renderLayout( data, function(){
  2857. // Add a module to the newly created row.
  2858. if(module !== null) {
  2859. $('.fl-node-' + rowId + ' .fl-col-content').append(module);
  2860. FLBuilder._reorderModule(module);
  2861. FLBuilder._addModuleAfterNodeRender = null;
  2862. }
  2863. FLBuilder._removeNodeLoadingPlaceholder( $( '.fl-node-' + rowId ) );
  2864. FLBuilder.triggerHook( 'didAddRow', rowId );
  2865. } );
  2866. },
  2867. /**
  2868. * Callback for when the delete row button is clicked.
  2869. *
  2870. * @since 1.0
  2871. * @access private
  2872. * @method _deleteRowClicked
  2873. * @param {Object} e The event object.
  2874. */
  2875. _deleteRowClicked: function( e )
  2876. {
  2877. var row = $(this).closest('.fl-row'),
  2878. result = null;
  2879. if(!row.find('.fl-module').length) {
  2880. FLBuilder._deleteRow(row);
  2881. }
  2882. else {
  2883. result = confirm(FLBuilderStrings.deleteRowMessage);
  2884. if(result) {
  2885. FLBuilder._deleteRow(row);
  2886. }
  2887. }
  2888. FLBuilder._removeAllOverlays();
  2889. e.stopPropagation();
  2890. },
  2891. /**
  2892. * Deletes a row.
  2893. *
  2894. * @since 1.0
  2895. * @access private
  2896. * @method _deleteRow
  2897. * @param {Object} row A jQuery reference of the row to delete.
  2898. */
  2899. _deleteRow: function(row)
  2900. {
  2901. var nodeId = row.attr('data-node');
  2902. FLBuilder.ajax({
  2903. action: 'delete_node',
  2904. node_id: nodeId
  2905. });
  2906. row.empty();
  2907. row.remove();
  2908. FLBuilder._setupEmptyLayout();
  2909. FLBuilder._removeRowOverlays();
  2910. FLBuilder.triggerHook( 'didDeleteRow', nodeId );
  2911. },
  2912. /**
  2913. * Duplicates a row.
  2914. *
  2915. * @since 1.3.8
  2916. * @access private
  2917. * @method _rowCopyClicked
  2918. * @param {Object} e The event object.
  2919. */
  2920. _rowCopyClicked: function(e)
  2921. {
  2922. var row = $( this ).closest( '.fl-row' ),
  2923. nodeId = row.attr( 'data-node' ),
  2924. position = $( FLBuilder._contentClass + ' > .fl-row' ).index( row ) + 1,
  2925. clone = row.clone(),
  2926. form = $( '.fl-builder-settings[data-node]' ),
  2927. formNodeId = form.attr( 'data-node' ),
  2928. formNode = ( formNodeId === nodeId ) ? row : row.find( '[data-node="' + formNodeId + '"]' ),
  2929. settings = null;
  2930. if ( form.length && formNode.length ) {
  2931. settings = FLBuilder._getSettings( form );
  2932. FLBuilderSettingsConfig.nodes[ formNodeId ] = settings;
  2933. }
  2934. clone.addClass( 'fl-node-' + nodeId + '-clone fl-builder-node-clone' );
  2935. clone.find( '.fl-block-overlay' ).remove();
  2936. row.after( clone );
  2937. $( 'html, body' ).animate( {
  2938. scrollTop: clone.offset().top - 75
  2939. }, 500 );
  2940. FLBuilder._showNodeLoading( nodeId + '-clone' );
  2941. FLBuilder._newRowPosition = position;
  2942. FLBuilder.ajax( {
  2943. action: 'copy_row',
  2944. node_id: nodeId,
  2945. settings: settings,
  2946. settings_id: formNodeId
  2947. }, function( response ) {
  2948. var data = JSON.parse( response );
  2949. data.duplicatedRow = nodeId;
  2950. FLBuilder._rowCopyComplete( data );
  2951. } );
  2952. e.stopPropagation();
  2953. },
  2954. /**
  2955. * Callback for when a row has been duplicated.
  2956. *
  2957. * @since 1.7
  2958. * @access private
  2959. * @method _rowCopyComplete
  2960. * @param {Object} data
  2961. */
  2962. _rowCopyComplete: function( data )
  2963. {
  2964. data.nodeParent = $( FLBuilder._contentClass );
  2965. data.nodePosition = FLBuilder._newRowPosition;
  2966. FLBuilder._renderLayout( data, function() {
  2967. FLBuilder.triggerHook( 'didDuplicateRow', {
  2968. newNodeId : data.nodeId,
  2969. oldNodeId : data.duplicatedRow
  2970. } );
  2971. data.nodeParent.find( '.fl-builder-node-loading' ).eq( 0 ).remove();
  2972. } );
  2973. },
  2974. /**
  2975. * Shows the settings lightbox and loads the row settings
  2976. * when the row settings button is clicked.
  2977. *
  2978. * @since 1.0
  2979. * @access private
  2980. * @method _rowSettingsClicked
  2981. */
  2982. _rowSettingsClicked: function( e )
  2983. {
  2984. var button = $( this ),
  2985. nodeId = button.closest( '.fl-row' ).attr( 'data-node' ),
  2986. global = button.closest( '.fl-block-overlay-global' ).length > 0,
  2987. win = null;
  2988. if ( global && 'row' != FLBuilderConfig.userTemplateType ) {
  2989. if ( FLBuilderConfig.userCanEditGlobalTemplates ) {
  2990. win = window.open( $( '.fl-row[data-node="' + nodeId + '"]' ).attr( 'data-template-url' ) );
  2991. win.FLBuilderGlobalNodeId = nodeId;
  2992. }
  2993. }
  2994. else if ( button.hasClass( 'fl-block-settings' ) ) {
  2995. FLBuilderSettingsForms.render( {
  2996. id : 'row',
  2997. nodeId : nodeId,
  2998. className : 'fl-builder-row-settings',
  2999. attrs : 'data-node="' + nodeId + '"',
  3000. buttons : ! global && ! FLBuilderConfig.lite && ! FLBuilderConfig.simpleUi ? ['save-as'] : [],
  3001. badges : global ? [ FLBuilderStrings.global ] : [],
  3002. settings : FLBuilderSettingsConfig.nodes[ nodeId ],
  3003. preview : {
  3004. type: 'row'
  3005. }
  3006. }, function() {
  3007. $( '#fl-field-width select' ).on( 'change', FLBuilder._rowWidthChanged );
  3008. $( '#fl-field-content_width select' ).on( 'change', FLBuilder._rowWidthChanged );
  3009. } );
  3010. }
  3011. e.stopPropagation();
  3012. },
  3013. /**
  3014. * Shows or hides the row max-width setting when the
  3015. * row or row content width is changed.
  3016. *
  3017. * @since 2.0
  3018. * @access private
  3019. * @method _rowWidthChanged
  3020. */
  3021. _rowWidthChanged: function()
  3022. {
  3023. var rowWidth = $( '#fl-field-width select' ).val(),
  3024. contentWidth = $( '#fl-field-content_width select' ).val(),
  3025. maxWidth = $( '#fl-field-max_content_width' );
  3026. if ( 'fixed' == rowWidth || ( 'full' == rowWidth && 'fixed' == contentWidth ) ) {
  3027. maxWidth.show();
  3028. } else {
  3029. maxWidth.hide();
  3030. }
  3031. },
  3032. /**
  3033. * Resets the max-width of a row.
  3034. *
  3035. * @since 2.0
  3036. * @access private
  3037. * @method _resetRowWidthClicked
  3038. */
  3039. _resetRowWidthClicked: function( e )
  3040. {
  3041. var button = $( this ),
  3042. row = button.closest( '.fl-row' ),
  3043. nodeId = row.attr( 'data-node' ),
  3044. content = row.find( '.fl-row-content' ),
  3045. width = FLBuilderConfig.global.row_width + 'px',
  3046. settings = $( '.fl-builder-row-settings' );
  3047. if ( row.hasClass( 'fl-row-fixed-width' ) ) {
  3048. row.css( 'max-width', width );
  3049. }
  3050. content.css( 'max-width', width );
  3051. if ( settings.length ) {
  3052. settings.find( '[name=max_content_width]' ).val( '' );
  3053. }
  3054. FLBuilder.ajax({
  3055. action : 'resize_row_content',
  3056. node : nodeId,
  3057. width : ''
  3058. });
  3059. FLBuilder._closeAllSubmenus();
  3060. FLBuilder.triggerHook( 'didResetRowWidth', nodeId );
  3061. e.stopPropagation();
  3062. },
  3063. /* Columns
  3064. ----------------------------------------------------------*/
  3065. /**
  3066. * Adds a dashed border to empty columns.
  3067. *
  3068. * @since 1.0
  3069. * @access private
  3070. * @method _highlightEmptyCols
  3071. */
  3072. _highlightEmptyCols: function()
  3073. {
  3074. var notGlobal = 'row' == FLBuilderConfig.userTemplateType || 'column' == FLBuilderConfig.userTemplateType ? '' : ':not(.fl-node-global)',
  3075. rows = $(FLBuilder._contentClass + ' .fl-row' + notGlobal),
  3076. cols = $(FLBuilder._contentClass + ' .fl-col' + notGlobal);
  3077. $( '.fl-row-highlight' ).removeClass('fl-row-highlight');
  3078. cols.removeClass('fl-col-highlight').find('.fl-col-content').css( 'height', '' );
  3079. cols.each(function(){
  3080. var col = $(this);
  3081. if(col.find('.fl-module, .fl-col').length === 0) {
  3082. col.addClass('fl-col-highlight');
  3083. }
  3084. });
  3085. },
  3086. /**
  3087. * Remove any column highlights
  3088. *
  3089. * @since 2.0
  3090. * @access private
  3091. * @method _removeEmptyColHighlights
  3092. */
  3093. _removeEmptyColHighlights: function() {
  3094. $( '.fl-row-highlight' ).removeClass('fl-row-highlight');
  3095. $( '.fl-col-highlight' ).removeClass('fl-col-highlight');
  3096. },
  3097. /**
  3098. * Sets up dashed borders to show where things can
  3099. * be dropped in rows and columns.
  3100. *
  3101. * @since 1.9
  3102. * @access private
  3103. * @method _highlightRowsAndColsForDrag
  3104. * @param {Object} target The event target for the drag.
  3105. */
  3106. _highlightRowsAndColsForDrag: function( target )
  3107. {
  3108. var notGlobal = 'row' == FLBuilderConfig.userTemplateType ? '' : ':not(.fl-node-global)';
  3109. // Do not highlight root parent column.
  3110. if ( 'column' == FLBuilderConfig.userTemplateType ) {
  3111. notGlobal = ':not(:first)';
  3112. }
  3113. // Highlight rows.
  3114. $( FLBuilder._contentClass + ' .fl-row' ).addClass( 'fl-row-highlight' );
  3115. // Highlight columns.
  3116. if ( ! target.closest( '.fl-row-overlay' ).length ) {
  3117. $( FLBuilder._contentClass + ' .fl-col' + notGlobal ).addClass( 'fl-col-highlight' );
  3118. }
  3119. },
  3120. /**
  3121. * Adjust the height of columns with modules in them
  3122. * to account for the drop zone and keep the layout
  3123. * from jumping around.
  3124. *
  3125. * @since 1.9
  3126. * @access private
  3127. * @method _adjustColHeightsForDrag
  3128. */
  3129. _adjustColHeightsForDrag: function()
  3130. {
  3131. var notGlobalRow = 'row' == FLBuilderConfig.userTemplateType ? '' : '.fl-row:not(.fl-node-global) ',
  3132. notGlobalCol = 'column' == FLBuilderConfig.userTemplateType ? '' : '.fl-col:not(.fl-node-global) ',
  3133. content = $( FLBuilder._contentClass ),
  3134. notNested = content.find( notGlobalRow + '.fl-col-group:not(.fl-col-group-nested) > ' + notGlobalCol + '> .fl-col-content' ),
  3135. nested = content.find( notGlobalRow + '.fl-col-group-nested ' + notGlobalCol + '.fl-col-content' ),
  3136. col = null,
  3137. i = 0;
  3138. $( '.fl-node-drag-init' ).hide();
  3139. for ( ; i < nested.length; i++ ) {
  3140. FLBuilder._adjustColHeightForDrag( nested.eq( i ) );
  3141. }
  3142. for ( i = 0; i < notNested.length; i++ ) {
  3143. FLBuilder._adjustColHeightForDrag( notNested.eq( i ) );
  3144. }
  3145. $( '.fl-node-drag-init' ).show();
  3146. },
  3147. /**
  3148. * Adjust the height of a single column for dragging.
  3149. *
  3150. * @since 1.9
  3151. * @access private
  3152. * @method _adjustColHeightForDrag
  3153. */
  3154. _adjustColHeightForDrag: function( col )
  3155. {
  3156. if ( col.find( '.fl-module:visible, .fl-col:visible' ).length ) {
  3157. col.height( col.height() + 45 );
  3158. }
  3159. },
  3160. /**
  3161. * Adds a border guide to a column when the column
  3162. * actions submenu is open for a module.
  3163. *
  3164. * @since 1.9
  3165. * @access private
  3166. * @method _showColHighlightGuide
  3167. */
  3168. _showColHighlightGuide: function()
  3169. {
  3170. var li = $( this ),
  3171. link = li.find( 'a' ),
  3172. col = li.closest( '.fl-col' ),
  3173. parentCol = col.parents( '.fl-col' ),
  3174. guide = $( '<div class="fl-col-highlight-guide"></div>' ),
  3175. guideTop = null,
  3176. overlayTop = li.closest( '.fl-block-overlay' ).offset().top;
  3177. if ( link.hasClass( 'fl-block-col-move-parent' ) || link.hasClass( 'fl-block-col-edit-parent' ) ) {
  3178. col = parentCol;
  3179. }
  3180. if ( col.hasClass( 'fl-col-highlight' ) ) {
  3181. return;
  3182. }
  3183. col.find( '> .fl-col-content' ).append( guide );
  3184. col.addClass( 'fl-col-has-highlight-guide' );
  3185. guideTop = guide.offset().top;
  3186. if ( guideTop > overlayTop ) {
  3187. guide.css( 'top', ( overlayTop - guideTop + 4 ) + 'px' );
  3188. }
  3189. },
  3190. /**
  3191. * Removes all column highlight guides.
  3192. *
  3193. * @since 1.9
  3194. * @access private
  3195. * @method _showColHighlightGuide
  3196. */
  3197. _removeColHighlightGuides: function()
  3198. {
  3199. $( '.fl-col-has-highlight-guide' ).removeClass( 'fl-col-has-highlight-guide' );
  3200. $( '.fl-col-highlight-guide' ).remove();
  3201. },
  3202. /**
  3203. * Shows an overlay with actions when the mouse enters a column.
  3204. *
  3205. * @since 1.1.9
  3206. * @access private
  3207. * @method _colMouseenter
  3208. */
  3209. _colMouseenter: function()
  3210. {
  3211. var col = $( this ),
  3212. group = col.closest( '.fl-col-group' ),
  3213. groupLoading = group.hasClass( 'fl-col-group-has-child-loading' ),
  3214. global = col.hasClass( 'fl-node-global' ),
  3215. parentGlobal = col.parents( '.fl-node-global' ).length > 0,
  3216. numCols = col.closest( '.fl-col-group' ).find( '> .fl-col' ).length,
  3217. index = group.find( '> .fl-col' ).index( col ),
  3218. first = 0 === index,
  3219. last = numCols === index + 1,
  3220. hasChildCols = col.find( '.fl-col' ).length > 0,
  3221. hasModules = col.find('.fl-module').length > 0,
  3222. parentCol = col.parents( '.fl-col' ),
  3223. parentGroup = parentCol.closest( '.fl-col-group' ),
  3224. hasParentCol = parentCol.length > 0,
  3225. isColTemplate = 'undefined' !== typeof col.data('template-url'),
  3226. isRootCol = 'column' == FLBuilderConfig.userTemplateType && ! hasParentCol;
  3227. numParentCols = hasParentCol ? parentGroup.find( '> .fl-col' ).length : 0,
  3228. parentIndex = parentGroup.find( '> .fl-col' ).index( parentCol ),
  3229. parentFirst = hasParentCol ? 0 === parentIndex : false,
  3230. parentLast = hasParentCol ? numParentCols === parentIndex + 1 : false,
  3231. contentWidth = col.find( '> .fl-col-content' ).width(),
  3232. row = col.closest('.fl-row'),
  3233. rowIsFixedWidth = !! row.find('.fl-row-fixed-width').addBack('.fl-row-fixed-width').length,
  3234. userCanResizeRows = FLBuilderConfig.rowResize.userCanResizeRows,
  3235. hasRules = col.hasClass( 'fl-node-has-rules' ),
  3236. template = wp.template( 'fl-col-overlay' ),
  3237. overlay = null;
  3238. if ( FLBuilderConfig.simpleUi ) {
  3239. return;
  3240. }
  3241. else if ( global && parentGlobal && hasModules && ! isColTemplate ) {
  3242. return;
  3243. }
  3244. else if ( global && 'column' == FLBuilderConfig.userTemplateType && hasModules ) {
  3245. return;
  3246. }
  3247. else if ( ! global && col.find( '.fl-module' ).length > 0 ) {
  3248. return;
  3249. }
  3250. else if ( col.find( '.fl-builder-node-loading-placeholder' ).length > 0 ) {
  3251. return;
  3252. }
  3253. else if ( ! hasModules && hasChildCols ) {
  3254. return;
  3255. }
  3256. else if ( parentGlobal && hasChildCols && ! isColTemplate ) {
  3257. return;
  3258. }
  3259. else if ( col.closest( '.fl-builder-node-loading' ).length ) {
  3260. return;
  3261. }
  3262. else if ( ! col.hasClass( 'fl-block-overlay-active' ) ) {
  3263. // Remove existing overlays.
  3264. FLBuilder._removeColOverlays();
  3265. FLBuilder._removeModuleOverlays();
  3266. // Append the template.
  3267. overlay = FLBuilder._appendOverlay( col, template( {
  3268. global : global,
  3269. groupLoading : groupLoading,
  3270. numCols : numCols,
  3271. first : first,
  3272. last : last,
  3273. isRootCol : isRootCol,
  3274. hasChildCols : hasChildCols,
  3275. hasParentCol : hasParentCol,
  3276. parentFirst : parentFirst,
  3277. parentLast : parentLast,
  3278. numParentCols : numParentCols,
  3279. contentWidth : contentWidth,
  3280. rowIsFixedWidth : rowIsFixedWidth,
  3281. userCanResizeRows : userCanResizeRows,
  3282. hasRules : hasRules,
  3283. } ) );
  3284. // Build the overlay overflow menu if needed.
  3285. FLBuilder._buildOverlayOverflowMenu( overlay );
  3286. // Init column resizing.
  3287. FLBuilder._initColDragResizing();
  3288. }
  3289. $( 'body' ).addClass( 'fl-block-overlay-muted' );
  3290. },
  3291. /**
  3292. * Removes overlays when the mouse leaves a column.
  3293. *
  3294. * @since 1.1.9
  3295. * @access private
  3296. * @method _colMouseleave
  3297. * @param {Object} e The event object.
  3298. */
  3299. _colMouseleave: function(e)
  3300. {
  3301. var col = $(this),
  3302. toElement = $(e.toElement) || $(e.relatedTarget),
  3303. hasModules = col.find('.fl-module').length > 0,
  3304. global = col.hasClass( 'fl-node-global' ),
  3305. isColTemplate = 'undefined' !== typeof col.data('template-url'),
  3306. isTipTip = toElement.is('#tiptip_holder'),
  3307. isTipTipChild = toElement.closest('#tiptip_holder').length > 0;
  3308. if( isTipTip || isTipTipChild ) {
  3309. return;
  3310. }
  3311. if( hasModules && ! isColTemplate ) {
  3312. return;
  3313. }
  3314. FLBuilder._removeColOverlays();
  3315. FLBuilder._removeColHighlightGuides();
  3316. FLBuilder._closeAllSubmenus();
  3317. },
  3318. /**
  3319. * Removes all column overlays from the page.
  3320. *
  3321. * @since 1.6.4
  3322. * @access private
  3323. * @method _removeColOverlays
  3324. */
  3325. _removeColOverlays: function()
  3326. {
  3327. var cols = $( '.fl-col' );
  3328. cols.removeClass('fl-block-overlay-active');
  3329. cols.find('.fl-col-overlay').remove();
  3330. $('body').removeClass('fl-block-overlay-muted');
  3331. FLBuilder._closeAllSubmenus();
  3332. },
  3333. /**
  3334. * Returns a helper element for column drag operations.
  3335. *
  3336. * @since 1.9
  3337. * @access private
  3338. * @method _colDragHelper
  3339. * @return {Object} The helper element.
  3340. */
  3341. _colDragHelper: function()
  3342. {
  3343. return $('<div class="fl-builder-block-drag-helper">' + FLBuilderStrings.column + '</div>');
  3344. },
  3345. /**
  3346. * Initializes dragging for columns. Columns themselves aren't sortables
  3347. * as nesting that many sortables breaks down quickly and draggable by
  3348. * itself is slow. Instead, we are programmatically triggering the drag
  3349. * of our helper div that isn't a nested sortable but connected to the
  3350. * sortables in the main layout.
  3351. *
  3352. * @since 1.9
  3353. * @access private
  3354. * @method _colDragInit
  3355. * @param {Object} e The event object.
  3356. */
  3357. _colDragInit: function( e )
  3358. {
  3359. var handle = $( e.target ),
  3360. helper = $( '.fl-col-sortable-proxy-item' ),
  3361. col = handle.closest( '.fl-col' );
  3362. if ( handle.hasClass( 'fl-block-col-move-parent' ) ) {
  3363. col = col.parents( '.fl-col' );
  3364. }
  3365. col.addClass( 'fl-node-dragging' );
  3366. FLBuilder._blockDragInit( e );
  3367. FLBuilder._removeColHighlightGuides();
  3368. e.target = helper[ 0 ];
  3369. helper.trigger( e );
  3370. },
  3371. /**
  3372. * Callback that fires when dragging starts for a column.
  3373. *
  3374. * @since 1.9
  3375. * @access private
  3376. * @method _colDragStart
  3377. * @param {Object} e The event object.
  3378. * @param {Object} ui An object with additional info for the drag.
  3379. */
  3380. _colDragStart: function( e, ui )
  3381. {
  3382. var col = $( '.fl-node-dragging' );
  3383. col.hide();
  3384. FLBuilder._resetColumnWidths( col.parent() );
  3385. FLBuilder._blockDragStart( e, ui );
  3386. },
  3387. /**
  3388. * Callback that fires when dragging stops for a column.
  3389. *
  3390. * @since 1.9
  3391. * @access private
  3392. * @method _colDragStop
  3393. * @param {Object} e The event object.
  3394. * @param {Object} ui An object with additional info for the drag.
  3395. */
  3396. _colDragStop: function( e, ui )
  3397. {
  3398. FLBuilder._blockDragStop( e, ui );
  3399. var col = $( '.fl-node-dragging' ).removeClass( 'fl-node-dragging' ).show(),
  3400. colId = col.attr( 'data-node' ),
  3401. newParent = ui.item.parent(),
  3402. oldGroup = col.parent(),
  3403. oldGroupId = oldGroup.attr( 'data-node' )
  3404. newGroup = newParent.closest( '.fl-col-group' ),
  3405. newGroupId = newGroup.attr( 'data-node' ),
  3406. newRow = newParent.closest('.fl-row'),
  3407. position = 0;
  3408. // Cancel the drop if the sortable is disabled?
  3409. if ( newParent.hasClass( 'fl-sortable-disabled' ) ) {
  3410. FLBuilder._resetColumnWidths( oldGroup );
  3411. }
  3412. // A column was dropped back into the sortable proxy.
  3413. else if ( newParent.hasClass( 'fl-col-sortable-proxy' ) ) {
  3414. FLBuilder._resetColumnWidths( oldGroup );
  3415. }
  3416. // A column was dropped into a column.
  3417. else if ( newParent.hasClass( 'fl-col-content' ) ) {
  3418. // Remove the column.
  3419. col.remove();
  3420. // Remove empty old groups (needs to be done here for correct position).
  3421. if ( 0 === oldGroup.find( '.fl-col' ).length ) {
  3422. oldGroup.remove();
  3423. }
  3424. // Find the new group position.
  3425. position = newParent.find( '> .fl-module, .fl-col-group, .fl-col-sortable-proxy-item' ).index( ui.item );
  3426. // Add the new group.
  3427. FLBuilder._addColGroup( newParent.closest( '.fl-col' ).attr('data-node'), colId, position );
  3428. }
  3429. // A column was dropped into a column position.
  3430. else if ( newParent.hasClass( 'fl-col-drop-target' ) ) {
  3431. // Move the column in the UI.
  3432. if ( newParent.hasClass( 'fl-col-drop-target-last' ) ) {
  3433. newParent.parent().after( col );
  3434. }
  3435. else {
  3436. newParent.parent().before( col );
  3437. }
  3438. // Reset the UI column widths.
  3439. FLBuilder._resetColumnWidths( newGroup );
  3440. // Save the column move via AJAX.
  3441. if ( oldGroupId == newGroupId ) {
  3442. FLBuilder.ajax( {
  3443. action: 'reorder_col',
  3444. node_id: colId,
  3445. position: col.index()
  3446. } );
  3447. }
  3448. else {
  3449. FLBuilder.ajax( {
  3450. action: 'move_col',
  3451. node_id: colId,
  3452. new_parent: newGroupId,
  3453. position: col.index(),
  3454. resize: [ oldGroupId, newGroupId ]
  3455. } );
  3456. }
  3457. // Trigger a layout resize.
  3458. FLBuilder._resizeLayout();
  3459. }
  3460. // A column was dropped into a column group position.
  3461. else if ( newParent.hasClass( 'fl-col-group-drop-target' ) ) {
  3462. // Remove the column.
  3463. col.remove();
  3464. // Remove empty old groups (needs to be done here for correct position).
  3465. if ( 0 === oldGroup.find( '.fl-col' ).length ) {
  3466. oldGroup.remove();
  3467. }
  3468. // Find the new group position.
  3469. position = newRow.find( '.fl-row-content > .fl-col-group' ).index( newGroup );
  3470. position = newParent.hasClass( 'fl-drop-target-last' ) ? position + 1 : position;
  3471. // Add the new group.
  3472. FLBuilder._addColGroup( newRow.attr('data-node'), colId, position );
  3473. }
  3474. // A column was dropped into a row position.
  3475. else if ( newParent.hasClass( 'fl-row-drop-target' ) ) {
  3476. // Remove the column.
  3477. col.remove();
  3478. // Find the new row position.
  3479. position = newParent.closest( '.fl-builder-content' ).find( '.fl-row' ).index( newRow );
  3480. position = newParent.hasClass( 'fl-drop-target-last' ) ? position + 1 : position;
  3481. // Add the new row.
  3482. FLBuilder._addRow( colId, position );
  3483. }
  3484. // Remove empty old groups.
  3485. if ( 0 === oldGroup.find( '.fl-col' ).length ) {
  3486. oldGroup.remove();
  3487. }
  3488. // Revert the proxy to its parent.
  3489. $( '.fl-col-sortable-proxy' ).append( ui.item );
  3490. // Finish the drag.
  3491. FLBuilder._highlightEmptyCols();
  3492. FLBuilder._initDropTargets();
  3493. FLBuilder._initSortables();
  3494. FLBuilder._closeAllSubmenus();
  3495. },
  3496. /**
  3497. * Shows the settings lightbox and loads the column settings
  3498. * when the column settings button is clicked.
  3499. *
  3500. * @since 1.1.9
  3501. * @access private
  3502. * @method _colSettingsClicked
  3503. * @param {Object} e The event object.
  3504. */
  3505. _colSettingsClicked: function(e)
  3506. {
  3507. var button = $( this ),
  3508. col = button.closest('.fl-col'),
  3509. content = col.find( '> .fl-col-content' ),
  3510. hasSubmenu = button.parent().find( 'ul.fl-builder-submenu' ).length > 0,
  3511. global = button.closest( '.fl-block-overlay-global' ).length > 0,
  3512. isGlobalCol = button.closest( '.fl-block-overlay-global' ).hasClass( 'fl-col-overlay' ),
  3513. isColTemplate = 'column' != FLBuilderConfig.userTemplateType && 'undefined' !== typeof col.attr( 'data-template-url' ),
  3514. nodeId = null;
  3515. if ( FLBuilder._colResizing ) {
  3516. return;
  3517. }
  3518. if ( global && ! FLBuilderConfig.userCanEditGlobalTemplates ) {
  3519. return;
  3520. }
  3521. if ( hasSubmenu && ! button.hasClass( 'fl-col-overlay') ) {
  3522. return;
  3523. }
  3524. if ( button.hasClass( 'fl-block-col-edit-parent' ) ) {
  3525. col = col.parents( '.fl-col' );
  3526. }
  3527. nodeId = col.attr('data-node');
  3528. if ( global && isGlobalCol && isColTemplate ) {
  3529. if ( FLBuilderConfig.userCanEditGlobalTemplates ) {
  3530. win = window.open( $( '.fl-col[data-node="' + nodeId + '"]' ).attr( 'data-template-url' ) );
  3531. win.FLBuilderGlobalNodeId = nodeId;
  3532. }
  3533. }
  3534. else {
  3535. FLBuilderSettingsForms.render( {
  3536. id : 'col',
  3537. nodeId : nodeId,
  3538. className : 'fl-builder-col-settings',
  3539. attrs : 'data-node="' + nodeId + '"',
  3540. buttons : ! global && ! FLBuilderConfig.lite && ! FLBuilderConfig.simpleUi ? ['save-as'] : [],
  3541. badges : global ? [ FLBuilderStrings.global ] : [],
  3542. settings : FLBuilderSettingsConfig.nodes[ nodeId ],
  3543. preview : {
  3544. type: 'col'
  3545. }
  3546. }, function() {
  3547. if ( col.siblings( '.fl-col' ).length === 0 ) {
  3548. $( '#fl-builder-settings-section-general' ).hide();
  3549. }
  3550. else if( content.width() <= 40 ) {
  3551. $( '#fl-field-size' ).hide();
  3552. }
  3553. } );
  3554. }
  3555. e.stopPropagation();
  3556. },
  3557. /**
  3558. * Callback for when the copy column button is clicked.
  3559. *
  3560. * @since 2.0
  3561. * @access private
  3562. * @method _copyColClicked
  3563. * @param {Object} e The event object.
  3564. */
  3565. _copyColClicked: function( e )
  3566. {
  3567. var col = $( this ).closest( '.fl-col' ),
  3568. nodeId = col.attr( 'data-node' ),
  3569. clone = col.clone(),
  3570. group = col.parent(),
  3571. form = $( '.fl-builder-settings[data-node]' ),
  3572. formNodeId = form.attr( 'data-node' ),
  3573. formNode = ( formNodeId === nodeId ) ? col : col.find( '[data-node="' + formNodeId + '"]' ),
  3574. settings = null;
  3575. if ( form.length && formNode.length ) {
  3576. settings = FLBuilder._getSettings( form );
  3577. FLBuilderSettingsConfig.nodes[ formNodeId ] = settings;
  3578. }
  3579. clone.addClass( 'fl-node-' + nodeId + '-clone fl-builder-node-clone' );
  3580. clone.find( '.fl-block-overlay' ).remove();
  3581. col.after( clone );
  3582. FLBuilder._showNodeLoading( nodeId + '-clone' );
  3583. FLBuilder._newColParent = group;
  3584. FLBuilder._newColPosition = col.index() + 1;
  3585. FLBuilder._resetColumnWidths( group );
  3586. FLBuilder.ajax( {
  3587. action: 'copy_col',
  3588. node_id: nodeId,
  3589. settings: settings,
  3590. settings_id: formNodeId
  3591. }, function( response ){
  3592. var data = JSON.parse( response );
  3593. data.duplicatedColumn = nodeId;
  3594. FLBuilder._copyColComplete( data );
  3595. } );
  3596. e.stopPropagation();
  3597. },
  3598. /**
  3599. * Callback for when a column has been duplicated.
  3600. *
  3601. * @since 2.0
  3602. * @access private
  3603. * @method _copyColComplete
  3604. * @param {Object} data
  3605. */
  3606. _copyColComplete: function( data )
  3607. {
  3608. data.nodeParent = FLBuilder._newColParent;
  3609. data.nodePosition = FLBuilder._newColPosition;
  3610. FLBuilder._renderLayout( data, function(){
  3611. FLBuilder.triggerHook( 'didDuplicateColumn', {
  3612. newNodeId : data.nodeId,
  3613. oldNodeId : data.duplicatedColumn
  3614. } );
  3615. data.nodeParent.find( '.fl-builder-node-loading' ).eq( 0 ).remove();
  3616. } );
  3617. },
  3618. /**
  3619. * Callback for when the delete column button is clicked.
  3620. *
  3621. * @since 1.0
  3622. * @access private
  3623. * @method _deleteColClicked
  3624. * @param {Object} e The event object.
  3625. */
  3626. _deleteColClicked: function( e )
  3627. {
  3628. var button = $( this ),
  3629. col = button.closest( '.fl-col' ),
  3630. parentGroup = col.closest( '.fl-col-group' ),
  3631. parentCol = col.parents( '.fl-col' ),
  3632. hasParentCol = parentCol.length > 0,
  3633. parentChildren = parentCol.find( '> .fl-col-content > .fl-module, > .fl-col-content > .fl-col-group' ),
  3634. siblingCols = col.siblings( '.fl-col' ),
  3635. result = true;
  3636. if ( col.find( '.fl-module' ).length > 0 ) {
  3637. result = confirm( FLBuilderStrings.deleteColumnMessage );
  3638. }
  3639. // Handle deleting of nested columns.
  3640. if ( hasParentCol && 1 === parentChildren.length ) {
  3641. if ( 0 === siblingCols.length ) {
  3642. col = parentCol;
  3643. }
  3644. else if ( 1 === siblingCols.length && ! siblingCols.find( '.fl-module' ).length ) {
  3645. col = parentGroup;
  3646. }
  3647. }
  3648. if ( result ) {
  3649. FLBuilder._deleteCol( col );
  3650. FLBuilder._removeAllOverlays();
  3651. FLBuilder._highlightEmptyCols();
  3652. }
  3653. e.stopPropagation();
  3654. },
  3655. /**
  3656. * Deletes a column.
  3657. *
  3658. * @since 1.0
  3659. * @access private
  3660. * @method _deleteCol
  3661. * @param {Object} col A jQuery reference of the column to delete (can also be a group).
  3662. */
  3663. _deleteCol: function(col)
  3664. {
  3665. var nodeId = col.attr('data-node'),
  3666. row = col.closest('.fl-row'),
  3667. group = col.closest('.fl-col-group'),
  3668. cols = null,
  3669. width = 0;
  3670. col.remove();
  3671. rowCols = row.find('.fl-row-content > .fl-col-group > .fl-col');
  3672. groupCols = group.find(' > .fl-col');
  3673. if(0 === rowCols.length && 'row' != FLBuilderConfig.userTemplateType && 'column' != FLBuilderConfig.userTemplateType) {
  3674. FLBuilder._deleteRow(row);
  3675. }
  3676. else {
  3677. if(0 === groupCols.length) {
  3678. group.remove();
  3679. }
  3680. else {
  3681. if ( 6 === groupCols.length ) {
  3682. width = 16.65;
  3683. }
  3684. else if ( 7 === groupCols.length ) {
  3685. width = 14.28;
  3686. }
  3687. else {
  3688. width = Math.round( 100 / groupCols.length * 100 ) / 100;
  3689. }
  3690. groupCols.css('width', width + '%');
  3691. FLBuilder.triggerHook( 'didResetColumnWidths', {
  3692. cols : groupCols
  3693. } );
  3694. }
  3695. FLBuilder.ajax({
  3696. action : 'delete_col',
  3697. node_id : nodeId,
  3698. new_width : width
  3699. });
  3700. FLBuilder._initDropTargets();
  3701. FLBuilder._initSortables();
  3702. FLBuilder.triggerHook( 'didDeleteColumn', nodeId );
  3703. }
  3704. },
  3705. /**
  3706. * Inserts a column (or columns) before or after another column.
  3707. *
  3708. * @since 1.6.4
  3709. * @access private
  3710. * @method _addCols
  3711. * @param {Object} col A jQuery reference of the column to insert before or after.
  3712. * @param {String} insert Either before or after.
  3713. * @param {String} type The type of column(s) to insert.
  3714. * @param {Boolean} nested Whether these columns are nested or not.
  3715. */
  3716. _addCols: function( col, insert, type, nested )
  3717. {
  3718. var parent = col.closest( '.fl-col-group' ),
  3719. position = parent.find( '.fl-col' ).index( col );
  3720. type = typeof type == 'undefined' ? '1-col' : type;
  3721. nested = typeof nested == 'undefined' ? false : nested;
  3722. if ( 'after' == insert ) {
  3723. position++;
  3724. }
  3725. FLBuilder._showNodeLoadingPlaceholder( parent, position );
  3726. FLBuilder._removeAllOverlays();
  3727. FLBuilder.ajax( {
  3728. action : 'render_new_columns',
  3729. node_id : col.attr('data-node'),
  3730. insert : insert,
  3731. type : type,
  3732. nested : nested ? 1 : 0
  3733. }, FLBuilder._addColsComplete );
  3734. },
  3735. /**
  3736. * Adds the HTML for columns to the layout when the AJAX add
  3737. * operation is complete. Adds a module if one is queued to
  3738. * go in a new column.
  3739. *
  3740. * @since 1.9
  3741. * @access private
  3742. * @method _addColsComplete
  3743. * @param {Object|String} response The JSON response with the HTML for the new column(s).
  3744. */
  3745. _addColsComplete: function( response )
  3746. {
  3747. var data = 'object' === typeof response ? response : JSON.parse( response ),
  3748. col = null,
  3749. moduleData = FLBuilder._addModuleAfterNodeRender,
  3750. module = null;
  3751. data.nodeParent = FLBuilder._newColParent;
  3752. data.nodePosition = FLBuilder._newColPosition;
  3753. // Render the layout.
  3754. FLBuilder._renderLayout( data, function() {
  3755. // Add a module to a newly created column.
  3756. if ( moduleData !== null ) {
  3757. $( '.fl-module[data-node="' + moduleData.module.data( 'node' ) + '"]' ).remove()
  3758. col = $( '.fl-col[data-node="' + moduleData.colId + '"]' );
  3759. if ( 'after' == moduleData.position ) {
  3760. col.next().find( '.fl-col-content' ).append( moduleData.module );
  3761. }
  3762. else {
  3763. col.prev().find( '.fl-col-content' ).append( moduleData.module );
  3764. }
  3765. FLBuilder._reorderModule( moduleData.module );
  3766. FLBuilder._addModuleAfterNodeRender = null;
  3767. }
  3768. // Remove the loading placeholder.
  3769. FLBuilder._removeNodeLoadingPlaceholder( $( '.fl-node-' + data.nodeId ) );
  3770. FLBuilder.triggerHook( 'didAddColumn', data.nodeId );
  3771. FLBuilder.triggerHook( 'didResetColumnWidths', {
  3772. cols : $( '.fl-node-' + data.nodeId ).find( '> .fl-col' )
  3773. } );
  3774. } );
  3775. },
  3776. /**
  3777. * Adds a new column group to the layout.
  3778. *
  3779. * @since 1.0
  3780. * @access private
  3781. * @method _addColGroup
  3782. * @param {String} nodeId The node ID of the parent row.
  3783. * @param {String} cols The type of column layout to use.
  3784. * @param {Number} position The position of the new column group.
  3785. */
  3786. _addColGroup: function(nodeId, cols, position)
  3787. {
  3788. var parent = $( '.fl-node-' + nodeId );
  3789. // Save the new column group info.
  3790. FLBuilder._newColGroupPosition = position;
  3791. if ( parent.hasClass( 'fl-col' ) ) {
  3792. FLBuilder._newColGroupParent = parent.find( ' > .fl-col-content' );
  3793. }
  3794. else {
  3795. FLBuilder._newColGroupParent = parent.find( '.fl-row-content' );
  3796. }
  3797. // Show the loader.
  3798. FLBuilder._showNodeLoadingPlaceholder( FLBuilder._newColGroupParent, position );
  3799. // Send the request.
  3800. FLBuilder.ajax({
  3801. action : 'render_new_column_group',
  3802. cols : cols,
  3803. node_id : nodeId,
  3804. position : position
  3805. }, FLBuilder._addColGroupComplete);
  3806. },
  3807. /**
  3808. * Adds the HTML for a new column group to the layout when
  3809. * the AJAX add operation is complete. Adds a module if one
  3810. * is queued to go in the new column group.
  3811. *
  3812. * @since 1.0
  3813. * @access private
  3814. * @method _addColGroupComplete
  3815. * @param {String} response The JSON response with the HTML for the new column group.
  3816. */
  3817. _addColGroupComplete: function(response)
  3818. {
  3819. var data = JSON.parse(response),
  3820. html = $(data.html),
  3821. groupId = html.data('node'),
  3822. colId = html.find('.fl-col').data('node'),
  3823. module = FLBuilder._addModuleAfterNodeRender;
  3824. // Add new column group info to the data.
  3825. data.nodeParent = FLBuilder._newColGroupParent;
  3826. data.nodePosition = FLBuilder._newColGroupPosition;
  3827. // Render the layout.
  3828. FLBuilder._renderLayout( data, function(){
  3829. // Added the nested columns class if needed.
  3830. if ( data.nodeParent.hasClass( 'fl-col-content' ) ) {
  3831. data.nodeParent.parents( '.fl-col' ).addClass( 'fl-col-has-cols' );
  3832. }
  3833. // Add a module to the newly created column group.
  3834. if(module !== null) {
  3835. $('.fl-node-' + colId + ' .fl-col-content').append(module);
  3836. FLBuilder._reorderModule(module);
  3837. FLBuilder._addModuleAfterNodeRender = null;
  3838. }
  3839. // Remove the loading placeholder.
  3840. FLBuilder._removeNodeLoadingPlaceholder( $( '.fl-node-' + groupId ) );
  3841. FLBuilder.triggerHook( 'didAddColumnGroup', groupId );
  3842. } );
  3843. },
  3844. /**
  3845. * Initializes draggables for column resizing.
  3846. *
  3847. * @since 1.6.4
  3848. * @access private
  3849. * @method _initColDragResizing
  3850. */
  3851. _initColDragResizing: function()
  3852. {
  3853. $( '.fl-block-col-resize' ).not( '.fl-block-row-resize' ).draggable( {
  3854. axis : 'x',
  3855. start : FLBuilder._colDragResizeStart,
  3856. drag : FLBuilder._colDragResize,
  3857. stop : FLBuilder._colDragResizeStop
  3858. } );
  3859. },
  3860. /**
  3861. * Fires when dragging for a column resize starts.
  3862. *
  3863. * @since 1.6.4
  3864. * @access private
  3865. * @method _colDragResizeStart
  3866. * @param {Object} e The event object.
  3867. * @param {Object} ui An object with additional info for the drag.
  3868. */
  3869. _colDragResizeStart: function( e, ui )
  3870. {
  3871. // Setup resize vars.
  3872. var handle = $( ui.helper ),
  3873. direction = '',
  3874. resizeParent = handle.hasClass( 'fl-block-col-resize-parent' ),
  3875. parentCol = resizeParent ? handle.closest( '.fl-col' ).parents( '.fl-col' ) : null,
  3876. group = resizeParent ? parentCol.parents( '.fl-col-group' ) : handle.closest( '.fl-col-group' ),
  3877. cols = group.find( '> .fl-col' ),
  3878. col = resizeParent ? parentCol : handle.closest( '.fl-col' ),
  3879. colId = col.attr( 'data-node' ),
  3880. colSetting = $( '[data-node=' + colId + '] #fl-field-size input' ),
  3881. sibling = null,
  3882. siblingId = null,
  3883. siblingSetting = null,
  3884. availWidth = 100,
  3885. i = 0,
  3886. setting = null,
  3887. settingType = null;
  3888. // Find the direction and sibling.
  3889. if ( handle.hasClass( 'fl-block-col-resize-e' ) ) {
  3890. direction = 'e';
  3891. sibling = col.nextAll( '.fl-col' ).first();
  3892. }
  3893. else {
  3894. direction = 'w';
  3895. sibling = col.prevAll( '.fl-col' ).first();
  3896. }
  3897. siblingId = sibling.attr( 'data-node' );
  3898. siblingSetting = $( '[data-node=' + siblingId + '] #fl-field-size input' );
  3899. // Find the available width.
  3900. for ( ; i < cols.length; i++ ) {
  3901. if ( cols.eq( i ).data( 'node' ) == col.data( 'node' ) ) {
  3902. continue;
  3903. }
  3904. if ( cols.eq( i ).data( 'node' ) == sibling.data( 'node' ) ) {
  3905. continue;
  3906. }
  3907. availWidth -= parseFloat( cols.eq( i )[ 0 ].style.width );
  3908. }
  3909. // Find the setting if a column form is open.
  3910. if ( colSetting.length ) {
  3911. setting = colSetting;
  3912. settingType = 'col';
  3913. } else if ( siblingSetting.length ) {
  3914. setting = siblingSetting;
  3915. settingType = 'sibling';
  3916. }
  3917. // Build the resize data object.
  3918. FLBuilder._colResizeData = {
  3919. handle : handle,
  3920. feedbackLeft : handle.find( '.fl-block-col-resize-feedback-left' ),
  3921. feedbackRight : handle.find( '.fl-block-col-resize-feedback-right' ),
  3922. direction : direction,
  3923. groupWidth : group.outerWidth(),
  3924. col : col,
  3925. colWidth : parseFloat( col[ 0 ].style.width ) / 100,
  3926. sibling : sibling,
  3927. offset : ui.position.left,
  3928. availWidth : availWidth,
  3929. setting : setting,
  3930. settingType : settingType
  3931. };
  3932. // Set the resizing flag.
  3933. FLBuilder._colResizing = true;
  3934. // Add the body col resize class.
  3935. $( 'body' ).addClass( 'fl-builder-col-resizing' );
  3936. // Close the builder panel and destroy overlay events.
  3937. FLBuilder._closePanel();
  3938. FLBuilder._destroyOverlayEvents();
  3939. // Trigger the col-resize-start hook.
  3940. FLBuilder.triggerHook( 'col-resize-start' );
  3941. },
  3942. /**
  3943. * Fires when dragging for a column resize is in progress.
  3944. *
  3945. * @since 1.6.4
  3946. * @access private
  3947. * @method _colDragResize
  3948. * @param {Object} e The event object.
  3949. * @param {Object} ui An object with additional info for the drag.
  3950. */
  3951. _colDragResize: function( e, ui )
  3952. {
  3953. // Setup resize vars.
  3954. var data = FLBuilder._colResizeData,
  3955. directionRef = FLBuilderConfig.isRtl ? 'w' : 'e',
  3956. overlay = data.handle.closest( '.fl-block-overlay' ),
  3957. change = ( data.offset - ui.position.left ) / data.groupWidth,
  3958. colWidth = directionRef == data.direction ? ( data.colWidth - change ) * 100 : ( data.colWidth + change ) * 100,
  3959. colRound = Math.round( colWidth * 100 ) / 100,
  3960. siblingWidth = data.availWidth - colWidth,
  3961. siblingRound = Math.round( siblingWidth * 100 ) / 100,
  3962. minRound = 8,
  3963. maxRound = Math.round( ( data.availWidth - minRound ) * 100 ) / 100;
  3964. // Set the min/max width if needed.
  3965. if ( colRound < minRound ) {
  3966. colRound = minRound;
  3967. siblingRound = maxRound;
  3968. }
  3969. else if ( siblingRound < minRound ) {
  3970. colRound = maxRound;
  3971. siblingRound = minRound;
  3972. }
  3973. // Set the feedback values.
  3974. if ( directionRef == data.direction ) {
  3975. data.feedbackLeft.html( colRound.toFixed( 1 ) + '%' ).show();
  3976. data.feedbackRight.html( siblingRound.toFixed( 1 ) + '%' ).show();
  3977. }
  3978. else {
  3979. data.feedbackLeft.html( siblingRound.toFixed( 1 ) + '%' ).show();
  3980. data.feedbackRight.html( colRound.toFixed( 1 ) + '%' ).show();
  3981. }
  3982. // Set the width attributes.
  3983. data.col.css( 'width', colRound + '%' );
  3984. data.sibling.css( 'width', siblingRound + '%' );
  3985. // Update the setting if the col or sibling's settings are open.
  3986. if ( data.setting ) {
  3987. if ( 'col' === data.settingType ) {
  3988. data.setting.val( parseFloat( data.col[ 0 ].style.width ) );
  3989. } else if ( 'sibling' === data.settingType ) {
  3990. data.setting.val( parseFloat( data.sibling[ 0 ].style.width ) );
  3991. }
  3992. }
  3993. // Build the overlay overflow menu if needed.
  3994. FLBuilder._buildOverlayOverflowMenu( overlay );
  3995. // Trigger the col-resize-drag hook.
  3996. FLBuilder.triggerHook( 'col-resize-drag' );
  3997. },
  3998. /**
  3999. * Fires when dragging for a column resize stops.
  4000. *
  4001. * @since 1.6.4
  4002. * @access private
  4003. * @method _colDragResizeStop
  4004. * @param {Object} e The event object.
  4005. * @param {Object} ui An object with additional info for the drag.
  4006. */
  4007. _colDragResizeStop: function( e, ui )
  4008. {
  4009. var data = FLBuilder._colResizeData,
  4010. overlay = FLBuilder._colResizeData.handle.closest( '.fl-block-overlay' ),
  4011. colId = data.col.data( 'node' ),
  4012. colWidth = parseFloat( data.col[ 0 ].style.width ),
  4013. siblingId = data.sibling.data( 'node' ),
  4014. siblingWidth = parseFloat( data.sibling[ 0 ].style.width );
  4015. // Hide the feedback divs.
  4016. FLBuilder._colResizeData.feedbackLeft.hide();
  4017. FLBuilder._colResizeData.feedbackRight.hide();
  4018. // Save the resize data.
  4019. FLBuilder.ajax({
  4020. action : 'resize_cols',
  4021. col_id : colId,
  4022. col_width : colWidth,
  4023. sibling_id : siblingId,
  4024. sibling_width : siblingWidth
  4025. });
  4026. // Build the overlay overflow menu if needed.
  4027. FLBuilder._buildOverlayOverflowMenu( overlay );
  4028. // Reset the resize data.
  4029. FLBuilder._colResizeData = null;
  4030. // Remove the body col resize class.
  4031. $( 'body' ).removeClass( 'fl-builder-col-resizing' );
  4032. // Rebind overlay events.
  4033. FLBuilder._bindOverlayEvents();
  4034. // Set the resizing flag to false with a timeout so other events get the right value.
  4035. setTimeout( function() { FLBuilder._colResizing = false; }, 50 );
  4036. // Trigger the col-resize-stop hook.
  4037. FLBuilder.triggerHook( 'col-resize-stop' );
  4038. FLBuilder.triggerHook( 'didResizeColumn', {
  4039. colId : colId,
  4040. colWidth : colWidth,
  4041. siblingId : siblingId,
  4042. siblingWidth : siblingWidth
  4043. } );
  4044. },
  4045. /**
  4046. * Resets the widths of all columns in a row when the
  4047. * Reset Column Widths button is clicked.
  4048. *
  4049. * @since 1.6.4
  4050. * @access private
  4051. * @method _resetColumnWidthsClicked
  4052. * @param {Object} e The event object.
  4053. */
  4054. _resetColumnWidthsClicked: function( e )
  4055. {
  4056. var button = $( this ),
  4057. isRow = !! button.closest( '.fl-row-overlay' ).length,
  4058. group = null,
  4059. groups = null,
  4060. groupIds = [],
  4061. children = null,
  4062. i = 0,
  4063. settings = $( '.fl-builder-col-settings' ),
  4064. col = null;
  4065. if ( isRow ) {
  4066. groups = button.closest( '.fl-row' ).find( '.fl-row-content > .fl-col-group' );
  4067. } else {
  4068. groups = button.parents( '.fl-col-group' ).last();
  4069. }
  4070. groups.each( function() {
  4071. group = $( this );
  4072. children = group.find( '.fl-col-group' );
  4073. groupIds.push( group.data( 'node' ) );
  4074. FLBuilder._resetColumnWidths( group );
  4075. for ( i = 0; i < children.length; i++ ) {
  4076. FLBuilder._resetColumnWidths( children.eq( i ) );
  4077. groupIds.push( children.eq( i ).data( 'node' ) );
  4078. }
  4079. } );
  4080. if ( settings.length ) {
  4081. col = $( '.fl-node-' + settings.attr( 'data-node' ) );
  4082. settings.find( '#fl-field-size input' ).val( parseFloat( col[ 0 ].style.width ) );
  4083. }
  4084. FLBuilder.ajax({
  4085. action : 'reset_col_widths',
  4086. group_id : groupIds
  4087. });
  4088. FLBuilder.triggerHook( 'col-reset-widths' );
  4089. FLBuilder._closeAllSubmenus();
  4090. e.stopPropagation();
  4091. },
  4092. /**
  4093. * Resets the widths of all columns in a group.
  4094. *
  4095. * @since 1.6.4
  4096. * @access private
  4097. * @method _resetColumnWidths
  4098. * @param {Object} e The event object.
  4099. */
  4100. _resetColumnWidths: function( group )
  4101. {
  4102. var cols = group.find( ' > .fl-col:visible' ),
  4103. width = 0;
  4104. if ( 6 === cols.length ) {
  4105. width = 16.65;
  4106. }
  4107. else if ( 7 === cols.length ) {
  4108. width = 14.28;
  4109. }
  4110. else {
  4111. width = Math.round( 100 / cols.length * 100 ) / 100;
  4112. }
  4113. cols.css( 'width', width + '%' );
  4114. FLBuilder.triggerHook( 'didResetColumnWidths', {
  4115. cols : cols
  4116. } );
  4117. },
  4118. /* Modules
  4119. ----------------------------------------------------------*/
  4120. /**
  4121. * Shows an overlay with actions when the mouse enters a module.
  4122. *
  4123. * @since 1.0
  4124. * @access private
  4125. * @method _moduleMouseenter
  4126. */
  4127. _moduleMouseenter: function()
  4128. {
  4129. var module = $( this ),
  4130. moduleName = module.attr( 'data-name' ),
  4131. global = module.hasClass( 'fl-node-global' ),
  4132. parentGlobal = module.parents( '.fl-node-global' ).length > 0,
  4133. group = module.parents( '.fl-col-group' ).last(),
  4134. groupLoading = group.hasClass( 'fl-col-group-has-child-loading' ),
  4135. numCols = module.closest( '.fl-col-group' ).find( '> .fl-col' ).length,
  4136. col = module.closest( '.fl-col' ),
  4137. colFirst = 0 === col.index(),
  4138. colLast = numCols === col.index() + 1,
  4139. parentCol = col.parents( '.fl-col' ),
  4140. hasParentCol = parentCol.length > 0,
  4141. numParentCols = hasParentCol ? parentCol.closest( '.fl-col-group' ).find( '> .fl-col' ).length : 0,
  4142. parentFirst = hasParentCol ? 0 === parentCol.index() : false,
  4143. parentLast = hasParentCol ? numParentCols === parentCol.index() + 1 : false,
  4144. isRootCol = 'column' == FLBuilderConfig.userTemplateType && ! hasParentCol;
  4145. contentWidth = col.find( '> .fl-col-content' ).width(),
  4146. row = module.closest('.fl-row'),
  4147. isGlobalRow = row.hasClass( 'fl-node-global' ),
  4148. rowIsFixedWidth = !! row.find('.fl-row-fixed-width').addBack('.fl-row-fixed-width').length,
  4149. userCanResizeRows = FLBuilderConfig.rowResize.userCanResizeRows,
  4150. hasRules = module.hasClass( 'fl-node-has-rules' ),
  4151. colHasRules = col.hasClass( 'fl-node-has-rules' ),
  4152. template = wp.template( 'fl-module-overlay' ),
  4153. overlay = null;
  4154. // Remove existing overlays.
  4155. FLBuilder._removeColOverlays();
  4156. FLBuilder._removeModuleOverlays();
  4157. if ( global && parentGlobal && 'row' != FLBuilderConfig.userTemplateType && isGlobalRow ) {
  4158. return;
  4159. }
  4160. else if ( global && parentGlobal && 'column' != FLBuilderConfig.userTemplateType && ! isGlobalRow ) {
  4161. return;
  4162. }
  4163. else if ( module.closest( '.fl-builder-node-loading' ).length ) {
  4164. return;
  4165. }
  4166. else if ( module.find( '.fl-inline-editor:visible' ).length ) {
  4167. return;
  4168. }
  4169. else if ( ! module.hasClass( 'fl-block-overlay-active' ) ) {
  4170. // Append the template.
  4171. overlay = FLBuilder._appendOverlay( module, template( {
  4172. global : global,
  4173. moduleName : moduleName,
  4174. groupLoading : groupLoading,
  4175. numCols : numCols,
  4176. colFirst : colFirst,
  4177. colLast : colLast,
  4178. isRootCol : isRootCol,
  4179. hasParentCol : hasParentCol,
  4180. numParentCols : numParentCols,
  4181. parentFirst : parentFirst,
  4182. parentLast : parentLast,
  4183. contentWidth : contentWidth,
  4184. rowIsFixedWidth : rowIsFixedWidth,
  4185. userCanResizeRows : userCanResizeRows,
  4186. hasRules : hasRules,
  4187. colHasRules : colHasRules,
  4188. } ) );
  4189. // Build the overlay overflow menu if necessary.
  4190. FLBuilder._buildOverlayOverflowMenu( overlay );
  4191. // Init column resizing.
  4192. FLBuilder._initColDragResizing();
  4193. }
  4194. $( 'body' ).addClass( 'fl-block-overlay-muted' );
  4195. },
  4196. /**
  4197. * Removes overlays when the mouse leaves a module.
  4198. *
  4199. * @since 1.0
  4200. * @access private
  4201. * @method _moduleMouseleave
  4202. * @param {Object} e The event object.
  4203. */
  4204. _moduleMouseleave: function(e)
  4205. {
  4206. var module = $(this),
  4207. toElement = $(e.toElement) || $(e.relatedTarget),
  4208. isTipTip = toElement.is('#tiptip_holder'),
  4209. isTipTipChild = toElement.closest('#tiptip_holder').length > 0;
  4210. if(isTipTip || isTipTipChild) {
  4211. return;
  4212. }
  4213. FLBuilder._removeModuleOverlays();
  4214. FLBuilder._removeColHighlightGuides();
  4215. },
  4216. /**
  4217. * Removes all module overlays from the page.
  4218. *
  4219. * @since 1.6.4
  4220. * @access private
  4221. * @method _removeModuleOverlays
  4222. */
  4223. _removeModuleOverlays: function()
  4224. {
  4225. var modules = $('.fl-module');
  4226. modules.removeClass('fl-block-overlay-active');
  4227. modules.find('.fl-module-overlay').remove();
  4228. $('body').removeClass('fl-block-overlay-muted');
  4229. FLBuilder._closeAllSubmenus();
  4230. },
  4231. /**
  4232. * Returns a helper element for module drag operations.
  4233. *
  4234. * @since 1.0
  4235. * @access private
  4236. * @method _moduleDragHelper
  4237. * @param {Object} e The event object.
  4238. * @param {Object} item The element being dragged.
  4239. * @return {Object} The helper element.
  4240. */
  4241. _moduleDragHelper: function(e, item)
  4242. {
  4243. return $('<div class="fl-builder-block-drag-helper">' + item.attr('data-name') + '</div>');
  4244. },
  4245. /**
  4246. * Callback that fires when dragging starts for a module.
  4247. *
  4248. * @since 1.9
  4249. * @access private
  4250. * @method _moduleDragStart
  4251. * @param {Object} e The event object.
  4252. * @param {Object} ui An object with additional info for the drag.
  4253. */
  4254. _moduleDragStart: function( e, ui )
  4255. {
  4256. $( ui.item ).data( 'original-position', ui.item.index() );
  4257. FLBuilder._blockDragStart( e, ui );
  4258. },
  4259. /**
  4260. * Callback for when a module drag operation completes.
  4261. *
  4262. * @since 1.0
  4263. * @access private
  4264. * @method _moduleDragStop
  4265. * @param {Object} e The event object.
  4266. * @param {Object} ui An object with additional info for the drag.
  4267. */
  4268. _moduleDragStop: function(e, ui)
  4269. {
  4270. FLBuilder._blockDragStop( e, ui );
  4271. var item = ui.item,
  4272. parent = item.parent(),
  4273. node = null,
  4274. position = 0,
  4275. parentId = 0;
  4276. // A module was dropped back into the module list.
  4277. if ( parent.hasClass( 'fl-builder-modules' ) || parent.hasClass( 'fl-builder-widgets' ) ) {
  4278. item.remove();
  4279. return;
  4280. }
  4281. // A new module was dropped.
  4282. else if ( item.hasClass( 'fl-builder-block' ) ) {
  4283. // Cancel the drop if the sortable is disabled?
  4284. if ( parent.hasClass( 'fl-sortable-disabled' ) ) {
  4285. item.remove();
  4286. FLBuilder._showPanel();
  4287. return;
  4288. }
  4289. // A new module was dropped into a row position.
  4290. else if ( parent.hasClass( 'fl-row-drop-target' ) ) {
  4291. parent = item.closest('.fl-builder-content');
  4292. parentId = 0;
  4293. node = item.closest('.fl-row');
  4294. position = parent.find( '.fl-row' ).index( node );
  4295. }
  4296. // A new module was dropped into a column group position.
  4297. else if ( parent.hasClass( 'fl-col-group-drop-target' ) ) {
  4298. parent = item.closest( '.fl-row-content' );
  4299. parentId = parent.closest( '.fl-row' ).attr( 'data-node' );
  4300. node = item.closest( '.fl-col-group' );
  4301. position = parent.find( ' > .fl-col-group' ).index( node );
  4302. }
  4303. // A new module was dropped into a column position.
  4304. else if ( parent.hasClass( 'fl-col-drop-target' ) ) {
  4305. parent = item.closest( '.fl-col-group' );
  4306. parentId = parent.attr( 'data-node' );
  4307. node = item.closest( '.fl-col' );
  4308. position = parent.find( ' > .fl-col' ).index( node );
  4309. }
  4310. // A new module was dropped into a column.
  4311. else {
  4312. position = parent.find( '> .fl-module, .fl-col-group, .fl-builder-block' ).index( item );
  4313. parentId = item.closest( '.fl-col' ).attr( 'data-node' );
  4314. }
  4315. // Increment the position?
  4316. if ( item.closest( '.fl-drop-target-last' ).length ) {
  4317. position += 1;
  4318. }
  4319. // Add the new module.
  4320. FLBuilder._addModule( parent, parentId, item.attr( 'data-type' ), position, item.attr( 'data-widget' ), item.attr( 'data-alias' ) );
  4321. // Remove the drag helper.
  4322. item.remove();
  4323. }
  4324. // Cancel the drop if the sortable is disabled?
  4325. else if ( parent.hasClass( 'fl-sortable-disabled' ) ) {
  4326. $( e.target ).append( ui.item );
  4327. $( e.target ).children().eq( ui.item.data( 'original-position' ) ).before( ui.item );
  4328. FLBuilder._highlightEmptyCols();
  4329. return;
  4330. }
  4331. // A module was dropped into a row position.
  4332. else if ( parent.hasClass( 'fl-row-drop-target' ) ) {
  4333. node = item.closest( '.fl-row' );
  4334. position = item.closest( '.fl-builder-content' ).children( '.fl-row' ).index( node );
  4335. position = item.closest( '.fl-drop-target-last' ).length ? position + 1 : position;
  4336. FLBuilder._addModuleAfterNodeRender = item;
  4337. FLBuilder._addRow( '1-col', position );
  4338. item.remove();
  4339. }
  4340. // A module was dropped into a column group position.
  4341. else if ( parent.hasClass( 'fl-col-group-drop-target' ) ) {
  4342. node = item.closest( '.fl-col-group' );
  4343. position = item.closest( '.fl-row-content ').find( ' > .fl-col-group' ).index( node );
  4344. position = item.closest( '.fl-drop-target-last' ).length ? position + 1 : position;
  4345. FLBuilder._addModuleAfterNodeRender = item;
  4346. FLBuilder._addColGroup( item.closest( '.fl-row' ).attr( 'data-node' ), '1-col', position );
  4347. item.remove();
  4348. }
  4349. // A module was dropped into a column position.
  4350. else if ( parent.hasClass( 'fl-col-drop-target' ) ) {
  4351. node = item.closest( '.fl-col' );
  4352. position = item.closest( '.fl-col-drop-target-last' ).length ? 'after' : 'before';
  4353. FLBuilder._addModuleAfterNodeRender = { module: item, colId: node.data( 'node' ), position: position };
  4354. FLBuilder._addCols( node, position, '1-col', item.closest( '.fl-col-group-nested' ).length > 0 );
  4355. item.remove();
  4356. }
  4357. // A module was dropped into another column.
  4358. else {
  4359. FLBuilder._reorderModule( item );
  4360. }
  4361. FLBuilder._resizeLayout();
  4362. },
  4363. /**
  4364. * Reorders a module within a column.
  4365. *
  4366. * @since 1.0
  4367. * @access private
  4368. * @method _reorderModule
  4369. * @param {Object} module The module element being dragged.
  4370. */
  4371. _reorderModule: function(module)
  4372. {
  4373. var newParent = module.closest('.fl-col').attr('data-node'),
  4374. oldParent = module.attr('data-parent'),
  4375. nodeId = module.attr('data-node'),
  4376. position = module.index();
  4377. if(newParent == oldParent) {
  4378. FLBuilder._reorderNode( nodeId, position );
  4379. }
  4380. else {
  4381. module.attr('data-parent', newParent);
  4382. FLBuilder._moveNode( newParent, nodeId, position );
  4383. }
  4384. },
  4385. /**
  4386. * Callback for when the delete module button is clicked.
  4387. *
  4388. * @since 1.0
  4389. * @access private
  4390. * @method _deleteModuleClicked
  4391. * @param {Object} e The event object.
  4392. */
  4393. _deleteModuleClicked: function(e)
  4394. {
  4395. var module = $(this).closest('.fl-module'),
  4396. result = confirm(FLBuilderStrings.deleteModuleMessage);
  4397. if(result) {
  4398. FLBuilder._deleteModule(module);
  4399. FLBuilder._removeAllOverlays();
  4400. }
  4401. e.stopPropagation();
  4402. },
  4403. /**
  4404. * Deletes a module.
  4405. *
  4406. * @since 1.0
  4407. * @access private
  4408. * @method _deleteModule
  4409. * @param {Object} module A jQuery reference of the module to delete.
  4410. */
  4411. _deleteModule: function(module)
  4412. {
  4413. var row = module.closest('.fl-row'),
  4414. nodeId = module.attr('data-node');
  4415. FLBuilder.ajax({
  4416. action: 'delete_node',
  4417. node_id: nodeId
  4418. });
  4419. module.empty();
  4420. module.remove();
  4421. row.removeClass('fl-block-overlay-muted');
  4422. FLBuilder._highlightEmptyCols();
  4423. FLBuilder._removeAllOverlays();
  4424. FLBuilder.triggerHook( 'didDeleteModule', nodeId );
  4425. },
  4426. /**
  4427. * Duplicates a module.
  4428. *
  4429. * @since 1.0
  4430. * @access private
  4431. * @method _moduleCopyClicked
  4432. * @param {Object} e The event object.
  4433. */
  4434. _moduleCopyClicked: function(e)
  4435. {
  4436. var module = $( this ).closest( '.fl-module' ),
  4437. nodeId = module.attr( 'data-node' ),
  4438. parent = module.parent(),
  4439. position = parent.find( ' > .fl-col-group, > .fl-module' ).index( module ) + 1,
  4440. clone = module.clone(),
  4441. form = $( '.fl-builder-module-settings[data-node=' + nodeId + ']' ),
  4442. settings = null;
  4443. if ( form.length ) {
  4444. settings = FLBuilder._getSettings( form );
  4445. FLBuilderSettingsConfig.nodes[ nodeId ] = settings;
  4446. }
  4447. clone.addClass( 'fl-node-' + nodeId + '-clone fl-builder-node-clone' );
  4448. clone.find( '.fl-block-overlay' ).remove();
  4449. module.after( clone );
  4450. $( 'html, body' ).animate( {
  4451. scrollTop: clone.offset().top - 75
  4452. }, 500 );
  4453. FLBuilder._showNodeLoading( nodeId + '-clone' );
  4454. FLBuilder._newModuleParent = parent;
  4455. FLBuilder._newModulePosition = position;
  4456. FLBuilder.ajax({
  4457. action: 'copy_module',
  4458. node_id: nodeId,
  4459. settings: settings
  4460. }, function( response ) {
  4461. var data = JSON.parse( response );
  4462. data.duplicatedModule = nodeId;
  4463. FLBuilder._moduleCopyComplete( data );
  4464. } );
  4465. e.stopPropagation();
  4466. },
  4467. /**
  4468. * Callback for when a module has been duplicated.
  4469. *
  4470. * @since 1.7
  4471. * @access private
  4472. * @method _moduleCopyComplete
  4473. * @param {Object}
  4474. */
  4475. _moduleCopyComplete: function( data )
  4476. {
  4477. data.nodeParent = FLBuilder._newModuleParent;
  4478. data.nodePosition = FLBuilder._newModulePosition;
  4479. FLBuilder._renderLayout( data, function(){
  4480. FLBuilder.triggerHook( 'didDuplicateModule', {
  4481. newNodeId : data.nodeId,
  4482. oldNodeId : data.duplicatedModule
  4483. } );
  4484. data.nodeParent.find( '.fl-builder-node-loading' ).eq( 0 ).remove();
  4485. } );
  4486. },
  4487. /**
  4488. * Shows the settings lightbox and loads the module settings
  4489. * when the module settings button is clicked.
  4490. *
  4491. * @since 1.0
  4492. * @access private
  4493. * @method _moduleSettingsClicked
  4494. * @param {Object} e The event object.
  4495. */
  4496. _moduleSettingsClicked: function(e)
  4497. {
  4498. var button = $( this ),
  4499. type = button.closest( '.fl-module' ).attr( 'data-type' ),
  4500. nodeId = button.closest( '.fl-module' ).attr( 'data-node' ),
  4501. parentId = button.closest( '.fl-col' ).attr( 'data-node' ),
  4502. global = button.closest( '.fl-block-overlay-global' ).length > 0;
  4503. e.stopPropagation();
  4504. if ( FLBuilder._colResizing ) {
  4505. return;
  4506. }
  4507. if ( global && ! FLBuilderConfig.userCanEditGlobalTemplates ) {
  4508. return;
  4509. }
  4510. FLBuilder._showModuleSettings( {
  4511. type : type,
  4512. nodeId : nodeId,
  4513. parentId : parentId,
  4514. global : global
  4515. } );
  4516. },
  4517. /**
  4518. * Shows the lightbox and loads the settings for a module.
  4519. *
  4520. * @since 1.0
  4521. * @access private
  4522. * @method _showModuleSettings
  4523. * @param {Object} data
  4524. * @param {Function} callback
  4525. */
  4526. _showModuleSettings: function( data, callback )
  4527. {
  4528. if ( ! FLBuilderSettingsConfig.modules ) {
  4529. return;
  4530. }
  4531. var config = FLBuilderSettingsConfig.modules[ data.type ],
  4532. settings = data.settings ? data.settings : FLBuilderSettingsConfig.nodes[ data.nodeId ],
  4533. head = $( 'head' ),
  4534. layout = null;
  4535. // Add settings CSS and JS.
  4536. if ( -1 === $.inArray( data.type, FLBuilder._loadedModuleAssets ) ) {
  4537. if ( '' !== config.assets.css ) {
  4538. head.append( config.assets.css );
  4539. }
  4540. if ( '' !== config.assets.js ) {
  4541. head.append( config.assets.js );
  4542. }
  4543. FLBuilder._loadedModuleAssets.push( data.type );
  4544. }
  4545. // Render the form.
  4546. FLBuilderSettingsForms.render( {
  4547. type : 'module',
  4548. id : data.type,
  4549. nodeId : data.nodeId,
  4550. className : 'fl-builder-module-settings fl-builder-' + data.type + '-settings',
  4551. attrs : 'data-node="' + data.nodeId + '" data-parent="' + data.parentId + '" data-type="' + data.type + '"',
  4552. buttons : ! data.global && ! FLBuilderConfig.lite && ! FLBuilderConfig.simpleUi ? ['save-as'] : [],
  4553. badges : data.global ? [ FLBuilderStrings.global ] : [],
  4554. settings : settings,
  4555. legacy : data.legacy,
  4556. helper : FLBuilder._moduleHelpers[ data.type ],
  4557. rules : FLBuilder._moduleHelpers[ data.type ] ? FLBuilder._moduleHelpers[ data.type ].rules : null,
  4558. preview : {
  4559. type : 'module',
  4560. layout : data.layout,
  4561. callback : function() {
  4562. FLBuilder.triggerHook( 'didAddModule', data.nodeId );
  4563. }
  4564. }
  4565. }, callback );
  4566. },
  4567. /**
  4568. * Validates the module settings and saves them if
  4569. * the form is valid.
  4570. *
  4571. * @since 1.0
  4572. * @access private
  4573. * @method _saveModuleClicked
  4574. */
  4575. _saveModuleClicked: function()
  4576. {
  4577. var form = $(this).closest('.fl-builder-settings'),
  4578. type = form.attr('data-type'),
  4579. id = form.attr('data-node'),
  4580. helper = FLBuilder._moduleHelpers[type],
  4581. valid = true;
  4582. if(typeof helper !== 'undefined') {
  4583. form.find('label.error').remove();
  4584. form.validate().hideErrors();
  4585. valid = form.validate().form();
  4586. if(valid) {
  4587. valid = helper.submit();
  4588. }
  4589. }
  4590. if(valid) {
  4591. FLBuilder._saveSettings();
  4592. }
  4593. else {
  4594. FLBuilder._toggleSettingsTabErrors();
  4595. }
  4596. },
  4597. /**
  4598. * Adds a new module to a column and loads the settings.
  4599. *
  4600. * @since 1.0
  4601. * @access private
  4602. * @method _addModule
  4603. * @param {Object} parent A jQuery reference to the new module's parent.
  4604. * @param {String} parentId The node id of the new module's parent.
  4605. * @param {String} type The type of module to add.
  4606. * @param {Number} position The position of the new module within its parent.
  4607. * @param {String} widget The type of widget if this module is a widget.
  4608. * @param {String} alias A module alias key if this module is an alias to another module.
  4609. */
  4610. _addModule: function( parent, parentId, type, position, widget, alias )
  4611. {
  4612. // Show the loader.
  4613. FLBuilder._showNodeLoadingPlaceholder( parent, position );
  4614. // Save the new module data.
  4615. if ( parent.hasClass( 'fl-col-group' ) ) {
  4616. FLBuilder._newModuleParent = null;
  4617. FLBuilder._newModulePosition = 0;
  4618. }
  4619. else {
  4620. FLBuilder._newModuleParent = parent;
  4621. FLBuilder._newModulePosition = position;
  4622. }
  4623. // Send the request.
  4624. FLBuilder.ajax( {
  4625. action : 'render_new_module',
  4626. parent_id : parentId,
  4627. type : type,
  4628. position : position,
  4629. node_preview : 1,
  4630. widget : typeof widget === 'undefined' ? '' : widget,
  4631. alias : typeof alias === 'undefined' ? '' : alias
  4632. }, FLBuilder._addModuleComplete );
  4633. },
  4634. /**
  4635. * Shows the settings lightbox and sets the content when
  4636. * the module settings have finished loading.
  4637. *
  4638. * @since 1.0
  4639. * @access private
  4640. * @method _addModuleComplete
  4641. * @param {String} response The JSON encoded response.
  4642. */
  4643. _addModuleComplete: function( response )
  4644. {
  4645. var data = JSON.parse( response );
  4646. // Setup a preview layout if we have one.
  4647. if ( data.layout ) {
  4648. data.layout.nodeParent = FLBuilder._newModuleParent;
  4649. data.layout.nodePosition = FLBuilder._newModulePosition;
  4650. }
  4651. // Make sure we have settings before rendering the form.
  4652. if ( ! data.settings ) {
  4653. data.settings = FLBuilderSettingsConfig.defaults.modules[ data.type ];
  4654. }
  4655. // Render the module if a settings form is already open.
  4656. if ( $( 'form.fl-builder-settings' ).length ) {
  4657. if ( data.layout ) {
  4658. FLBuilder._renderLayout( data.layout );
  4659. }
  4660. } else {
  4661. FLBuilder._showModuleSettings( data, function() {
  4662. $( '.fl-builder-module-settings' ).data( 'new-module', '1' );
  4663. } );
  4664. }
  4665. },
  4666. /**
  4667. * Registers a helper class for a module's settings.
  4668. *
  4669. * @since 1.0
  4670. * @method registerModuleHelper
  4671. * @param {String} type The type of module.
  4672. * @param {Object} obj The module helper.
  4673. */
  4674. registerModuleHelper: function(type, obj)
  4675. {
  4676. var defaults = {
  4677. rules: {},
  4678. init: function(){},
  4679. submit: function(){ return true; },
  4680. preview: function(){}
  4681. };
  4682. FLBuilder._moduleHelpers[type] = $.extend({}, defaults, obj);
  4683. },
  4684. /**
  4685. * Deprecated. Use the public method registerModuleHelper instead.
  4686. *
  4687. * @since 1.0
  4688. * @access private
  4689. * @method _registerModuleHelper
  4690. * @param {String} type The type of module.
  4691. * @param {Object} obj The module helper.
  4692. */
  4693. _registerModuleHelper: function(type, obj)
  4694. {
  4695. FLBuilder.registerModuleHelper(type, obj);
  4696. },
  4697. /* Node Templates
  4698. ----------------------------------------------------------*/
  4699. /**
  4700. * Saves a node's settings and shows the node template settings
  4701. * when the Save As button is clicked.
  4702. *
  4703. * @since 1.6.3
  4704. * @access private
  4705. * @method _showNodeTemplateSettings
  4706. * @param {Object} e An event object.
  4707. */
  4708. _showNodeTemplateSettings: function( e )
  4709. {
  4710. var form = $( '.fl-builder-settings-lightbox .fl-builder-settings' ),
  4711. nodeId = form.attr( 'data-node' ),
  4712. title = FLBuilderStrings.saveModule;
  4713. if ( form.hasClass( 'fl-builder-row-settings' ) ) {
  4714. title = FLBuilderStrings.saveRow;
  4715. }
  4716. else if ( form.hasClass( 'fl-builder-col-settings' ) ) {
  4717. title = FLBuilderStrings.saveColumn;
  4718. }
  4719. if ( ! FLBuilder._triggerSettingsSave( false, false, false ) ) {
  4720. return false;
  4721. }
  4722. FLBuilderSettingsForms.render( {
  4723. id : 'node_template',
  4724. nodeId : nodeId,
  4725. title : title,
  4726. attrs : 'data-node="' + nodeId + '"',
  4727. className : 'fl-builder-node-template-settings',
  4728. rules : {
  4729. name: {
  4730. required: true
  4731. }
  4732. }
  4733. }, function() {
  4734. if ( ! FLBuilderConfig.userCanEditGlobalTemplates ) {
  4735. $( '#fl-field-global' ).hide();
  4736. }
  4737. } );
  4738. },
  4739. /**
  4740. * Saves a node as a template when the save button is clicked.
  4741. *
  4742. * @since 1.6.3
  4743. * @access private
  4744. * @method _saveNodeTemplate
  4745. */
  4746. _saveNodeTemplate: function()
  4747. {
  4748. var form = $( '.fl-builder-node-template-settings' ),
  4749. nodeId = form.attr( 'data-node' ),
  4750. valid = form.validate().form();
  4751. if ( valid ) {
  4752. FLBuilder._showNodeLoading( nodeId );
  4753. FLBuilder.ajax({
  4754. action : 'save_node_template',
  4755. node_id : nodeId,
  4756. settings : FLBuilder._getSettings( form )
  4757. }, function( response ) {
  4758. FLBuilder._saveNodeTemplateComplete( response );
  4759. FLBuilder._hideNodeLoading( nodeId );
  4760. } );
  4761. FLBuilder._lightbox.close();
  4762. }
  4763. },
  4764. /**
  4765. * Callback for when a node template has been saved.
  4766. *
  4767. * @since 1.6.3
  4768. * @access private
  4769. * @method _saveNodeTemplateComplete
  4770. */
  4771. _saveNodeTemplateComplete: function( response )
  4772. {
  4773. var data = JSON.parse( response ),
  4774. panel = $( '.fl-builder-saved-' + data.type + 's' ),
  4775. blocks = panel.find( '.fl-builder-block' ),
  4776. block = null,
  4777. text = '',
  4778. name = data.name.toLowerCase(),
  4779. i = 0,
  4780. template = wp.template( 'fl-node-template-block' ),
  4781. newLibraryItem = {
  4782. name: data.name,
  4783. isGlobal: data.global,
  4784. content: data.type,
  4785. id: data.id,
  4786. postID: data.postID,
  4787. kind: "template",
  4788. type: "user",
  4789. link: data.link,
  4790. category: {
  4791. uncategorized: FLBuilderStrings.uncategorized
  4792. }
  4793. };
  4794. FLBuilderConfig.contentItems.template.push(newLibraryItem);
  4795. FLBuilder.triggerHook('contentItemsChanged');
  4796. // Update the layout for global templates.
  4797. if ( data.layout ) {
  4798. FLBuilder._renderLayout( data.layout );
  4799. FLBuilder.triggerHook( 'didSaveGlobalNodeTemplate', data.config );
  4800. }
  4801. // Add the new template to the builder panel.
  4802. if ( 0 === blocks.length ) {
  4803. panel.append( template( data ) );
  4804. }
  4805. else {
  4806. for ( ; i < blocks.length; i++ ) {
  4807. block = blocks.eq( i );
  4808. text = block.text().toLowerCase().trim();
  4809. if ( 0 === i && name < text ) {
  4810. panel.prepend( template( data ) );
  4811. break;
  4812. }
  4813. else if ( name < text ) {
  4814. block.before( template( data ) );
  4815. break;
  4816. }
  4817. else if ( blocks.length - 1 === i ) {
  4818. panel.append( template( data ) );
  4819. break;
  4820. }
  4821. }
  4822. }
  4823. // Remove the no templates placeholder.
  4824. panel.find( '.fl-builder-block-no-node-templates' ).remove();
  4825. },
  4826. /**
  4827. * Callback for when a node template drag from the
  4828. * builder panel has stopped.
  4829. *
  4830. * @since 1.6.3
  4831. * @access private
  4832. * @method _nodeTemplateDragStop
  4833. * @param {Object} e The event object.
  4834. * @param {Object} ui An object with additional info for the drag.
  4835. */
  4836. _nodeTemplateDragStop: function( e, ui )
  4837. {
  4838. FLBuilder._blockDragStop( e, ui );
  4839. var item = ui.item,
  4840. parent = item.parent(),
  4841. parentId = null,
  4842. position = 0,
  4843. node = null,
  4844. action = '',
  4845. callback = null;
  4846. // A node template was dropped back into the templates list.
  4847. if ( parent.hasClass( 'fl-builder-blocks-section-content' ) ) {
  4848. item.remove();
  4849. return;
  4850. }
  4851. // A saved row was dropped.
  4852. else if ( item.hasClass( 'fl-builder-block-saved-row' ) || item.hasClass( 'fl-builder-block-row-template' ) ) {
  4853. node = item.closest( '.fl-row' );
  4854. position = ! node.length ? 0 : $( FLBuilder._contentClass + ' .fl-row' ).index( node );
  4855. position = parent.hasClass( 'fl-drop-target-last' ) ? position + 1 : position;
  4856. parentId = null;
  4857. action = 'render_new_row';
  4858. callback = FLBuilder._addRowComplete;
  4859. FLBuilder._newRowPosition = position;
  4860. FLBuilder._showNodeLoadingPlaceholder( $( FLBuilder._contentClass ), position );
  4861. }
  4862. // A saved column was dropped.
  4863. else if ( item.hasClass( 'fl-builder-block-saved-column' ) ) {
  4864. node = item.closest( '.fl-col' ),
  4865. colGroup = parent.closest( '.fl-col-group' ),
  4866. colGroupId = colGroup.attr( 'data-node' );
  4867. action = 'render_new_col_template';
  4868. callback = FLBuilder._addColsComplete;
  4869. // Cancel the drop if the sortable is disabled?
  4870. if ( parent.hasClass( 'fl-sortable-disabled' ) ) {
  4871. item.remove();
  4872. FLBuilder._showPanel();
  4873. return;
  4874. }
  4875. // A column was dropped into a row position.
  4876. else if ( parent.hasClass( 'fl-row-drop-target' ) ) {
  4877. node = item.closest( '.fl-row' ),
  4878. parentId = 0;
  4879. parent = $( FLBuilder._contentClass );
  4880. position = ! node.length ? 0 : parent.find( '.fl-row' ).index( node );
  4881. }
  4882. // A column was dropped into a column group position.
  4883. else if ( parent.hasClass( 'fl-col-group-drop-target' ) ) {
  4884. parent = item.closest( '.fl-row-content' );
  4885. parentId = item.closest( '.fl-row' ).attr( 'data-node' );
  4886. position = item.closest( '.fl-row' ).find( '.fl-row-content > .fl-col-group' ).index( item.closest( '.fl-col-group' ) );
  4887. }
  4888. // A column was dropped into a column position.
  4889. else if ( parent.hasClass( 'fl-col-drop-target' ) ) {
  4890. parent = item.closest('.fl-col-group');
  4891. position = parent.children('.fl-col').index( item.closest('.fl-col') );
  4892. parentId = parent.attr('data-node');
  4893. }
  4894. // Increment the position?
  4895. if ( item.closest( '.fl-drop-target-last' ).length ) {
  4896. position += 1;
  4897. }
  4898. if ( parent.hasClass( 'fl-col-group' ) ) {
  4899. FLBuilder._newColParent = null;
  4900. }
  4901. else {
  4902. FLBuilder._newColParent = parent;
  4903. }
  4904. FLBuilder._newColPosition = position;
  4905. // Show the loader.
  4906. FLBuilder._showNodeLoadingPlaceholder( parent, position );
  4907. }
  4908. // A saved module was dropped.
  4909. else if ( item.hasClass( 'fl-builder-block-saved-module' ) || item.hasClass( 'fl-builder-block-module-template' ) ) {
  4910. action = 'render_new_module';
  4911. callback = FLBuilder._addModuleComplete;
  4912. // Cancel the drop if the sortable is disabled?
  4913. if ( parent.hasClass( 'fl-sortable-disabled' ) ) {
  4914. item.remove();
  4915. FLBuilder._showPanel();
  4916. return;
  4917. }
  4918. // Dropped into a row position.
  4919. else if ( parent.hasClass( 'fl-row-drop-target' ) ) {
  4920. parent = item.closest('.fl-builder-content');
  4921. parentId = 0;
  4922. position = parent.find( '.fl-row' ).index( item.closest('.fl-row') );
  4923. }
  4924. // Dropped into a column group position.
  4925. else if ( parent.hasClass( 'fl-col-group-drop-target' ) ) {
  4926. parent = item.closest( '.fl-row-content' );
  4927. parentId = parent.closest( '.fl-row' ).attr( 'data-node' );
  4928. position = parent.find( ' > .fl-col-group' ).index( item.closest( '.fl-col-group' ) );
  4929. }
  4930. // Dropped into a column position.
  4931. else if ( parent.hasClass( 'fl-col-drop-target' ) ) {
  4932. parent = item.closest('.fl-col-group');
  4933. position = parent.children('.fl-col').index( item.closest('.fl-col') );
  4934. parentId = parent.attr('data-node');
  4935. }
  4936. // Dropped into a column.
  4937. else {
  4938. position = parent.children( '.fl-module, .fl-builder-block' ).index( item );
  4939. parentId = item.closest( '.fl-col' ).attr( 'data-node' );
  4940. }
  4941. // Increment the position?
  4942. if ( item.closest( '.fl-drop-target-last' ).length ) {
  4943. position += 1;
  4944. }
  4945. // Save the new module data.
  4946. if ( parent.hasClass( 'fl-col-group' ) ) {
  4947. FLBuilder._newModuleParent = null;
  4948. FLBuilder._newModulePosition = 0;
  4949. }
  4950. else {
  4951. FLBuilder._newModuleParent = parent;
  4952. FLBuilder._newModulePosition = position;
  4953. }
  4954. // Show the loader.
  4955. FLBuilder._showNodeLoadingPlaceholder( parent, position );
  4956. }
  4957. // Apply and render the node template.
  4958. FLBuilder.ajax({
  4959. action : action,
  4960. template_id : item.attr( 'data-id' ),
  4961. template_type : item.attr( 'data-type' ),
  4962. parent_id : parentId,
  4963. position : position
  4964. }, function( response ) {
  4965. if ( action.indexOf( 'row' ) > -1 ) {
  4966. var data = JSON.parse( response );
  4967. FLBuilder.triggerHook( 'didApplyRowTemplateComplete', data.config );
  4968. callback( data.layout );
  4969. } else if ( action.indexOf( 'col' ) > -1 ) {
  4970. var data = JSON.parse( response );
  4971. FLBuilder.triggerHook( 'didApplyColTemplateComplete', data.config );
  4972. callback( data.layout );
  4973. } else {
  4974. callback( response );
  4975. }
  4976. } );
  4977. // Remove the helper.
  4978. item.remove();
  4979. },
  4980. /**
  4981. * Launches the builder in a new tab to edit a user
  4982. * defined node template when the edit link is clicked.
  4983. *
  4984. * @since 1.6.3
  4985. * @access private
  4986. * @method _editUserTemplateClicked
  4987. * @param {Object} e The event object.
  4988. */
  4989. _editNodeTemplateClicked: function( e )
  4990. {
  4991. e.preventDefault();
  4992. e.stopPropagation();
  4993. window.open( $( this ).attr( 'href' ) );
  4994. },
  4995. /**
  4996. * Fires when the delete node template icon is clicked in the builder's panel.
  4997. *
  4998. * @since 1.6.3
  4999. * @access private
  5000. * @method _deleteNodeTemplateClicked
  5001. * @param {Object} e The event object.
  5002. */
  5003. _deleteNodeTemplateClicked: function( e )
  5004. {
  5005. var button = $( e.target ),
  5006. section = button.closest( '.fl-builder-blocks-section' ),
  5007. panel = section.find( '.fl-builder-blocks-section-content' ),
  5008. blocks = panel.find( '.fl-builder-block' ),
  5009. block = button.closest( '.fl-builder-block' ),
  5010. global = block.hasClass( 'fl-builder-block-global' ),
  5011. callback = global ? FLBuilder._updateLayout : undefined,
  5012. message = global ? FLBuilderStrings.deleteGlobalTemplate : FLBuilderStrings.deleteTemplate,
  5013. index = null;
  5014. if ( confirm( message ) ) {
  5015. // Delete the UI block.
  5016. block.remove();
  5017. // Add the no templates placeholder?
  5018. if ( 1 === blocks.length ) {
  5019. if ( block.hasClass( 'fl-builder-block-saved-row' ) ) {
  5020. panel.append( '<span class="fl-builder-block-no-node-templates">' + FLBuilderStrings.noSavedRows + '</span>' );
  5021. }
  5022. else {
  5023. panel.append( '<span class="fl-builder-block-no-node-templates">' + FLBuilderStrings.noSavedModules + '</span>' );
  5024. }
  5025. }
  5026. // Show the loader?
  5027. if ( block.hasClass( 'fl-builder-block-global' ) ) {
  5028. FLBuilder.showAjaxLoader();
  5029. }
  5030. // Delete the template.
  5031. FLBuilder.ajax({
  5032. action : 'delete_node_template',
  5033. template_id : block.attr( 'data-id' )
  5034. }, callback);
  5035. // Remove the item from library
  5036. index = _.findIndex(FLBuilderConfig.contentItems.template, {
  5037. id: block.attr('data-id'),
  5038. type: 'user'
  5039. });
  5040. FLBuilderConfig.contentItems.template.splice(index, 1);
  5041. FLBuilder.triggerHook('contentItemsChanged');
  5042. }
  5043. },
  5044. /* Settings
  5045. ----------------------------------------------------------*/
  5046. /**
  5047. * Initializes logic for settings forms.
  5048. *
  5049. * @since 1.0
  5050. * @access private
  5051. * @method _initSettingsForms
  5052. */
  5053. _initSettingsForms: function()
  5054. {
  5055. FLBuilder._initCodeFields();
  5056. FLBuilder._initColorPickers();
  5057. FLBuilder._initSelectFields();
  5058. FLBuilder._initEditorFields();
  5059. FLBuilder._initMultipleFields();
  5060. FLBuilder._initAutoSuggestFields();
  5061. FLBuilder._initLinkFields();
  5062. FLBuilder._initFontFields();
  5063. FLBuilder._initOrderingFields();
  5064. FLBuilder._initTimezoneFields();
  5065. FLBuilder._focusFirstSettingsControl();
  5066. FLBuilder._lightbox._resizeEditors();
  5067. $( '.fl-builder-settings-fields' ).css( 'visibility', 'visible' );
  5068. /**
  5069. * Hook for settings form init.
  5070. */
  5071. FLBuilder.triggerHook('settings-form-init');
  5072. },
  5073. /**
  5074. * Destroys all active settings forms.
  5075. *
  5076. * @since 2.0
  5077. * @access private
  5078. * @method _destroySettingsForms
  5079. */
  5080. _destroySettingsForms: function()
  5081. {
  5082. FLBuilder._destroyEditorFields();
  5083. },
  5084. /**
  5085. * Inserts settings forms rendered with PHP. This method is only around for
  5086. * backwards compatibility with third party settings forms that are
  5087. * still being rendered via AJAX. Going forward, all settings forms
  5088. * should be rendered on the frontend using FLBuilderSettingsForms.render.
  5089. *
  5090. * @since 1.0
  5091. * @access private
  5092. * @method _setSettingsFormContent
  5093. * @param {String} html
  5094. */
  5095. _setSettingsFormContent: function( html )
  5096. {
  5097. $( '.fl-legacy-settings' ).remove();
  5098. $( 'body' ).append( html );
  5099. },
  5100. /**
  5101. * Shows the content for a settings form tab when it is clicked.
  5102. *
  5103. * @since 1.0
  5104. * @access private
  5105. * @method _settingsTabClicked
  5106. * @param {Object} e The event object.
  5107. */
  5108. _settingsTabClicked: function(e)
  5109. {
  5110. var tab = $( this ),
  5111. form = tab.closest( '.fl-builder-settings' ),
  5112. id = tab.attr( 'href' ).split( '#' ).pop();
  5113. FLBuilder._resetSettingsTabsState();
  5114. form.find( '.fl-builder-settings-tab' ).removeClass( 'fl-active' );
  5115. form.find( '#' + id ).addClass( 'fl-active' );
  5116. form.find( '.fl-builder-settings-tabs .fl-active' ).removeClass( 'fl-active' );
  5117. form.find( 'a[href*=' + id + ']' ).addClass( 'fl-active' );
  5118. FLBuilder._focusFirstSettingsControl();
  5119. e.preventDefault();
  5120. },
  5121. _resetSettingsTabsState: function() {
  5122. var $lightbox = $('.fl-lightbox:visible');
  5123. FLBuilder._hideTabsOverflowMenu();
  5124. $lightbox.find('.fl-builder-settings-tabs .fl-active').removeClass('fl-active');
  5125. $lightbox.find('.fl-builder-settings-tabs-overflow-menu .fl-active').removeClass('fl-active');
  5126. $lightbox.find('.fl-contains-active').removeClass('fl-contains-active');
  5127. },
  5128. /**
  5129. * Measures tabs and adds extra items to overflow menu.
  5130. *
  5131. * @since 2.0
  5132. * @access private
  5133. * @return void
  5134. * @method _settingsTabsToOverflowMenu
  5135. */
  5136. _calculateSettingsTabsOverflow: function() {
  5137. var $lightbox = $('.fl-lightbox:visible'),
  5138. lightboxWidth = $lightbox.outerWidth(),
  5139. isSlim = $lightbox.hasClass('fl-lightbox-width-slim'),
  5140. $tabWrap = $lightbox.find('.fl-builder-settings-tabs'),
  5141. $overflowMenu = $lightbox.find('.fl-builder-settings-tabs-overflow-menu'),
  5142. $overflowMenuBtn = $lightbox.find('.fl-builder-settings-tabs-more'),
  5143. $tabs = $tabWrap.find('a'),
  5144. shouldEjectRemainingTabs = false,
  5145. tabsAreaWidth = lightboxWidth - 60, /* 60 is size of "more" btn */
  5146. tabsWidthTotal = 0,
  5147. tabPadding = isSlim ? ( 8 * 2 ) : ( 15 * 2 );
  5148. // Reset the menu
  5149. $overflowMenu.html('');
  5150. FLBuilder._hideTabsOverflowMenu();
  5151. $tabs.removeClass('fl-overflowed');
  5152. // Measure each tab
  5153. $tabs.each(function() {
  5154. if ( !$(this).is(":visible") ) {
  5155. return true;
  5156. }
  5157. // Calculate size until too wide for tab area.
  5158. if ( !shouldEjectRemainingTabs ) {
  5159. // Width of text + padding + bumper space
  5160. var currentTabWidth = $(this).textWidth() + tabPadding + 12;
  5161. tabsWidthTotal += currentTabWidth;
  5162. if ( tabsWidthTotal >= tabsAreaWidth ) {
  5163. shouldEjectRemainingTabs = true;
  5164. } else {
  5165. }
  5166. }
  5167. if ( shouldEjectRemainingTabs ) {
  5168. var label = $(this).html(),
  5169. handle = $(this).attr('href'),
  5170. classAttr = "";
  5171. if ( $(this).hasClass('fl-active') ) {
  5172. classAttr = 'fl-active';
  5173. }
  5174. if ( $(this).hasClass('error') ) {
  5175. classAttr += ' error';
  5176. }
  5177. if ( classAttr !== '' ) {
  5178. classAttr = 'class="' + classAttr + '"';
  5179. }
  5180. var $item = $('<a href="' + handle + '" ' + classAttr + '>' + label + '</a>');
  5181. $overflowMenu.append( $item );
  5182. $(this).addClass('fl-overflowed');
  5183. } else {
  5184. $(this).removeClass('fl-overflowed');
  5185. }
  5186. });
  5187. if ( shouldEjectRemainingTabs ) {
  5188. $lightbox.addClass('fl-lightbox-has-tab-overflow');
  5189. } else {
  5190. $lightbox.removeClass('fl-lightbox-has-tab-overflow');
  5191. }
  5192. if ( $overflowMenu.find('.fl-active').length > 0 ) {
  5193. $overflowMenuBtn.addClass('fl-contains-active');
  5194. } else {
  5195. $overflowMenuBtn.removeClass('fl-contains-active');
  5196. }
  5197. if ( $overflowMenu.find('.error').length > 0 ) {
  5198. $overflowMenuBtn.addClass('fl-contains-errors');
  5199. } else {
  5200. $overflowMenuBtn.removeClass('fl-contains-errors');
  5201. }
  5202. },
  5203. /**
  5204. * Trigger the orignal tab when a menu item is clicked.
  5205. *
  5206. * @since 2.0
  5207. * @var {Event} e
  5208. * @return void
  5209. */
  5210. _settingsTabsToOverflowMenuItemClicked: function(e) {
  5211. var $item = $(e.currentTarget),
  5212. handle = $item.attr('href'),
  5213. $tabsWrap = $item.closest('.fl-lightbox-header-wrap').find('.fl-builder-settings-tabs'),
  5214. $tab = $tabsWrap.find('a[href="' + handle + '"]'),
  5215. $moreBtn = $tabsWrap.find('.fl-builder-settings-tabs-more');
  5216. FLBuilder._resetSettingsTabsState();
  5217. $tab.trigger('click');
  5218. $item.addClass('fl-active');
  5219. $moreBtn.addClass('fl-contains-active');
  5220. FLBuilder._hideTabsOverflowMenu();
  5221. e.preventDefault();
  5222. },
  5223. /**
  5224. * Check if overflow menu contains any tabs
  5225. *
  5226. * @since 2.0
  5227. * @return bool
  5228. */
  5229. _hasOverflowTabs: function() {
  5230. var $lightbox = $('.fl-lightbox:visible'),
  5231. $tabs = $lightbox.find('.fl-builder-settings-tabs-overflow-menu a');
  5232. if ( $tabs.length > 0 ) {
  5233. return true;
  5234. } else {
  5235. return false;
  5236. }
  5237. },
  5238. /**
  5239. * Show the overflow menu
  5240. *
  5241. */
  5242. _showTabsOverflowMenu: function() {
  5243. if ( ! FLBuilder._hasOverflowTabs() ) return;
  5244. var $lightbox = $('.fl-lightbox:visible');
  5245. $lightbox.find('.fl-builder-settings-tabs-overflow-menu').css('display', 'flex');
  5246. $lightbox.find('.fl-builder-settings-tabs-overflow-click-mask').show();
  5247. this.isShowingSettingsTabsOverflowMenu = true;
  5248. },
  5249. /**
  5250. * Hide the overflow menu
  5251. */
  5252. _hideTabsOverflowMenu: function() {
  5253. var $lightbox = $('.fl-lightbox:visible');
  5254. $lightbox.find('.fl-builder-settings-tabs-overflow-menu').css('display', 'none');
  5255. $lightbox.find('.fl-builder-settings-tabs-overflow-click-mask').hide();
  5256. this.isShowingSettingsTabsOverflowMenu = false;
  5257. },
  5258. /**
  5259. * Toggle the overflow menu
  5260. */
  5261. _toggleTabsOverflowMenu: function( e ) {
  5262. if ( FLBuilder.isShowingSettingsTabsOverflowMenu ) {
  5263. FLBuilder._hideTabsOverflowMenu();
  5264. } else {
  5265. FLBuilder._showTabsOverflowMenu();
  5266. }
  5267. e.stopPropagation();
  5268. },
  5269. /**
  5270. * Reverts an active preview and hides the lightbox when
  5271. * the cancel button of a settings lightbox is clicked.
  5272. *
  5273. * @since 1.0
  5274. * @access private
  5275. * @method _settingsCancelClicked
  5276. * @param {Object} e The event object.
  5277. */
  5278. _settingsCancelClicked: function(e)
  5279. {
  5280. var nestedLightbox = $( '.fl-builder-lightbox[data-parent]' ),
  5281. moduleSettings = $('.fl-builder-module-settings'),
  5282. existingNodes = null,
  5283. previewModule = null,
  5284. previewCol = null,
  5285. existingCol = null;
  5286. // Close a nested settings lightbox.
  5287. if ( nestedLightbox.length > 0 ) {
  5288. FLBuilder._closeNestedSettings();
  5289. return;
  5290. }
  5291. // Delete a new module preview?
  5292. else if(moduleSettings.length > 0 && typeof moduleSettings.data('new-module') != 'undefined') {
  5293. existingNodes = $(FLBuilder.preview.state.html);
  5294. previewModule = $('.fl-node-' + moduleSettings.data('node'));
  5295. previewCol = previewModule.closest('.fl-col');
  5296. existingCol = existingNodes.find('.fl-node-' + previewCol.data('node'));
  5297. if(existingCol.length > 0) {
  5298. FLBuilder._deleteModule(previewModule);
  5299. }
  5300. else {
  5301. FLBuilder._deleteCol(previewCol);
  5302. }
  5303. }
  5304. // Do a standard preview revert.
  5305. else if( FLBuilder.preview ) {
  5306. FLBuilder.preview.revert();
  5307. }
  5308. FLBuilder.preview = null;
  5309. FLLightbox.closeParent(this);
  5310. },
  5311. /**
  5312. * Focus the first visible control in a settings panel
  5313. *
  5314. * @since 2.0
  5315. */
  5316. _focusFirstSettingsControl: function() {
  5317. var form = $( '.fl-builder-settings:visible' ),
  5318. tab = form.find( '.fl-builder-settings-tab.fl-active' ),
  5319. nodeId = form.data( 'node' ),
  5320. field = tab.find('.fl-field').first(),
  5321. input = field.find( 'input:not([type="hidden"]), textarea, select, button, a, .fl-editor-field' ).first();
  5322. // Don't focus fields that have an inline editor.
  5323. if ( nodeId && $( '.fl-node-' + nodeId + ' .fl-inline-editor' ).length ) {
  5324. return;
  5325. }
  5326. if ( 'undefined' !== typeof tinyMCE && input.hasClass('fl-editor-field') ) {
  5327. // TinyMCE fields
  5328. var id = input.find('textarea.wp-editor-area').attr('id');
  5329. tinyMCE.get( id ).focus();
  5330. } else {
  5331. // Everybody else
  5332. setTimeout(function() {
  5333. input.focus().css('animation-name', 'fl-grab-attention');
  5334. }, 300 );
  5335. }
  5336. // Grab attention
  5337. field.css('animation-name', 'fl-grab-attention');
  5338. field.on('animationend', function() {
  5339. field.css('animation-name', '');
  5340. });
  5341. },
  5342. /**
  5343. * Initializes validation logic for a settings form.
  5344. *
  5345. * @since 1.0
  5346. * @access private
  5347. * @method _initSettingsValidation
  5348. * @param {Object} rules The validation rules object.
  5349. * @param {Object} messages Custom messages to show for invalid fields.
  5350. */
  5351. _initSettingsValidation: function(rules, messages)
  5352. {
  5353. var form = $('.fl-builder-settings').last();
  5354. form.validate({
  5355. ignore: '.fl-ignore-validation',
  5356. rules: rules,
  5357. messages: messages,
  5358. errorPlacement: FLBuilder._settingsErrorPlacement
  5359. });
  5360. },
  5361. /**
  5362. * Places a validation error after the invalid field.
  5363. *
  5364. * @since 1.0
  5365. * @access private
  5366. * @method _settingsErrorPlacement
  5367. * @param {Object} error The error element.
  5368. * @param {Object} element The invalid field.
  5369. */
  5370. _settingsErrorPlacement: function(error, element)
  5371. {
  5372. error.appendTo(element.parent());
  5373. },
  5374. /**
  5375. * Resets all tab error icons and then shows any for tabs
  5376. * that have fields with errors.
  5377. *
  5378. * @since 1.0
  5379. * @access private
  5380. * @method _toggleSettingsTabErrors
  5381. */
  5382. _toggleSettingsTabErrors: function()
  5383. {
  5384. var form = $('.fl-builder-settings:visible'),
  5385. tabs = form.find('.fl-builder-settings-tab'),
  5386. tab = null,
  5387. tabErrors = null,
  5388. i = 0;
  5389. for( ; i < tabs.length; i++) {
  5390. tab = tabs.eq(i);
  5391. tabErrors = tab.find('label.error');
  5392. tabLink = form.find('.fl-builder-settings-tabs a[href*='+ tab.attr('id') +']');
  5393. tabLink.find('.fl-error-icon').remove();
  5394. tabLink.removeClass('error');
  5395. if(tabErrors.length > 0) {
  5396. tabLink.append('<span class="fl-error-icon"></span>');
  5397. tabLink.addClass('error');
  5398. }
  5399. }
  5400. FLBuilder._calculateSettingsTabsOverflow();
  5401. },
  5402. /**
  5403. * Returns an object with key/value pairs for all fields
  5404. * within a settings form.
  5405. *
  5406. * @since 1.0
  5407. * @access private
  5408. * @method _getSettings
  5409. * @param {Object} form The settings form element.
  5410. * @return {Object} The settings object.
  5411. */
  5412. _getSettings: function( form )
  5413. {
  5414. FLBuilder._updateEditorFields();
  5415. var data = form.serializeArray(),
  5416. i = 0,
  5417. k = 0,
  5418. value = '',
  5419. name = '',
  5420. key = '',
  5421. keys = [],
  5422. matches = [],
  5423. settings = {};
  5424. // Loop through the form data.
  5425. for ( i = 0; i < data.length; i++ ) {
  5426. value = data[ i ].value.replace( /\r/gm, '' ).replace( /&#39;/g, "'" );
  5427. // Don't save text editor textareas.
  5428. if ( data[ i ].name.indexOf( 'flrich' ) > -1 ) {
  5429. continue;
  5430. }
  5431. // Support foo[]... setting keys.
  5432. else if ( data[ i ].name.indexOf( '[' ) > -1 ) {
  5433. name = data[ i ].name.replace( /\[(.*)\]/, '' );
  5434. key = data[ i ].name.replace( name, '' );
  5435. keys = [];
  5436. matches = key.match( /\[[^\]]*\]/g );
  5437. // Remove [] from the keys.
  5438. for ( k = 0; k < matches.length; k++ ) {
  5439. if ( '[]' == matches[ k ] ) {
  5440. continue;
  5441. }
  5442. keys.push( matches[ k ].replace( /\[|\]/g, '' ) );
  5443. }
  5444. // foo[][key][key]
  5445. if ( key.match( /\[\]\[[^\]]*\]\[[^\]]+\]/ ) ) {
  5446. if ( 'undefined' == typeof settings[ name ] ) {
  5447. settings[ name ] = {};
  5448. }
  5449. if ( 'undefined' == typeof settings[ name ][ keys[ 0 ] ] ) {
  5450. settings[ name ][ keys[ 0 ] ] = {};
  5451. }
  5452. if ( 'undefined' == typeof settings[ name ][ keys[ 0 ] ][ keys[ 1 ] ] ) {
  5453. settings[ name ][ keys[ 0 ] ][ keys[ 1 ] ] = {};
  5454. }
  5455. settings[ name ][ keys[ 0 ] ][ keys[ 1 ] ] = value;
  5456. }
  5457. // foo[][key][]
  5458. else if ( key.match( /\[\]\[[^\]]*\]\[\]/ ) ) {
  5459. if ( 'undefined' == typeof settings[ name ] ) {
  5460. settings[ name ] = {};
  5461. }
  5462. if ( 'undefined' == typeof settings[ name ][ keys[ 0 ] ] ) {
  5463. settings[ name ][ keys[ 0 ] ] = [];
  5464. }
  5465. settings[ name ][ keys[ 0 ] ].push( value );
  5466. }
  5467. // foo[][key]
  5468. else if ( key.match( /\[\]\[[^\]]*\]/ ) ) {
  5469. if ( 'undefined' == typeof settings[ name ] ) {
  5470. settings[ name ] = {};
  5471. }
  5472. settings[ name ][ keys[ 0 ] ] = value;
  5473. }
  5474. // foo[]
  5475. else if ( key.match( /\[\]/ ) ) {
  5476. if ( 'undefined' == typeof settings[ name ] ) {
  5477. settings[ name ] = [];
  5478. }
  5479. settings[ name ].push( value );
  5480. }
  5481. }
  5482. // Standard name/value pair.
  5483. else {
  5484. settings[ data[ i ].name ] = value;
  5485. }
  5486. }
  5487. // Update auto suggest values.
  5488. for ( key in settings ) {
  5489. if ( 'undefined' != typeof settings[ 'as_values_' + key ] ) {
  5490. settings[ key ] = $.grep(
  5491. settings[ 'as_values_' + key ].split( ',' ),
  5492. function( n ) {
  5493. return n !== '';
  5494. }
  5495. ).join( ',' );
  5496. try {
  5497. delete settings[ 'as_values_' + key ];
  5498. }
  5499. catch( e ) {}
  5500. }
  5501. }
  5502. // In the case of multi-select or checkboxes we need to put the blank setting back in.
  5503. $.each( form.find( '[name]' ), function( key, input ) {
  5504. var name = $( input ).attr( 'name' ).replace( /\[(.*)\]/, '' );
  5505. if ( ! ( name in settings ) ) {
  5506. settings[ name ] = '';
  5507. }
  5508. });
  5509. // Merge in the original settings in case legacy fields haven't rendered yet.
  5510. settings = $.extend( {}, FLBuilder._getOriginalSettings( form ), settings );
  5511. // Return the settings.
  5512. return settings;
  5513. },
  5514. /**
  5515. * Returns JSON encoded settings to be used in HTML form elements.
  5516. *
  5517. * @since 2.0
  5518. * @access private
  5519. * @method _getSettingsJSONForHTML
  5520. * @param {Object} settings The settings object.
  5521. * @return {String} The settings JSON.
  5522. */
  5523. _getSettingsJSONForHTML: function( settings )
  5524. {
  5525. return JSON.stringify( settings ).replace( /\'/g, '&#39;' ).replace( '<wbr \/>', '<wbr>' );
  5526. },
  5527. /**
  5528. * Returns the original settings for a settings form. This is only
  5529. * used to work with legacy PHP settings fields.
  5530. *
  5531. * @since 2.0
  5532. * @access private
  5533. * @method _getOriginalSettings
  5534. * @param {Object} form The settings form element.
  5535. * @param {Boolean} all Whether to include all of the settings or just those with fields.
  5536. * @return {Object} The settings object.
  5537. */
  5538. _getOriginalSettings: function( form, all )
  5539. {
  5540. var formJSON = form.find( '.fl-builder-settings-json' ),
  5541. nodeId = form.data( 'node' ),
  5542. config = FLBuilderSettingsConfig.nodes,
  5543. original = null,
  5544. settings = {};
  5545. if ( nodeId && config[ nodeId ] ) {
  5546. original = config[ nodeId ];
  5547. } else if ( formJSON.length ) {
  5548. original = JSON.parse( formJSON.val().replace( /&#39;/g, "'" ) );
  5549. }
  5550. if ( original ) {
  5551. for ( key in original ) {
  5552. if ( $( '#fl-field-' + key ).length || all ) {
  5553. settings[ key ] = original[ key ];
  5554. }
  5555. }
  5556. }
  5557. return settings;
  5558. },
  5559. /**
  5560. * Gets the settings that are saved to see if settings
  5561. * have changed when saving or canceling.
  5562. *
  5563. * @since 2.1
  5564. * @method getSettingsForChangedCheck
  5565. * @param {Object} form
  5566. * @return {Object}
  5567. */
  5568. _getSettingsForChangedCheck: function( nodeId, form ) {
  5569. var settings = FLBuilder._getSettings( form );
  5570. // Make sure we're getting the original setting if even it
  5571. // was changed by inline editing before the form loaded.
  5572. if ( nodeId ) {
  5573. var node = $( '.fl-node-' + nodeId );
  5574. if ( node.hasClass( 'fl-module' ) ) {
  5575. var type = node.data( 'type' );
  5576. var config = FLBuilderSettingsConfig.editables[ type ];
  5577. if ( config && FLBuilderSettingsConfig.nodes[ nodeId ] ) {
  5578. for ( var key in config ) {
  5579. settings[ key ] = FLBuilderSettingsConfig.nodes[ nodeId ][ key ]
  5580. }
  5581. }
  5582. }
  5583. }
  5584. return settings;
  5585. },
  5586. /**
  5587. * Saves the settings for the current settings form, shows
  5588. * the loader and hides the lightbox.
  5589. *
  5590. * @since 1.0
  5591. * @access private
  5592. * @method _saveSettings
  5593. * @param {Boolean} render Whether the layout should render after saving.
  5594. */
  5595. _saveSettings: function( render )
  5596. {
  5597. var form = $( '.fl-builder-settings-lightbox .fl-builder-settings' ),
  5598. newModule = form.data( 'new-module' ),
  5599. nodeId = form.attr( 'data-node' ),
  5600. settings = FLBuilder._getSettings( form ),
  5601. preview = FLBuilder.preview;
  5602. // Default to true for render.
  5603. if ( _.isUndefined( render ) || ! _.isBoolean( render ) ) {
  5604. render = true;
  5605. }
  5606. // Only proceed if the settings have changed.
  5607. if ( preview && ! preview._settingsHaveChanged() && _.isUndefined( newModule ) ) {
  5608. FLBuilder._lightbox.close();
  5609. return;
  5610. }
  5611. // Show the loader.
  5612. FLBuilder._showNodeLoading( nodeId );
  5613. // Update the settings config object.
  5614. FLBuilderSettingsConfig.nodes[ nodeId ] = settings;
  5615. // Make the AJAX call.
  5616. FLBuilder.ajax( {
  5617. action : 'save_settings',
  5618. node_id : nodeId,
  5619. settings : settings
  5620. }, FLBuilder._saveSettingsComplete.bind( this, render, preview ) );
  5621. // Trigger the hook.
  5622. FLBuilder.triggerHook( 'didSaveNodeSettings', {
  5623. nodeId : nodeId,
  5624. settings : settings
  5625. } );
  5626. // Close the lightbox.
  5627. FLBuilder._lightbox.close();
  5628. },
  5629. /**
  5630. * Renders a new layout when the settings for the current
  5631. * form have finished saving.
  5632. *
  5633. * @since 1.0
  5634. * @access private
  5635. * @method _saveSettingsComplete
  5636. * @param {Boolean} render Whether the layout should render after saving.
  5637. * @param {Object} preview The preview object for this settings save.
  5638. * @param {String} response The layout data from the server.
  5639. */
  5640. _saveSettingsComplete: function( render, preview, response )
  5641. {
  5642. var data = JSON.parse( response ),
  5643. callback = function() {
  5644. if( preview && data.layout.partial && data.layout.nodeId === preview.nodeId ) {
  5645. preview.clear();
  5646. preview = null;
  5647. }
  5648. };
  5649. if ( true === render ) {
  5650. FLBuilder._renderLayout( data.layout, callback );
  5651. } else {
  5652. callback();
  5653. }
  5654. FLBuilder.triggerHook( 'didSaveNodeSettingsComplete', {
  5655. nodeId : data.node_id,
  5656. settings : data.settings
  5657. } );
  5658. },
  5659. /**
  5660. * Triggers a click on the settings save button so all save
  5661. * logic runs for any form that is currently in the lightbox.
  5662. *
  5663. * @since 2.0
  5664. * @access private
  5665. * @method _triggerSettingsSave
  5666. * @param {Boolean} disableClose
  5667. * @param {Boolean} showAlert
  5668. * @param {Boolean} destroy
  5669. * @return {Boolean}
  5670. */
  5671. _triggerSettingsSave: function( disableClose, showAlert, destroy )
  5672. {
  5673. var form = FLBuilder._lightbox._node.find( 'form.fl-builder-settings' ),
  5674. lightboxId = FLBuilder._lightbox._node.data( 'instance-id' ),
  5675. lightbox = FLLightbox._instances[ lightboxId ],
  5676. nested = $( '.fl-lightbox-wrap[data-parent]:visible' ),
  5677. changed = false,
  5678. valid = true;
  5679. disableClose = _.isUndefined( disableClose ) ? false : disableClose;
  5680. showAlert = _.isUndefined( showAlert ) ? false : showAlert;
  5681. destroy = _.isUndefined( destroy ) ? true : destroy;
  5682. if ( form.length ) {
  5683. // Save any nested settings forms.
  5684. if ( nested.length ) {
  5685. // Save the form.
  5686. nested.find( '.fl-builder-settings-save' ).trigger( 'click' );
  5687. // Don't proceed if not saved.
  5688. if ( nested.find( 'label.error' ).length || $( '.fl-builder-alert-lightbox:visible' ).length ) {
  5689. valid = false;
  5690. }
  5691. }
  5692. // Do a validation check of the main form to see if we should save.
  5693. if ( valid && ! form.validate().form() ) {
  5694. valid = false;
  5695. }
  5696. // Check to see if the main settings have changed.
  5697. changed = FLBuilderSettingsForms.settingsHaveChanged();
  5698. // Save the main settings form if it has changes.
  5699. if ( valid && changed ) {
  5700. // Disable lightbox close?
  5701. if ( disableClose ) {
  5702. lightbox.disableClose();
  5703. }
  5704. // Save the form.
  5705. form.find( '.fl-builder-settings-save' ).trigger( 'click' );
  5706. // Enable lightbox close if it was disabled.
  5707. if ( disableClose ) {
  5708. lightbox.enableClose();
  5709. }
  5710. // Don't proceed if not saved.
  5711. if ( form.find( 'label.error' ).length || $( '.fl-builder-alert-lightbox:visible' ).length ) {
  5712. valid = false;
  5713. }
  5714. }
  5715. // Destroy the settings form?
  5716. if ( destroy ) {
  5717. FLBuilder._destroySettingsForms();
  5718. // Destroy the preview if settings don't have changes.
  5719. if ( ! changed && FLBuilder.preview ) {
  5720. FLBuilder.preview.clear();
  5721. FLBuilder.preview = null;
  5722. }
  5723. }
  5724. // Close the main lightbox if it doesn't have changes and closing isn't disabled.
  5725. if ( ! changed && ! disableClose ) {
  5726. lightbox.close();
  5727. }
  5728. }
  5729. if ( ! valid ) {
  5730. FLBuilder.triggerHook( 'didFailSettingsSave' );
  5731. FLBuilder._toggleSettingsTabErrors();
  5732. if ( showAlert && ! $( '.fl-builder-alert-lightbox:visible' ).length ) {
  5733. FLBuilder.alert( FLBuilderStrings.settingsHaveErrors );
  5734. }
  5735. }
  5736. return valid;
  5737. },
  5738. /**
  5739. * Refreshes preview references for a node's settings panel
  5740. * in case they have been broken by work in the layout.
  5741. *
  5742. * @since 2.0
  5743. * @access private
  5744. * @method _refreshSettingsPreviewReference
  5745. */
  5746. _refreshSettingsPreviewReference: function()
  5747. {
  5748. if ( FLBuilder.preview ) {
  5749. FLBuilder.preview._initElementsAndClasses();
  5750. }
  5751. },
  5752. /* Nested Settings Forms
  5753. ----------------------------------------------------------*/
  5754. /**
  5755. * Opens a nested settings lightbox.
  5756. *
  5757. * @since 1.10
  5758. * @access private
  5759. * @method _openNestedSettings
  5760. * @return object The settings lightbox object.
  5761. */
  5762. _openNestedSettings: function( settings )
  5763. {
  5764. if ( settings.className && -1 === settings.className.indexOf( 'fl-builder-settings-lightbox' ) ) {
  5765. settings.className += ' fl-builder-settings-lightbox';
  5766. }
  5767. settings = $.extend( {
  5768. className: 'fl-builder-lightbox fl-builder-settings-lightbox',
  5769. destroyOnClose: true,
  5770. resizable: true
  5771. }, settings );
  5772. var parentBoxWrap = $( '.fl-lightbox-wrap:visible' ),
  5773. parentBox = parentBoxWrap.find( '.fl-lightbox' ),
  5774. nestedBoxObj = new FLLightbox( settings ),
  5775. nestedBoxWrap = nestedBoxObj._node,
  5776. nestedBox = nestedBoxWrap.find( '.fl-lightbox' );
  5777. parentBoxWrap.hide();
  5778. nestedBoxWrap.attr( 'data-parent', parentBoxWrap.attr( 'data-instance-id' ) );
  5779. nestedBox.attr( 'style', parentBox.attr( 'style' ) );
  5780. nestedBoxObj.on( 'resized', FLBuilder._calculateSettingsTabsOverflow );
  5781. nestedBoxObj.open( '<div class="fl-builder-lightbox-loading"></div>' );
  5782. return nestedBoxObj;
  5783. },
  5784. /**
  5785. * Opens the active nested settings lightbox.
  5786. *
  5787. * @since 1.10
  5788. * @access private
  5789. * @method _closeNestedSettings
  5790. */
  5791. _closeNestedSettings: function()
  5792. {
  5793. var nestedBoxWrap = $( '.fl-builder-lightbox[data-parent]:visible' ),
  5794. nestedBox = nestedBoxWrap.find( '.fl-lightbox' ),
  5795. nestedBoxId = nestedBoxWrap.attr( 'data-instance-id' ),
  5796. nestedBoxObj = FLLightbox._instances[ nestedBoxId ],
  5797. parentBoxId = nestedBoxWrap.attr( 'data-parent' ),
  5798. parentBoxWrap = $( '[data-instance-id="' + parentBoxId + '"]' ),
  5799. parentBox = parentBoxWrap.find( '.fl-lightbox' ),
  5800. parentBoxForm = parentBoxWrap.find('form'),
  5801. parentBoxObj = FLLightbox._instances[ parentBoxId ];
  5802. nestedBoxObj.on( 'close', function() {
  5803. parentBox.attr( 'style', nestedBox.attr( 'style' ) );
  5804. parentBoxWrap.show();
  5805. parentBoxObj._resize();
  5806. parentBoxWrap.find( 'label.error' ).remove();
  5807. parentBoxForm.validate().hideErrors();
  5808. FLBuilder._toggleSettingsTabErrors();
  5809. } );
  5810. nestedBoxObj.close();
  5811. },
  5812. /* Tooltips
  5813. ----------------------------------------------------------*/
  5814. /**
  5815. * Shows a help tooltip in the settings lightbox.
  5816. *
  5817. * @since 1.0
  5818. * @access private
  5819. * @method _showHelpTooltip
  5820. */
  5821. _showHelpTooltip: function()
  5822. {
  5823. $(this).siblings('.fl-help-tooltip-text').fadeIn();
  5824. },
  5825. /**
  5826. * Hides a help tooltip in the settings lightbox.
  5827. *
  5828. * @since 1.0
  5829. * @access private
  5830. * @method _hideHelpTooltip
  5831. */
  5832. _hideHelpTooltip: function()
  5833. {
  5834. $(this).siblings('.fl-help-tooltip-text').fadeOut();
  5835. },
  5836. /* Auto Suggest Fields
  5837. ----------------------------------------------------------*/
  5838. /**
  5839. * Initializes all auto suggest fields within a settings form.
  5840. *
  5841. * @since 1.2.3
  5842. * @access private
  5843. * @method _initAutoSuggestFields
  5844. */
  5845. _initAutoSuggestFields: function()
  5846. {
  5847. var fields = $('.fl-builder-settings:visible .fl-suggest-field'),
  5848. field = null,
  5849. values = null,
  5850. name = null,
  5851. data = [];
  5852. fields.each( function() {
  5853. field = $( this );
  5854. if ( '' !== field.attr( 'data-value' ) ) {
  5855. FLBuilderSettingsForms.showFieldLoader( field );
  5856. data.push( {
  5857. name : field.attr( 'name' ),
  5858. value : field.attr( 'data-value' ),
  5859. action : field.attr( 'data-action' ),
  5860. data : field.attr( 'data-action-data' ),
  5861. } );
  5862. }
  5863. } );
  5864. if ( data.length ) {
  5865. FLBuilder.ajax( {
  5866. action: 'get_autosuggest_values',
  5867. fields: data
  5868. }, function( response ) {
  5869. values = JSON.parse( response );
  5870. for ( name in values ) {
  5871. $( '.fl-suggest-field[name="' + name + '"]' ).attr( 'data-value', values[ name ] );
  5872. }
  5873. fields.each( FLBuilder._initAutoSuggestField );
  5874. } );
  5875. } else {
  5876. fields.each( FLBuilder._initAutoSuggestField );
  5877. }
  5878. },
  5879. /**
  5880. * Initializes a single auto suggest field.
  5881. *
  5882. * @since 1.2.3
  5883. * @access private
  5884. * @method _initAutoSuggestField
  5885. */
  5886. _initAutoSuggestField: function()
  5887. {
  5888. var field = $(this);
  5889. field.autoSuggest(FLBuilder._ajaxUrl({
  5890. 'fl_action' : 'fl_builder_autosuggest',
  5891. 'fl_as_action' : field.data('action'),
  5892. 'fl_as_action_data' : field.data('action-data'),
  5893. '_wpnonce' : FLBuilderConfig.ajaxNonce
  5894. }), $.extend({}, {
  5895. asHtmlID : field.attr('name'),
  5896. selectedItemProp : 'name',
  5897. searchObjProps : 'name',
  5898. minChars : 2,
  5899. keyDelay : 1000,
  5900. fadeOut : false,
  5901. usePlaceholder : true,
  5902. emptyText : FLBuilderStrings.noResultsFound,
  5903. showResultListWhenNoMatch : true,
  5904. preFill : field.data('value'),
  5905. queryParam : 'fl_as_query',
  5906. afterSelectionAdd : FLBuilder._updateAutoSuggestField,
  5907. afterSelectionRemove : FLBuilder._updateAutoSuggestField,
  5908. selectionLimit : field.data('limit')
  5909. }, field.data( 'args' )));
  5910. FLBuilderSettingsForms.hideFieldLoader( field );
  5911. },
  5912. /**
  5913. * Updates the value of an auto suggest field.
  5914. *
  5915. * @since 1.2.3
  5916. * @access private
  5917. * @method _initAutoSuggestField
  5918. * @param {Object} element The auto suggest field.
  5919. * @param {Object} item The current selection.
  5920. * @param {Array} selections An array of selected values.
  5921. */
  5922. _updateAutoSuggestField: function(element, item, selections)
  5923. {
  5924. $(this).siblings('.as-values').val(selections.join(',')).trigger('change');
  5925. },
  5926. /* Code Fields
  5927. ----------------------------------------------------------*/
  5928. /**
  5929. * Initializes all code fields in a settings form.
  5930. *
  5931. * @since 2.0
  5932. * @access private
  5933. * @method _initCodeFields
  5934. */
  5935. _initCodeFields: function()
  5936. {
  5937. $( '.fl-builder-settings:visible' ).find( '.fl-code-field' ).each( FLBuilder._initCodeField );
  5938. },
  5939. /**
  5940. * Initializes a single code field in a settings form.
  5941. *
  5942. * @since 2.0
  5943. * @access private
  5944. * @method _initCodeField
  5945. */
  5946. _initCodeField: function()
  5947. {
  5948. var field = $( this ),
  5949. settings = field.closest( '.fl-builder-settings' ),
  5950. textarea = field.find( 'textarea' ),
  5951. editorId = textarea.attr( 'id' ),
  5952. mode = textarea.data( 'editor' ),
  5953. wrap = textarea.data( 'wrap' ),
  5954. editDiv = $( '<div>', {
  5955. position: 'absolute',
  5956. height: parseInt( textarea.attr( 'rows' ), 10 ) * 20
  5957. } ),
  5958. editor = null;
  5959. editDiv.insertBefore( textarea );
  5960. textarea.css( 'display', 'none' );
  5961. ace.require( 'ace/ext/language_tools' );
  5962. editor = ace.edit( editDiv[0] );
  5963. editor.$blockScrolling = Infinity;
  5964. editor.getSession().setValue( textarea.val() );
  5965. editor.getSession().setMode( 'ace/mode/' + mode );
  5966. if ( wrap ) {
  5967. editor.getSession().setUseWrapMode( true );
  5968. }
  5969. editor.setOptions( FLBuilderConfig.AceEditorSettings );
  5970. editor.getSession().on( 'change', function( e ) {
  5971. textarea.val( editor.getSession().getValue() ).trigger( 'change' );
  5972. } );
  5973. /**
  5974. * Watch the editor for annotation changes and let the
  5975. * user know if there are any errors.
  5976. */
  5977. editor.getSession().on( 'changeAnnotation', function() {
  5978. var annot = editor.getSession().getAnnotations();
  5979. var saveBtn = settings.find( '.fl-builder-settings-save' );
  5980. var errorBtn = settings.find( '.fl-builder-settings-error' );
  5981. var hasError = false;
  5982. for ( var i = 0; i < annot.length; i++ ) {
  5983. if ( annot[ i ].text.indexOf( 'DOCTYPE' ) > -1 ) {
  5984. continue;
  5985. }
  5986. if ( annot[ i ].text.indexOf( 'Named entity expected' ) > -1 ) {
  5987. continue;
  5988. }
  5989. if ( annot[ i ].text.indexOf( '@supports' ) > -1 ) {
  5990. continue;
  5991. }
  5992. if ( 'error' === annot[ i ].type ) {
  5993. hasError = true;
  5994. break;
  5995. }
  5996. }
  5997. if ( hasError && ! errorBtn.length && FLBuilderConfig.CheckCodeErrors ) {
  5998. saveBtn.addClass( 'fl-builder-settings-error' );
  5999. saveBtn.on( 'click', FLBuilder._showCodeFieldError );
  6000. } else if ( ! hasError && errorBtn.length ) {
  6001. errorBtn.removeClass( 'fl-builder-settings-error' );
  6002. errorBtn.off( 'click', FLBuilder._showCodeFieldError );
  6003. }
  6004. });
  6005. textarea.closest( '.fl-field' ).data( 'editor', editor );
  6006. },
  6007. /**
  6008. * Shows the code error alert when a code field
  6009. * has an error.
  6010. *
  6011. * @since 2.1
  6012. * @access private
  6013. * @method _showCodeFieldError
  6014. */
  6015. _showCodeFieldError: function( e ) {
  6016. e.stopImmediatePropagation();
  6017. FLBuilder.confirm( {
  6018. message: FLBuilderStrings.codeError,
  6019. cancel: function(){
  6020. var saveBtn = $( '.fl-builder-settings:visible .fl-builder-settings-save' );
  6021. saveBtn.removeClass( 'fl-builder-settings-error' );
  6022. saveBtn.off( 'click', FLBuilder._showCodeFieldError );
  6023. saveBtn.trigger( 'click' );
  6024. },
  6025. strings: {
  6026. ok: FLBuilderStrings.codeErrorFix,
  6027. cancel: FLBuilderStrings.codeErrorIgnore
  6028. }
  6029. } );
  6030. },
  6031. /* Multiple Fields
  6032. ----------------------------------------------------------*/
  6033. /**
  6034. * Initializes all multiple fields in a settings form.
  6035. *
  6036. * @since 1.0
  6037. * @access private
  6038. * @method _initMultipleFields
  6039. */
  6040. _initMultipleFields: function()
  6041. {
  6042. var multiples = $('.fl-builder-settings:visible .fl-builder-field-multiples'),
  6043. multiple = null,
  6044. fields = null,
  6045. i = 0,
  6046. cursorAt = FLBuilderConfig.isRtl ? { left: 10 } : { right: 10 };
  6047. for( ; i < multiples.length; i++) {
  6048. multiple = multiples.eq(i);
  6049. fields = multiple.find('.fl-builder-field-multiple');
  6050. if(fields.length === 1) {
  6051. fields.eq(0).find('.fl-builder-field-actions').addClass('fl-builder-field-actions-single');
  6052. }
  6053. else {
  6054. fields.find('.fl-builder-field-actions').removeClass('fl-builder-field-actions-single');
  6055. }
  6056. }
  6057. $('.fl-builder-field-multiples').sortable({
  6058. items: '.fl-builder-field-multiple',
  6059. cursor: 'move',
  6060. cursorAt: cursorAt,
  6061. distance: 5,
  6062. opacity: 0.5,
  6063. placeholder: 'fl-builder-field-dd-zone',
  6064. stop: FLBuilder._fieldDragStop,
  6065. tolerance: 'pointer',
  6066. axis: "y"
  6067. });
  6068. },
  6069. /**
  6070. * Adds a new multiple field to the list when the add
  6071. * button is clicked.
  6072. *
  6073. * @since 1.0
  6074. * @access private
  6075. * @method _addFieldClicked
  6076. */
  6077. _addFieldClicked: function()
  6078. {
  6079. var button = $(this),
  6080. fieldName = button.attr('data-field'),
  6081. fieldRow = button.closest('tr').siblings('tr[data-field='+ fieldName +']').last(),
  6082. clone = fieldRow.clone(),
  6083. form = clone.find( '.fl-form-field' ),
  6084. formType = null,
  6085. index = parseInt(fieldRow.find('label span.fl-builder-field-index').html(), 10) + 1;
  6086. clone.find('th label span.fl-builder-field-index').html(index);
  6087. clone.find('.fl-form-field-preview-text').html('');
  6088. clone.find('.fl-form-field-before').remove();
  6089. clone.find('.fl-form-field-after').remove();
  6090. clone.find('input, textarea, select').val('');
  6091. fieldRow.after(clone);
  6092. FLBuilder._initMultipleFields();
  6093. if ( form.length ) {
  6094. formType = form.find( '.fl-form-field-edit' ).data( 'type' );
  6095. form.find( 'input' ).val( JSON.stringify( FLBuilderSettingsConfig.defaults.forms[ formType ] ) );
  6096. }
  6097. },
  6098. /**
  6099. * Copies a multiple field and adds it to the list when
  6100. * the copy button is clicked.
  6101. *
  6102. * @since 1.0
  6103. * @access private
  6104. * @method _copyFieldClicked
  6105. */
  6106. _copyFieldClicked: function()
  6107. {
  6108. var button = $(this),
  6109. row = button.closest('tr'),
  6110. clone = row.clone(),
  6111. index = parseInt(row.find('label span.fl-builder-field-index').html(), 10) + 1;
  6112. clone.find('th label span.fl-builder-field-index').html(index);
  6113. row.after(clone);
  6114. FLBuilder._renumberFields(row.parent());
  6115. FLBuilder._initMultipleFields();
  6116. FLBuilder.preview.delayPreview();
  6117. },
  6118. /**
  6119. * Deletes a multiple field from the list when the
  6120. * delete button is clicked.
  6121. *
  6122. * @since 1.0
  6123. * @access private
  6124. * @method _deleteFieldClicked
  6125. */
  6126. _deleteFieldClicked: function()
  6127. {
  6128. var row = $(this).closest('tr'),
  6129. parent = row.parent(),
  6130. result = confirm(FLBuilderStrings.deleteFieldMessage);
  6131. if(result) {
  6132. row.remove();
  6133. FLBuilder._renumberFields(parent);
  6134. FLBuilder._initMultipleFields();
  6135. FLBuilder.preview.delayPreview();
  6136. }
  6137. },
  6138. /**
  6139. * Renumbers the labels for a list of multiple fields.
  6140. *
  6141. * @since 1.0
  6142. * @access private
  6143. * @method _renumberFields
  6144. * @param {Object} table A table element with multiple fields.
  6145. */
  6146. _renumberFields: function(table)
  6147. {
  6148. var rows = table.find('.fl-builder-field-multiple'),
  6149. i = 0;
  6150. for( ; i < rows.length; i++) {
  6151. rows.eq(i).find('th label span.fl-builder-field-index').html(i + 1);
  6152. }
  6153. },
  6154. /**
  6155. * Returns an element for multiple field drag operations.
  6156. *
  6157. * @since 1.0
  6158. * @access private
  6159. * @method _fieldDragHelper
  6160. * @return {Object} The helper element.
  6161. */
  6162. _fieldDragHelper: function()
  6163. {
  6164. return $('<div class="fl-builder-field-dd-helper"></div>');
  6165. },
  6166. /**
  6167. * Renumbers and triggers a preview when a multiple field
  6168. * has finished dragging.
  6169. *
  6170. * @since 1.0
  6171. * @access private
  6172. * @method _fieldDragStop
  6173. * @param {Object} e The event object.
  6174. * @param {Object} ui An object with additional info for the drag.
  6175. */
  6176. _fieldDragStop: function(e, ui)
  6177. {
  6178. FLBuilder._renumberFields(ui.item.parent());
  6179. FLBuilder.preview.delayPreview();
  6180. },
  6181. /* Select Fields
  6182. ----------------------------------------------------------*/
  6183. /**
  6184. * Initializes select fields for a settings form.
  6185. *
  6186. * @since 1.0
  6187. * @access private
  6188. * @method _initSelectFields
  6189. */
  6190. _initSelectFields: function()
  6191. {
  6192. $('.fl-builder-settings:visible').find('.fl-builder-settings-fields select').trigger('change');
  6193. },
  6194. /**
  6195. * Callback for when a settings form select has been changed.
  6196. * If toggle data is present, other fields will be toggled
  6197. * when this select changes.
  6198. *
  6199. * @since 1.0
  6200. * @access private
  6201. * @method _settingsSelectChanged
  6202. */
  6203. _settingsSelectChanged: function()
  6204. {
  6205. var select = $(this),
  6206. toggle = select.attr('data-toggle'),
  6207. hide = select.attr('data-hide'),
  6208. trigger = select.attr('data-trigger'),
  6209. val = select.val(),
  6210. i = 0,
  6211. k = 0;
  6212. // TOGGLE sections, fields or tabs.
  6213. if(typeof toggle !== 'undefined') {
  6214. toggle = JSON.parse(toggle);
  6215. for(i in toggle) {
  6216. FLBuilder._settingsSelectToggle(toggle[i].fields, 'hide', '#fl-field-');
  6217. FLBuilder._settingsSelectToggle(toggle[i].sections, 'hide', '#fl-builder-settings-section-');
  6218. FLBuilder._settingsSelectToggle(toggle[i].tabs, 'hide', 'a[href*=fl-builder-settings-tab-', ']');
  6219. }
  6220. if(typeof toggle[val] !== 'undefined') {
  6221. FLBuilder._settingsSelectToggle(toggle[val].fields, 'show', '#fl-field-');
  6222. FLBuilder._settingsSelectToggle(toggle[val].sections, 'show', '#fl-builder-settings-section-');
  6223. FLBuilder._settingsSelectToggle(toggle[val].tabs, 'show', 'a[href*=fl-builder-settings-tab-', ']');
  6224. }
  6225. }
  6226. // HIDE sections, fields or tabs.
  6227. if(typeof hide !== 'undefined') {
  6228. hide = JSON.parse(hide);
  6229. if(typeof hide[val] !== 'undefined') {
  6230. FLBuilder._settingsSelectToggle(hide[val].fields, 'hide', '#fl-field-');
  6231. FLBuilder._settingsSelectToggle(hide[val].sections, 'hide', '#fl-builder-settings-section-');
  6232. FLBuilder._settingsSelectToggle(hide[val].tabs, 'hide', 'a[href*=fl-builder-settings-tab-', ']');
  6233. }
  6234. }
  6235. // TRIGGER select inputs.
  6236. if(typeof trigger !== 'undefined') {
  6237. trigger = JSON.parse(trigger);
  6238. if(typeof trigger[val] !== 'undefined') {
  6239. if(typeof trigger[val].fields !== 'undefined') {
  6240. for(i = 0; i < trigger[val].fields.length; i++) {
  6241. $('#fl-field-' + trigger[val].fields[i]).find('select').trigger('change');
  6242. }
  6243. }
  6244. }
  6245. }
  6246. FLBuilder._calculateSettingsTabsOverflow();
  6247. },
  6248. /**
  6249. * @since 1.0
  6250. * @access private
  6251. * @method _settingsSelectToggle
  6252. * @param {Array} inputArray
  6253. * @param {Function} func
  6254. * @param {String} prefix
  6255. * @param {String} suffix
  6256. */
  6257. _settingsSelectToggle: function(inputArray, func, prefix, suffix)
  6258. {
  6259. var i = 0;
  6260. suffix = 'undefined' == typeof suffix ? '' : suffix;
  6261. if(typeof inputArray !== 'undefined') {
  6262. for( ; i < inputArray.length; i++) {
  6263. $('.fl-builder-settings:visible').find(prefix + inputArray[i] + suffix)[func]();
  6264. // Resize code editor fields.
  6265. $( prefix + inputArray[i] + suffix ).parent().find( '.fl-field[data-type="code"]' ).each( function() {
  6266. $( this ).data( 'editor' ).resize();
  6267. } );
  6268. }
  6269. }
  6270. },
  6271. /* Color Pickers
  6272. ----------------------------------------------------------*/
  6273. /**
  6274. * Initializes color picker fields for a settings form.
  6275. *
  6276. * @since 1.0
  6277. * @access private
  6278. * @method _initColorPickers
  6279. */
  6280. _initColorPickers: function()
  6281. {
  6282. var colorPresets = FLBuilderConfig.colorPresets ? FLBuilderConfig.colorPresets : [];
  6283. FLBuilder.colorPicker = new FLBuilderColorPicker({
  6284. mode: 'hsv',
  6285. elements: '.fl-color-picker .fl-color-picker-value',
  6286. presets: colorPresets,
  6287. labels: {
  6288. colorPresets : FLBuilderStrings.colorPresets,
  6289. colorPicker : FLBuilderStrings.colorPicker,
  6290. placeholder : FLBuilderStrings.placeholder,
  6291. removePresetConfirm : FLBuilderStrings.removePresetConfirm,
  6292. noneColorSelected : FLBuilderStrings.noneColorSelected,
  6293. alreadySaved : FLBuilderStrings.alreadySaved,
  6294. noPresets : FLBuilderStrings.noPresets,
  6295. presetAdded : FLBuilderStrings.presetAdded,
  6296. }
  6297. });
  6298. $( FLBuilder.colorPicker ).on( 'presetRemoved presetAdded', function( event, data ) {
  6299. FLBuilder.ajax({
  6300. action: 'save_color_presets',
  6301. presets: data.presets
  6302. });
  6303. });
  6304. },
  6305. /* Single Photo Fields
  6306. ----------------------------------------------------------*/
  6307. /**
  6308. * Initializes the single photo selector.
  6309. *
  6310. * @since 1.8.6
  6311. * @access private
  6312. * @method _initSinglePhotoSelector
  6313. */
  6314. _initSinglePhotoSelector: function()
  6315. {
  6316. if(FLBuilder._singlePhotoSelector === null) {
  6317. FLBuilder._singlePhotoSelector = wp.media({
  6318. title: FLBuilderStrings.selectPhoto,
  6319. button: { text: FLBuilderStrings.selectPhoto },
  6320. library : { type : 'image' },
  6321. multiple: false
  6322. });
  6323. FLBuilder._singlePhotoSelector.on( 'open', FLBuilder._wpmedia_reset_errors );
  6324. _wpPluploadSettings['defaults']['multipart_params']['fl_upload_type']= 'photo';
  6325. }
  6326. },
  6327. /**
  6328. * Shows the single photo selector.
  6329. *
  6330. * @since 1.0
  6331. * @access private
  6332. * @method _selectSinglePhoto
  6333. */
  6334. _selectSinglePhoto: function()
  6335. {
  6336. FLBuilder._initSinglePhotoSelector();
  6337. FLBuilder._singlePhotoSelector.once('open', $.proxy(FLBuilder._singlePhotoOpened, this));
  6338. FLBuilder._singlePhotoSelector.once('select', $.proxy(FLBuilder._singlePhotoSelected, this));
  6339. FLBuilder._singlePhotoSelector.open();
  6340. },
  6341. /**
  6342. * Callback for when the single photo selector is shown.
  6343. *
  6344. * @since 1.0
  6345. * @access private
  6346. * @method _singlePhotoOpened
  6347. */
  6348. _singlePhotoOpened: function()
  6349. {
  6350. var selection = FLBuilder._singlePhotoSelector.state().get('selection'),
  6351. wrap = $(this).closest('.fl-photo-field'),
  6352. photoField = wrap.find('input[type=hidden]'),
  6353. photo = photoField.val(),
  6354. attachment = null;
  6355. if($(this).hasClass('fl-photo-replace')) {
  6356. selection.reset();
  6357. wrap.addClass('fl-photo-empty');
  6358. photoField.val('');
  6359. }
  6360. else if(photo !== '') {
  6361. attachment = wp.media.attachment(photo);
  6362. attachment.fetch();
  6363. selection.add(attachment ? [attachment] : []);
  6364. }
  6365. else {
  6366. selection.reset();
  6367. }
  6368. },
  6369. /**
  6370. * Callback for when a single photo is selected.
  6371. *
  6372. * @since 1.0
  6373. * @access private
  6374. * @method _singlePhotoSelected
  6375. */
  6376. _singlePhotoSelected: function()
  6377. {
  6378. var photo = FLBuilder._singlePhotoSelector.state().get('selection').first().toJSON(),
  6379. wrap = $(this).closest('.fl-photo-field'),
  6380. photoField = wrap.find('input[type=hidden]'),
  6381. preview = wrap.find('.fl-photo-preview img'),
  6382. srcSelect = wrap.find('select');
  6383. photoField.val(photo.id);
  6384. preview.attr('src', FLBuilder._getPhotoSrc(photo));
  6385. wrap.removeClass('fl-photo-empty').removeClass('fl-photo-no-attachment');
  6386. wrap.find('label.error').remove();
  6387. srcSelect.show();
  6388. srcSelect.html(FLBuilder._getPhotoSizeOptions(photo));
  6389. srcSelect.trigger('change');
  6390. FLBuilderSettingsConfig.attachments[ photo.id ] = photo;
  6391. },
  6392. /**
  6393. * Clears a photo that has been selected in a single photo field.
  6394. *
  6395. * @since 1.6.4.3
  6396. * @access private
  6397. * @method _singlePhotoRemoved
  6398. */
  6399. _singlePhotoRemoved: function()
  6400. {
  6401. FLBuilder._initSinglePhotoSelector();
  6402. var state = FLBuilder._singlePhotoSelector.state(),
  6403. selection = 'undefined' != typeof state ? state.get('selection') : null,
  6404. wrap = $(this).closest('.fl-photo-field'),
  6405. photoField = wrap.find('input[type=hidden]'),
  6406. srcSelect = wrap.find('select');
  6407. if ( selection ) {
  6408. selection.reset();
  6409. }
  6410. wrap.addClass('fl-photo-empty');
  6411. photoField.val('');
  6412. srcSelect.html('<option value="" selected></option>');
  6413. srcSelect.trigger('change');
  6414. },
  6415. /**
  6416. * Returns the src URL for a photo.
  6417. *
  6418. * @since 1.0
  6419. * @access private
  6420. * @method _getPhotoSrc
  6421. * @param {Object} photo A photo data object.
  6422. * @return {String} The src URL for a photo.
  6423. */
  6424. _getPhotoSrc: function(photo)
  6425. {
  6426. if(typeof photo.sizes === 'undefined') {
  6427. return photo.url;
  6428. }
  6429. else if(typeof photo.sizes.thumbnail !== 'undefined') {
  6430. return photo.sizes.thumbnail.url;
  6431. }
  6432. else {
  6433. return photo.sizes.full.url;
  6434. }
  6435. },
  6436. /**
  6437. * Builds the options for a photo size select.
  6438. *
  6439. * @since 1.0
  6440. * @access private
  6441. * @method _getPhotoSizeOptions
  6442. * @param {Object} photo A photo data object.
  6443. * @param {String} selectedSize The selected photo size if one is set.
  6444. * @return {String} The HTML for the photo size options.
  6445. */
  6446. _getPhotoSizeOptions: function( photo, selectedSize )
  6447. {
  6448. var html = '',
  6449. size = null,
  6450. selected = null,
  6451. check = null,
  6452. title = '',
  6453. titles = {
  6454. full : FLBuilderStrings.fullSize,
  6455. large : FLBuilderStrings.large,
  6456. medium : FLBuilderStrings.medium,
  6457. thumbnail : FLBuilderStrings.thumbnail
  6458. };
  6459. if(typeof photo.sizes === 'undefined' || 0 === photo.sizes.length) {
  6460. html += '<option value="' + photo.url + '">' + FLBuilderStrings.fullSize + '</option>';
  6461. }
  6462. else {
  6463. // Check the selected value without the protocol so we get a match if
  6464. // a site has switched to HTTPS since selecting this photo (#641).
  6465. if ( selectedSize ) {
  6466. selectedSize = selectedSize.split(/[\\/]/).pop();
  6467. }
  6468. for(size in photo.sizes) {
  6469. if ( 'undefined' != typeof titles[ size ] ) {
  6470. title = titles[ size ] + ' - ';
  6471. }
  6472. else if ( 'undefined' != typeof FLBuilderConfig.customImageSizeTitles[ size ] ) {
  6473. title = FLBuilderConfig.customImageSizeTitles[ size ] + ' - ';
  6474. }
  6475. else {
  6476. title = '';
  6477. }
  6478. selected = '';
  6479. if ( ! selectedSize ) {
  6480. selected = size == 'full' ? ' selected="selected"' : '';
  6481. } else if( selectedSize === photo.sizes[ size ].url.split(/[\\/]/).pop() ) {
  6482. selected = ' selected="selected"';
  6483. }
  6484. html += '<option value="' + photo.sizes[size].url + '"' + selected + '>' + title + photo.sizes[size].width + ' x ' + photo.sizes[size].height + '</option>';
  6485. }
  6486. }
  6487. return html;
  6488. },
  6489. /* Multiple Photo Fields
  6490. ----------------------------------------------------------*/
  6491. /**
  6492. * Shows the multiple photo selector.
  6493. *
  6494. * @since 1.0
  6495. * @access private
  6496. * @method _selectMultiplePhotos
  6497. */
  6498. _selectMultiplePhotos: function()
  6499. {
  6500. var wrap = $(this).closest('.fl-multiple-photos-field'),
  6501. photosField = wrap.find('input[type=hidden]'),
  6502. photosFieldVal = photosField.val(),
  6503. parsedVal = photosFieldVal === '' ? '' : JSON.parse(photosFieldVal),
  6504. defaultPostId = wp.media.gallery.defaults.id,
  6505. content = '[gallery ids="-1"]',
  6506. shortcode = null,
  6507. attachments = null,
  6508. selection = null,
  6509. i = null,
  6510. ids = [];
  6511. // Builder the gallery shortcode.
  6512. if ( 'object' == typeof parsedVal ) {
  6513. for ( i in parsedVal ) {
  6514. ids.push( parsedVal[ i ] );
  6515. }
  6516. content = '[gallery ids="'+ ids.join() +'"]';
  6517. }
  6518. shortcode = wp.shortcode.next('gallery', content).shortcode;
  6519. if(_.isUndefined(shortcode.get('id')) && !_.isUndefined(defaultPostId)) {
  6520. shortcode.set('id', defaultPostId);
  6521. }
  6522. // Get the selection object.
  6523. attachments = wp.media.gallery.attachments(shortcode);
  6524. selection = new wp.media.model.Selection(attachments.models, {
  6525. props: attachments.props.toJSON(),
  6526. multiple: true
  6527. });
  6528. selection.gallery = attachments.gallery;
  6529. // Fetch the query's attachments, and then break ties from the
  6530. // query to allow for sorting.
  6531. selection.more().done(function() {
  6532. if ( ! selection.length ) {
  6533. FLBuilder._multiplePhotoSelector.setState( 'gallery-library' );
  6534. }
  6535. // Break ties with the query.
  6536. selection.props.set({ query: false });
  6537. selection.unmirror();
  6538. selection.props.unset('orderby');
  6539. });
  6540. // Destroy the previous gallery frame.
  6541. if(FLBuilder._multiplePhotoSelector) {
  6542. FLBuilder._multiplePhotoSelector.dispose();
  6543. }
  6544. // Store the current gallery frame.
  6545. FLBuilder._multiplePhotoSelector = wp.media({
  6546. frame: 'post',
  6547. state: $(this).hasClass('fl-multiple-photos-edit') ? 'gallery-edit' : 'gallery-library',
  6548. title: wp.media.view.l10n.editGalleryTitle,
  6549. editing: true,
  6550. multiple: true,
  6551. selection: selection
  6552. }).open();
  6553. $(FLBuilder._multiplePhotoSelector.views.view.el).addClass('fl-multiple-photos-lightbox');
  6554. FLBuilder._multiplePhotoSelector.once('update', $.proxy(FLBuilder._multiplePhotosSelected, this));
  6555. },
  6556. /**
  6557. * Callback for when multiple photos have been selected.
  6558. *
  6559. * @since 1.0
  6560. * @access private
  6561. * @method _multiplePhotosSelected
  6562. * @param {Object} data The photo data object.
  6563. */
  6564. _multiplePhotosSelected: function(data)
  6565. {
  6566. var wrap = $(this).closest('.fl-multiple-photos-field'),
  6567. photosField = wrap.find('input[type=hidden]'),
  6568. count = wrap.find('.fl-multiple-photos-count'),
  6569. photos = [],
  6570. i = 0;
  6571. for( ; i < data.models.length; i++) {
  6572. photos.push(data.models[i].id);
  6573. }
  6574. if(photos.length == 1) {
  6575. count.html('1 ' + FLBuilderStrings.photoSelected);
  6576. }
  6577. else {
  6578. count.html(photos.length + ' ' + FLBuilderStrings.photosSelected);
  6579. }
  6580. wrap.removeClass('fl-multiple-photos-empty');
  6581. wrap.find('label.error').remove();
  6582. photosField.val(JSON.stringify(photos)).trigger('change');
  6583. },
  6584. /* Single Video Fields
  6585. ----------------------------------------------------------*/
  6586. /**
  6587. * Initializes the single video selector.
  6588. *
  6589. * @since 1.10.8
  6590. * @access private
  6591. * @method _initSingleVideoSelector
  6592. */
  6593. _initSingleVideoSelector: function()
  6594. {
  6595. if(FLBuilder._singleVideoSelector === null) {
  6596. FLBuilder._singleVideoSelector = wp.media({
  6597. title: FLBuilderStrings.selectVideo,
  6598. button: { text: FLBuilderStrings.selectVideo },
  6599. library : { type : 'video' },
  6600. multiple: false
  6601. });
  6602. FLBuilder._singleVideoSelector.on( 'open', FLBuilder._wpmedia_reset_errors );
  6603. _wpPluploadSettings['defaults']['multipart_params']['fl_upload_type']= 'video';
  6604. }
  6605. },
  6606. /**
  6607. * Shows the single video selector.
  6608. *
  6609. * @since 1.0
  6610. * @access private
  6611. * @method _selectSingleVideo
  6612. */
  6613. _selectSingleVideo: function()
  6614. {
  6615. FLBuilder._initSingleVideoSelector();
  6616. FLBuilder._singleVideoSelector.once('select', $.proxy(FLBuilder._singleVideoSelected, this));
  6617. FLBuilder._singleVideoSelector.open();
  6618. },
  6619. /**
  6620. * Callback for when a single video is selected.
  6621. *
  6622. * @since 1.0
  6623. * @access private
  6624. * @method _singleVideoSelected
  6625. */
  6626. _singleVideoSelected: function()
  6627. {
  6628. var video = FLBuilder._singleVideoSelector.state().get('selection').first().toJSON(),
  6629. wrap = $(this).closest('.fl-video-field'),
  6630. image = wrap.find('.fl-video-preview-img'),
  6631. filename = wrap.find('.fl-video-preview-filename'),
  6632. videoField = wrap.find('input[type=hidden]');
  6633. image.html('<span class="dashicons dashicons-media-video"></span>');
  6634. filename.html(video.filename);
  6635. wrap.removeClass('fl-video-empty');
  6636. wrap.find('label.error').remove();
  6637. videoField.val(video.id).trigger('change');
  6638. FLBuilderSettingsConfig.attachments[ video.id ] = video;
  6639. },
  6640. /**
  6641. * Clears a video that has been selected in a single video field.
  6642. *
  6643. * @since 2.1
  6644. * @access private
  6645. * @method _singleVideoRemoved
  6646. */
  6647. _singleVideoRemoved: function()
  6648. {
  6649. FLBuilder._initSingleVideoSelector();
  6650. var state = FLBuilder._singleVideoSelector.state(),
  6651. selection = 'undefined' != typeof state ? state.get('selection') : null,
  6652. wrap = $(this).closest('.fl-video-field'),
  6653. image = wrap.find('.fl-video-preview-img img'),
  6654. filename = wrap.find('.fl-video-preview-filename'),
  6655. videoField = wrap.find('input[type=hidden]');
  6656. if ( selection ) {
  6657. selection.reset();
  6658. }
  6659. image.attr('src', '');
  6660. filename.html('');
  6661. wrap.addClass('fl-video-empty');
  6662. videoField.val('').trigger('change');
  6663. },
  6664. /* Multiple Audios Field
  6665. ----------------------------------------------------------*/
  6666. /**
  6667. * Shows the multiple audio selector.
  6668. *
  6669. * @since 1.0
  6670. * @access private
  6671. * @method _selectMultipleAudios
  6672. */
  6673. _selectMultipleAudios: function()
  6674. {
  6675. var wrap = $(this).closest('.fl-multiple-audios-field'),
  6676. audiosField = wrap.find('input[type=hidden]'),
  6677. audiosFieldVal = audiosField.val(),
  6678. content = audiosFieldVal == '' ? '[playlist ids="-1"]' : '[playlist ids="'+ JSON.parse(audiosFieldVal).join() +'"]',
  6679. shortcode = wp.shortcode.next('playlist', content).shortcode,
  6680. defaultPostId = wp.media.playlist.defaults.id,
  6681. attachments = null,
  6682. selection = null;
  6683. if(_.isUndefined(shortcode.get('id')) && !_.isUndefined(defaultPostId)) {
  6684. shortcode.set('id', defaultPostId);
  6685. }
  6686. attachments = wp.media.playlist.attachments(shortcode);
  6687. selection = new wp.media.model.Selection(attachments.models, {
  6688. props: attachments.props.toJSON(),
  6689. multiple: true
  6690. });
  6691. selection.playlist = attachments.playlist;
  6692. // Fetch the query's attachments, and then break ties from the
  6693. // query to allow for sorting.
  6694. selection.more().done(function() {
  6695. // Break ties with the query.
  6696. selection.props.set({ query: false });
  6697. selection.unmirror();
  6698. selection.props.unset('orderby');
  6699. });
  6700. // Destroy the previous frame.
  6701. if(FLBuilder._multipleAudiosSelector) {
  6702. FLBuilder._multipleAudiosSelector.dispose();
  6703. }
  6704. // Store the current frame.
  6705. FLBuilder._multipleAudiosSelector = wp.media({
  6706. frame: 'post',
  6707. state: $(this).hasClass('fl-multiple-audios-edit') ? 'playlist-edit' : 'playlist-library',
  6708. title: wp.media.view.l10n.editPlaylistTitle,
  6709. editing: true,
  6710. multiple: true,
  6711. selection: selection
  6712. }).open();
  6713. // Hide the default playlist settings since we have them added in the audio settings
  6714. FLBuilder._multipleAudiosSelector.content.get('view').sidebar.unset('playlist');
  6715. FLBuilder._multipleAudiosSelector.on( 'content:render:browse', function( browser ) {
  6716. if ( !browser ) return;
  6717. // Hide Playlist Settings in sidebar
  6718. browser.sidebar.on('ready', function(){
  6719. browser.sidebar.unset('playlist');
  6720. });
  6721. });
  6722. FLBuilder._multipleAudiosSelector.once('update', $.proxy(FLBuilder._multipleAudiosSelected, this));
  6723. },
  6724. /**
  6725. * Callback for when a single/multiple audo is selected.
  6726. *
  6727. * @since 1.0
  6728. * @access private
  6729. * @method _multipleAudiosSelected
  6730. */
  6731. _multipleAudiosSelected: function(data)
  6732. {
  6733. var wrap = $(this).closest('.fl-multiple-audios-field'),
  6734. count = wrap.find('.fl-multiple-audios-count'),
  6735. audioField = wrap.find('input[type=hidden]'),
  6736. audios = [],
  6737. i = 0;
  6738. for( ; i < data.models.length; i++) {
  6739. audios.push(data.models[i].id);
  6740. }
  6741. if(audios.length == 1) {
  6742. count.html('1 ' + FLBuilderStrings.audioSelected);
  6743. }
  6744. else {
  6745. count.html(audios.length + ' ' + FLBuilderStrings.audiosSelected);
  6746. }
  6747. audioField.val(JSON.stringify(audios)).trigger('change');
  6748. wrap.removeClass('fl-multiple-audios-empty');
  6749. wrap.find('label.error').remove();
  6750. },
  6751. /* Icon Fields
  6752. ----------------------------------------------------------*/
  6753. /**
  6754. * Shows the icon selector.
  6755. *
  6756. * @since 1.0
  6757. * @access private
  6758. * @method _selectIcon
  6759. */
  6760. _selectIcon: function()
  6761. {
  6762. var self = this;
  6763. FLIconSelector.open(function(icon){
  6764. FLBuilder._iconSelected.apply(self, [icon]);
  6765. });
  6766. },
  6767. /**
  6768. * Callback for when an icon is selected.
  6769. *
  6770. * @since 1.0
  6771. * @access private
  6772. * @method _iconSelected
  6773. * @param {String} icon The selected icon's CSS classname.
  6774. */
  6775. _iconSelected: function(icon)
  6776. {
  6777. var wrap = $(this).closest('.fl-icon-field'),
  6778. iconField = wrap.find('input[type=hidden]'),
  6779. iconTag = wrap.find('i'),
  6780. oldIcon = iconTag.attr('data-icon');
  6781. iconField.val(icon).trigger('change');
  6782. iconTag.removeClass(oldIcon);
  6783. iconTag.addClass(icon);
  6784. iconTag.attr('data-icon', icon);
  6785. wrap.removeClass('fl-icon-empty');
  6786. wrap.find('label.error').remove();
  6787. },
  6788. /**
  6789. * Callback for when a selected icon is removed.
  6790. *
  6791. * @since 1.0
  6792. * @access private
  6793. * @method _removeIcon
  6794. */
  6795. _removeIcon: function()
  6796. {
  6797. var wrap = $(this).closest('.fl-icon-field'),
  6798. iconField = wrap.find('input[type=hidden]'),
  6799. iconTag = wrap.find('i');
  6800. iconField.val('').trigger('change');
  6801. iconTag.removeClass();
  6802. iconTag.attr('data-icon', '');
  6803. wrap.addClass('fl-icon-empty');
  6804. },
  6805. /* Settings Form Fields
  6806. ----------------------------------------------------------*/
  6807. /**
  6808. * Shows the settings for a nested form field when the
  6809. * edit link is clicked.
  6810. *
  6811. * @since 1.0
  6812. * @access private
  6813. * @method _formFieldClicked
  6814. */
  6815. _formFieldClicked: function()
  6816. {
  6817. var link = $( this ),
  6818. form = link.closest( '.fl-builder-settings' ),
  6819. type = link.attr( 'data-type' ),
  6820. settings = link.siblings( 'input' ).val(),
  6821. helper = FLBuilder._moduleHelpers[ type ],
  6822. config = FLBuilderSettingsConfig.forms[ type ],
  6823. lightbox = FLBuilder._openNestedSettings( { className: 'fl-builder-lightbox fl-form-field-settings' } );
  6824. if ( '' === settings ) {
  6825. settings = JSON.stringify( FLBuilderSettingsConfig.forms[ type ] );
  6826. }
  6827. FLBuilderSettingsForms.render( {
  6828. id : type,
  6829. nodeId : form.attr( 'data-node' ),
  6830. nodeSettings : FLBuilder._getSettings( form ),
  6831. settings : JSON.parse( settings.replace( /&#39;/g, "'" ) ),
  6832. lightbox : lightbox,
  6833. helper : helper,
  6834. rules : helper ? helper.rules : null
  6835. }, function() {
  6836. link.attr( 'id', 'fl-' + lightbox._node.attr( 'data-instance-id' ) );
  6837. lightbox._node.find( 'form.fl-builder-settings' ).attr( 'data-type', type );
  6838. } );
  6839. },
  6840. /**
  6841. * Saves the settings for a nested form field when the
  6842. * save button is clicked.
  6843. *
  6844. * @since 1.0
  6845. * @access private
  6846. * @method _saveFormFieldClicked
  6847. * @return {Boolean} Whether the save was successful or not.
  6848. */
  6849. _saveFormFieldClicked: function()
  6850. {
  6851. var form = $(this).closest('.fl-builder-settings'),
  6852. lightboxId = $(this).closest('.fl-lightbox-wrap').attr('data-instance-id'),
  6853. type = form.attr('data-type'),
  6854. settings = FLBuilder._getSettings(form),
  6855. oldSettings = {},
  6856. helper = FLBuilder._moduleHelpers[type],
  6857. link = $('.fl-builder-settings #fl-' + lightboxId),
  6858. preview = link.parent().attr('data-preview-text'),
  6859. previewField = form.find( '#fl-field-' + preview ),
  6860. previewText = settings[preview],
  6861. selectPreview = $( 'select[name="' + preview + '"]' ),
  6862. tmp = document.createElement('div'),
  6863. valid = true;
  6864. if ( selectPreview.length > 0 ) {
  6865. previewText = selectPreview.find( 'option[value="' + settings[ preview ] + '"]' ).text();
  6866. }
  6867. if(typeof helper !== 'undefined') {
  6868. form.find('label.error').remove();
  6869. form.validate().hideErrors();
  6870. valid = form.validate().form();
  6871. if(valid) {
  6872. valid = helper.submit();
  6873. }
  6874. }
  6875. if(valid) {
  6876. if(typeof preview !== 'undefined' && typeof previewText !== 'undefined') {
  6877. if('icon' === previewField.data('type')) {
  6878. previewText = '<i class="' + previewText + '"></i>';
  6879. }
  6880. else if(previewText.length > 35) {
  6881. tmp.innerHTML = previewText;
  6882. previewText = (tmp.textContent || tmp.innerText || '').replace(/^(.{35}[^\s]*).*/, "$1") + '...';
  6883. }
  6884. link.siblings('.fl-form-field-preview-text').html(previewText);
  6885. }
  6886. oldSettings = link.siblings('input').val().replace(/&#39;/g, "'");
  6887. if ( '' != oldSettings ) {
  6888. settings = $.extend( JSON.parse( oldSettings ), settings );
  6889. }
  6890. link.siblings('input').val(JSON.stringify(settings)).trigger('change');
  6891. FLBuilder._closeNestedSettings();
  6892. return true;
  6893. }
  6894. else {
  6895. FLBuilder._toggleSettingsTabErrors();
  6896. return false;
  6897. }
  6898. },
  6899. /* Layout Fields
  6900. ----------------------------------------------------------*/
  6901. /**
  6902. * Callback for when the item of a layout field is clicked.
  6903. *
  6904. * @since 1.0
  6905. * @access private
  6906. * @method _layoutFieldClicked
  6907. */
  6908. _layoutFieldClicked: function()
  6909. {
  6910. var option = $(this);
  6911. option.siblings().removeClass('fl-layout-field-option-selected');
  6912. option.addClass('fl-layout-field-option-selected');
  6913. option.siblings('input').val(option.attr('data-value'));
  6914. },
  6915. /* Link Fields
  6916. ----------------------------------------------------------*/
  6917. /**
  6918. * Initializes all link fields in a settings form.
  6919. *
  6920. * @since 1.3.9
  6921. * @access private
  6922. * @method _initLinkFields
  6923. */
  6924. _initLinkFields: function()
  6925. {
  6926. $('.fl-builder-settings:visible .fl-link-field').each(FLBuilder._initLinkField);
  6927. },
  6928. /**
  6929. * Initializes a single link field in a settings form.
  6930. *
  6931. * @since 1.3.9
  6932. * @access private
  6933. * @method _initLinkFields
  6934. */
  6935. _initLinkField: function()
  6936. {
  6937. var wrap = $(this),
  6938. searchInput = wrap.find('.fl-link-field-search-input');
  6939. searchInput.autoSuggest(FLBuilder._ajaxUrl({
  6940. 'fl_action' : 'fl_builder_autosuggest',
  6941. 'fl_as_action' : 'fl_as_links',
  6942. '_wpnonce' : FLBuilderConfig.ajaxNonce
  6943. }), {
  6944. asHtmlID : searchInput.attr('name'),
  6945. selectedItemProp : 'name',
  6946. searchObjProps : 'name',
  6947. minChars : 3,
  6948. keyDelay : 1000,
  6949. fadeOut : false,
  6950. usePlaceholder : true,
  6951. emptyText : FLBuilderStrings.noResultsFound,
  6952. showResultListWhenNoMatch : true,
  6953. queryParam : 'fl_as_query',
  6954. selectionLimit : 1,
  6955. afterSelectionAdd : FLBuilder._updateLinkField
  6956. });
  6957. },
  6958. /**
  6959. * Updates the value of a link field when a link has been
  6960. * selected from the auto suggest menu.
  6961. *
  6962. * @since 1.3.9
  6963. * @access private
  6964. * @method _updateLinkField
  6965. * @param {Object} element The auto suggest field.
  6966. * @param {Object} item The current selection.
  6967. * @param {Array} selections An array of selected values.
  6968. */
  6969. _updateLinkField: function(element, item, selections)
  6970. {
  6971. var wrap = element.closest('.fl-link-field'),
  6972. search = wrap.find('.fl-link-field-search'),
  6973. searchInput = wrap.find('.fl-link-field-search-input'),
  6974. field = wrap.find('.fl-link-field-input');
  6975. field.val(item.value).trigger('keyup');
  6976. searchInput.autoSuggest('remove', item.value);
  6977. search.hide();
  6978. },
  6979. /**
  6980. * Shows the auto suggest input for a link field.
  6981. *
  6982. * @since 1.3.9
  6983. * @access private
  6984. * @method _linkFieldSelectClicked
  6985. */
  6986. _linkFieldSelectClicked: function()
  6987. {
  6988. var $el = $(this).closest('.fl-link-field').find('.fl-link-field-search');
  6989. $el.show();
  6990. $el.find('input').focus();
  6991. },
  6992. /**
  6993. * Hides the auto suggest input for a link field.
  6994. *
  6995. * @since 1.3.9
  6996. * @access private
  6997. * @method _linkFieldSelectCancelClicked
  6998. */
  6999. _linkFieldSelectCancelClicked: function()
  7000. {
  7001. var $button = $(this);
  7002. $button.parent().hide();
  7003. $button.closest('.fl-link-field').find('input.fl-link-field-input').focus();
  7004. },
  7005. /* Font Fields
  7006. ----------------------------------------------------------*/
  7007. /**
  7008. * Initializes all font fields in a settings form.
  7009. *
  7010. * @since 1.6.3
  7011. * @access private
  7012. * @method _initFontFields
  7013. */
  7014. _initFontFields: function(){
  7015. $('.fl-builder-settings:visible .fl-font-field').each( FLBuilder._initFontField );
  7016. },
  7017. /**
  7018. * Initializes a single font field in a settings form.
  7019. *
  7020. * @since 1.6.3
  7021. * @access private
  7022. * @method _initFontFields
  7023. */
  7024. _initFontField: function(){
  7025. var wrap = $( this ),
  7026. value = wrap.attr( 'data-value' ),
  7027. font = wrap.find( '.fl-font-field-font' ),
  7028. weight = wrap.find( '.fl-font-field-weight' );
  7029. font.on( 'change', function(){
  7030. FLBuilder._getFontWeights( font );
  7031. } );
  7032. if ( value.indexOf( 'family' ) > -1 ) {
  7033. value = JSON.parse( value );
  7034. font.val( value.family );
  7035. font.trigger( 'change' );
  7036. if ( weight.find( 'option[value=' + value.weight + ']' ).length ) {
  7037. weight.val( value.weight );
  7038. }
  7039. }
  7040. },
  7041. /**
  7042. * Renders the correct weights list for a respective font.
  7043. *
  7044. * @since 1.6.3
  7045. * @acces private
  7046. * @method _getFontWeights
  7047. * @param {Object} currentFont The font field element.
  7048. */
  7049. _getFontWeights: function( currentFont ){
  7050. var selectWeight = currentFont.next( '.fl-font-field-weight' ),
  7051. font = currentFont.val(),
  7052. weightMap = {
  7053. 'default' : 'Default',
  7054. 'regular' : 'Regular',
  7055. '100': 'Thin 100',
  7056. '200': 'Extra-Light 200',
  7057. '300': 'Light 300',
  7058. '400': 'Normal 400',
  7059. '500': 'Medium 500',
  7060. '600': 'Semi-Bold 600',
  7061. '700': 'Bold 700',
  7062. '800': 'Extra-Bold 800',
  7063. '900': 'Ultra-Bold 900'
  7064. },
  7065. weights = {};
  7066. selectWeight.html('');
  7067. if ( 'undefined' != typeof FLBuilderFontFamilies.system[ font ] ) {
  7068. weights = FLBuilderFontFamilies.system[ font ].weights;
  7069. }
  7070. else if ( 'undefined' != typeof FLBuilderFontFamilies.google[ font ] ) {
  7071. weights = FLBuilderFontFamilies.google[ font ];
  7072. } else {
  7073. weights = FLBuilderFontFamilies.default[ font ];
  7074. }
  7075. $.each( weights, function( key, value ){
  7076. selectWeight.append( '<option value="' + value + '">' + weightMap[ value ] + '</option>' );
  7077. } );
  7078. },
  7079. /* Editor Fields
  7080. ----------------------------------------------------------*/
  7081. /**
  7082. * InitializeS TinyMCE when the builder is first loaded.
  7083. *
  7084. * @since 2.0
  7085. * @access private
  7086. * @method _initEditorFields
  7087. */
  7088. _initTinyMCE: function()
  7089. {
  7090. if ( tinymce.ui.FloatPanel ) {
  7091. tinymce.ui.FloatPanel.zIndex = 100100; // Fix zIndex issue in wp 4.8.1
  7092. }
  7093. $( '.fl-builder-hidden-editor' ).each( FLBuilder._initEditorField );
  7094. },
  7095. /**
  7096. * Initialize all TinyMCE editor fields.
  7097. *
  7098. * @since 1.10
  7099. * @access private
  7100. * @method _initEditorFields
  7101. */
  7102. _initEditorFields: function()
  7103. {
  7104. $( '.fl-builder-settings:visible .fl-editor-field' ).each( FLBuilder._initEditorField );
  7105. },
  7106. /**
  7107. * Initialize a single TinyMCE editor field.
  7108. *
  7109. * @since 2.0
  7110. * @method _initEditorField
  7111. */
  7112. _initEditorField: function()
  7113. {
  7114. var field = $( this ),
  7115. textarea = field.find( 'textarea' ),
  7116. name = field.attr( 'data-name' ),
  7117. editorId = 'flrich' + new Date().getTime() + '_' + name,
  7118. html = FLBuilderConfig.wp_editor,
  7119. config = tinyMCEPreInit,
  7120. buttons = Number( field.attr( 'data-buttons' ) ),
  7121. rows = field.attr( 'data-rows' ),
  7122. init = null,
  7123. wrap = null;
  7124. html = html.replace( /flbuildereditor/g , editorId );
  7125. config = JSON.parse( JSON.stringify( config ).replace( /flbuildereditor/g , editorId ) );
  7126. textarea.after( html ).remove();
  7127. $( 'textarea#' + editorId ).val( textarea.val() )
  7128. if ( undefined !== typeof tinymce && undefined !== config.mceInit[ editorId ] ) {
  7129. init = config.mceInit[ editorId ];
  7130. wrap = tinymce.$( '#wp-' + editorId + '-wrap' );
  7131. wrap.find( 'textarea' ).attr( 'rows', rows );
  7132. if ( ! buttons ) {
  7133. wrap.find( '.wp-media-buttons' ).remove();
  7134. }
  7135. if ( ( wrap.hasClass( 'tmce-active' ) || ! config.qtInit.hasOwnProperty( editorId ) ) && ! init.wp_skip_init ) {
  7136. tinymce.init( init );
  7137. }
  7138. }
  7139. if ( undefined !== typeof quicktags ) {
  7140. quicktags( config.qtInit[ editorId ] );
  7141. }
  7142. window.wpActiveEditor = editorId;
  7143. },
  7144. /**
  7145. * Reinitialize all TinyMCE editor fields.
  7146. *
  7147. * @since 2.0
  7148. * @access private
  7149. * @method _reinitEditorFields
  7150. */
  7151. _reinitEditorFields: function()
  7152. {
  7153. if ( ! $( '.fl-lightbox-resizable:visible' ).length ) {
  7154. return;
  7155. }
  7156. // Do this on a timeout so TinyMCE doesn't hold up other operations.
  7157. setTimeout( function() {
  7158. var i, id;
  7159. if ( 'undefined' === typeof tinymce ) {
  7160. return;
  7161. }
  7162. for ( i = tinymce.editors.length - 1; i > -1 ; i-- ) {
  7163. if ( ! tinymce.editors[ i ].inline ) {
  7164. id = tinymce.editors[ i ].id;
  7165. tinyMCE.execCommand( 'mceRemoveEditor', true, id );
  7166. tinyMCE.execCommand( 'mceAddEditor', true, id );
  7167. }
  7168. }
  7169. if ( FLBuilder.preview ) {
  7170. FLBuilder.preview._initDefaultFieldPreviews( $( '.fl-field[data-type="editor"]' ) );
  7171. }
  7172. }, 1 );
  7173. },
  7174. /**
  7175. * Destroy all TinyMCE editors.
  7176. *
  7177. * @since 1.10.8
  7178. * @method _destroyEditorFields
  7179. */
  7180. _destroyEditorFields: function()
  7181. {
  7182. var i, id;
  7183. if ( 'undefined' === typeof tinymce ) {
  7184. return;
  7185. }
  7186. for ( i = tinymce.editors.length - 1; i > -1 ; i-- ) {
  7187. if ( ! tinymce.editors[ i ].inline ) {
  7188. tinyMCE.execCommand( 'mceRemoveEditor', true, tinymce.editors[ i ].id );
  7189. }
  7190. }
  7191. $( '.wplink-autocomplete' ).remove();
  7192. $( '.ui-helper-hidden-accessible' ).remove();
  7193. },
  7194. /**
  7195. * Updates all editor fields within a settings form.
  7196. *
  7197. * @since 1.0
  7198. * @access private
  7199. * @method _updateEditorFields
  7200. */
  7201. _updateEditorFields: function()
  7202. {
  7203. var wpEditors = $('.fl-builder-settings:visible textarea.wp-editor-area');
  7204. wpEditors.each(FLBuilder._updateEditorField);
  7205. },
  7206. /**
  7207. * Updates a single editor field within a settings form.
  7208. * Creates a hidden textarea with the editor content so
  7209. * this field can be saved.
  7210. *
  7211. * @since 1.0
  7212. * @access private
  7213. * @method _updateEditorField
  7214. */
  7215. _updateEditorField: function()
  7216. {
  7217. var textarea = $( this ),
  7218. field = textarea.closest( '.fl-editor-field' ),
  7219. form = textarea.closest( '.fl-builder-settings' ),
  7220. wrap = textarea.closest( '.wp-editor-wrap' ),
  7221. id = textarea.attr( 'id' ),
  7222. setting = field.attr( 'data-name' ),
  7223. editor = typeof tinymce == 'undefined' ? false : tinymce.get( id ),
  7224. hidden = textarea.siblings( 'textarea[name="' + setting + '"]' ),
  7225. wpautop = field.data( 'wpautop' );
  7226. // Add a hidden textarea if we don't have one.
  7227. if ( 0 === hidden.length ) {
  7228. hidden = $( '<textarea name="' + setting + '"></textarea>' ).hide();
  7229. textarea.after( hidden );
  7230. }
  7231. // Save editor content.
  7232. if ( wpautop ) {
  7233. if ( editor && wrap.hasClass( 'tmce-active' ) ) {
  7234. hidden.val( editor.getContent() );
  7235. }
  7236. else if ( 'undefined' != typeof switchEditors ) {
  7237. hidden.val( switchEditors.wpautop( textarea.val() ) );
  7238. }
  7239. else {
  7240. hidden.val( textarea.val() );
  7241. }
  7242. }
  7243. else {
  7244. if ( editor && wrap.hasClass( 'tmce-active' ) ) {
  7245. editor.save();
  7246. }
  7247. hidden.val( textarea.val() );
  7248. }
  7249. },
  7250. /* Loop Settings Fields
  7251. ----------------------------------------------------------*/
  7252. /**
  7253. * Callback for the data source of loop settings changes.
  7254. *
  7255. * @since 1.10
  7256. * @access private
  7257. * @method _loopDataSourceChange
  7258. */
  7259. _loopDataSourceChange: function()
  7260. {
  7261. var val = $( this ).val();
  7262. $('.fl-loop-data-source').hide();
  7263. $('.fl-loop-data-source[data-source="' + val + '"]').show();
  7264. },
  7265. /**
  7266. * Callback for when the post type of a custom query changes.
  7267. *
  7268. * @since 1.2.3
  7269. * @access private
  7270. * @method _customQueryPostTypeChange
  7271. */
  7272. _customQueryPostTypeChange: function()
  7273. {
  7274. var val = $(this).val();
  7275. $('.fl-custom-query-filter').hide();
  7276. $('.fl-custom-query-' + val + '-filter').show();
  7277. },
  7278. /* Ordering Fields
  7279. ----------------------------------------------------------*/
  7280. /**
  7281. * Initializes all ordering fields in a settings form.
  7282. *
  7283. * @since 1.10
  7284. * @access private
  7285. * @method _initOrderingFields
  7286. */
  7287. _initOrderingFields: function()
  7288. {
  7289. $( '.fl-builder-settings:visible .fl-ordering-field-options' ).each( FLBuilder._initOrderingField );
  7290. },
  7291. /**
  7292. * Initializes a single ordering field in a settings form.
  7293. *
  7294. * @since 1.10
  7295. * @access private
  7296. * @method _initOrderingField
  7297. */
  7298. _initOrderingField: function()
  7299. {
  7300. $( this ).sortable( {
  7301. items: '.fl-ordering-field-option',
  7302. containment: 'parent',
  7303. tolerance: 'pointer',
  7304. stop: FLBuilder._updateOrderingField
  7305. } );
  7306. },
  7307. /**
  7308. * Updates an ordering field when dragging stops.
  7309. *
  7310. * @since 1.10
  7311. * @access private
  7312. * @method _updateOrderingField
  7313. * @param {Object} e The event object.
  7314. */
  7315. _updateOrderingField: function( e )
  7316. {
  7317. var options = $( e.target ),
  7318. input = options.siblings( 'input[type=hidden]' ),
  7319. value = [];
  7320. options.find( '.fl-ordering-field-option' ).each( function() {
  7321. value.push( $( this ).attr( 'data-key' ) );
  7322. } );
  7323. input.val( JSON.stringify( value ) ).trigger( 'change' );
  7324. },
  7325. /* Text Fields - Add Predefined Value Selector
  7326. ----------------------------------------------------------*/
  7327. /**
  7328. * Callback for when "add value" selectors for text fields changes.
  7329. *
  7330. * @since 1.6.5
  7331. * @access private
  7332. * @method _textFieldAddValueSelectChange
  7333. */
  7334. _textFieldAddValueSelectChange: function()
  7335. {
  7336. var dropdown = $( this ),
  7337. textField = $( 'input[name="' + dropdown.data( 'target' ) + '"]' ),
  7338. currentValue = textField.val(),
  7339. addingValue = dropdown.val(),
  7340. newValue = '';
  7341. // Adding selected value to target text field only once
  7342. if ( -1 == currentValue.indexOf( addingValue ) ) {
  7343. newValue = ( currentValue.trim() + ' ' + addingValue.trim() ).trim();
  7344. textField
  7345. .val( newValue )
  7346. .trigger( 'change' )
  7347. .trigger( 'keyup' );
  7348. }
  7349. // Resetting the selector
  7350. dropdown
  7351. .val( '' );
  7352. },
  7353. /* Number Fields
  7354. ----------------------------------------------------------*/
  7355. /**
  7356. * @since 2.0
  7357. * @access private
  7358. * @method _onNumberFieldFocus
  7359. */
  7360. _onNumberFieldFocus: function(e) {
  7361. var $input = $(e.currentTarget);
  7362. $input.addClass('mousetrap');
  7363. Mousetrap.bind('up', function() {
  7364. $input.attr('step', 1);
  7365. });
  7366. Mousetrap.bind('down', function() {
  7367. $input.attr('step', 1);
  7368. });
  7369. Mousetrap.bind('shift+up', function() {
  7370. $input.attr('step', 10);
  7371. });
  7372. Mousetrap.bind('shift+down', function() {
  7373. $input.attr('step', 10);
  7374. });
  7375. },
  7376. /**
  7377. * @since 2.0
  7378. * @access private
  7379. * @method _onNumberFieldBlur
  7380. */
  7381. _onNumberFieldBlur: function(e) {
  7382. var $input = $(e.currentTarget);
  7383. $input.attr('step', 1).removeClass('mousetrap');
  7384. },
  7385. /* Timezone Fields
  7386. ----------------------------------------------------------*/
  7387. /**
  7388. * @since 2.0
  7389. * @access private
  7390. * @method _initTimezoneFields
  7391. */
  7392. _initTimezoneFields: function() {
  7393. $( '.fl-builder-settings:visible .fl-field[data-type=timezone]' ).each( FLBuilder._initTimezoneField );
  7394. },
  7395. /**
  7396. * @since 2.0
  7397. * @access private
  7398. * @method _initTimezoneField
  7399. */
  7400. _initTimezoneField: function() {
  7401. var select = $( this ).find( 'select' ),
  7402. value = select.attr( 'data-value' );
  7403. select.find( 'option[value="' + value + '"]' ).attr( 'selected', 'selected' );
  7404. },
  7405. /* AJAX
  7406. ----------------------------------------------------------*/
  7407. /**
  7408. * Frontend AJAX for the builder interface.
  7409. *
  7410. * @since 1.0
  7411. * @method ajax
  7412. * @param {Object} data The data for the AJAX request.
  7413. * @param {Function} callback A function to call when the request completes.
  7414. */
  7415. ajax: function(data, callback)
  7416. {
  7417. var prop;
  7418. FLBuilder.triggerHook('didBeginAJAX', data );
  7419. // Undefined props don't get sent to the server, so make them null.
  7420. for ( prop in data ) {
  7421. if ( 'undefined' == typeof data[ prop ] ) {
  7422. data[ prop ] = null;
  7423. }
  7424. }
  7425. // Add the ajax nonce to the data.
  7426. data._wpnonce = FLBuilderConfig.ajaxNonce;
  7427. // Send the post id to the server.
  7428. data.post_id = FLBuilderConfig.postId;
  7429. // Tell the server that the builder is active.
  7430. data.fl_builder = 1;
  7431. // Append the builder namespace to the action.
  7432. data.fl_action = data.action;
  7433. // Prevent ModSecurity false positives if our fix is enabled.
  7434. if ( 'undefined' != typeof data.settings ) {
  7435. data.settings = FLBuilder._ajaxModSecFix( $.extend( true, {}, data.settings ) );
  7436. }
  7437. if ( 'undefined' != typeof data.node_settings ) {
  7438. data.node_settings = FLBuilder._ajaxModSecFix( $.extend( true, {}, data.node_settings ) );
  7439. }
  7440. // Store the data in a single variable to avoid conflicts.
  7441. data = { fl_builder_data: data };
  7442. // Do the ajax call.
  7443. return $.post(FLBuilder._ajaxUrl(), data, function(response) {
  7444. FLBuilder._ajaxComplete();
  7445. if(typeof callback !== 'undefined') {
  7446. callback.call(this, response);
  7447. }
  7448. FLBuilder.triggerHook('didCompleteAJAX', data );
  7449. });
  7450. },
  7451. /**
  7452. * Callback for when an AJAX request is complete.
  7453. *
  7454. * @since 1.0
  7455. * @access private
  7456. * @method _ajaxComplete
  7457. */
  7458. _ajaxComplete: function()
  7459. {
  7460. FLBuilder.hideAjaxLoader();
  7461. },
  7462. /**
  7463. * Returns a URL for an AJAX request.
  7464. *
  7465. * @since 1.0
  7466. * @access private
  7467. * @method _ajaxUrl
  7468. * @param {Object} params An object with key/value pairs for the AJAX query string.
  7469. * @return {String} The AJAX URL.
  7470. */
  7471. _ajaxUrl: function(params)
  7472. {
  7473. var url = window.location.href.split( '#' ).shift(),
  7474. param = null;
  7475. if(typeof params !== 'undefined') {
  7476. for(param in params) {
  7477. url += url.indexOf('?') > -1 ? '&' : '?';
  7478. url += param + '=' + params[param];
  7479. }
  7480. }
  7481. return url;
  7482. },
  7483. /**
  7484. * Shows the AJAX loading overlay.
  7485. *
  7486. * @since 1.0
  7487. * @method showAjaxLoader
  7488. */
  7489. showAjaxLoader: function()
  7490. {
  7491. if( 0 === $( '.fl-builder-lightbox-loading' ).length ) {
  7492. $( '.fl-builder-loading' ).show();
  7493. }
  7494. },
  7495. /**
  7496. * Hides the AJAX loading overlay.
  7497. *
  7498. * @since 1.0
  7499. * @method hideAjaxLoader
  7500. */
  7501. hideAjaxLoader: function()
  7502. {
  7503. $( '.fl-builder-loading' ).hide();
  7504. },
  7505. /**
  7506. * Fades a node when it is being loaded.
  7507. *
  7508. * @since 1.10
  7509. * @access private
  7510. * @param {String} nodeId
  7511. * @method _showNodeLoading
  7512. */
  7513. _showNodeLoading: function( nodeId )
  7514. {
  7515. var node = $( '.fl-node-' + nodeId );
  7516. node.addClass( 'fl-builder-node-loading' );
  7517. FLBuilder.triggerHook( 'didStartNodeLoading', node );
  7518. },
  7519. /**
  7520. * Brings a node back to 100% opacity when it's done loading.
  7521. *
  7522. * @since 2.0
  7523. * @access private
  7524. * @param {String} nodeId
  7525. * @method _hideNodeLoading
  7526. */
  7527. _hideNodeLoading: function( nodeId )
  7528. {
  7529. var node = $( '.fl-node-' + nodeId );
  7530. node.removeClass( 'fl-builder-node-loading' );
  7531. },
  7532. /**
  7533. * Inserts a placeholder in place of where a node will be
  7534. * that is currently loading.
  7535. *
  7536. * @since 1.10
  7537. * @access private
  7538. * @param {Object} parent
  7539. * @param {Number} position
  7540. * @method _showNodeLoadingPlaceholder
  7541. */
  7542. _showNodeLoadingPlaceholder: function( parent, position )
  7543. {
  7544. var placeholder = $( '<div class="fl-builder-node-loading-placeholder"></div>' );
  7545. // Get sibling rows.
  7546. if ( parent.hasClass( 'fl-builder-content' ) ) {
  7547. siblings = parent.find( ' > .fl-row' );
  7548. }
  7549. // Get sibling column groups.
  7550. else if ( parent.hasClass( 'fl-row-content' ) ) {
  7551. siblings = parent.find( ' > .fl-col-group' );
  7552. }
  7553. // Get sibling columns.
  7554. else if ( parent.hasClass( 'fl-col-group' ) ) {
  7555. parent.addClass( 'fl-col-group-has-child-loading' );
  7556. siblings = parent.find( ' > .fl-col' );
  7557. }
  7558. // Get sibling modules.
  7559. else {
  7560. siblings = parent.find( ' > .fl-col-group, > .fl-module' );
  7561. }
  7562. // Add the placeholder.
  7563. if ( 0 === siblings.length || siblings.length == position) {
  7564. parent.append( placeholder );
  7565. }
  7566. else {
  7567. siblings.eq( position ).before( placeholder );
  7568. }
  7569. },
  7570. /**
  7571. * Removes the node loading placeholder for a node.
  7572. *
  7573. * @since 1.10
  7574. * @access private
  7575. * @param {Object} node
  7576. * @method _removeNodeLoadingPlaceholder
  7577. */
  7578. _removeNodeLoadingPlaceholder: function( node )
  7579. {
  7580. var prev = node.prev( '.fl-builder-node-loading-placeholder' ),
  7581. next = node.next( '.fl-builder-node-loading-placeholder' );
  7582. if ( prev.length ) {
  7583. prev.remove();
  7584. } else {
  7585. next.remove();
  7586. }
  7587. },
  7588. /**
  7589. * Base64 encode settings to prevent ModSecurity false
  7590. * positives if our fix is enabled.
  7591. *
  7592. * @since 1.8.4
  7593. * @access private
  7594. * @method _ajaxModSecFix
  7595. */
  7596. _ajaxModSecFix: function( settings )
  7597. {
  7598. var prop;
  7599. if ( FLBuilderConfig.modSecFix && 'undefined' != typeof btoa ) {
  7600. if ( 'string' == typeof settings ) {
  7601. settings = FLBuilder._btoa( settings );
  7602. }
  7603. else {
  7604. for ( prop in settings ) {
  7605. if ( 'string' == typeof settings[ prop ] ) {
  7606. settings[ prop ] = FLBuilder._btoa( settings[ prop ] );
  7607. }
  7608. else if( 'object' == typeof settings[ prop ] ) {
  7609. settings[ prop ] = FLBuilder._ajaxModSecFix( settings[ prop ] );
  7610. }
  7611. }
  7612. }
  7613. }
  7614. return settings;
  7615. },
  7616. /**
  7617. * Helper function for _ajaxModSecFix
  7618. * btoa() does not handle utf8/16 characters
  7619. * See: https://stackoverflow.com/questions/30106476/using-javascripts-atob-to-decode-base64-doesnt-properly-decode-utf-8-strings
  7620. *
  7621. * @since 1.10.7
  7622. * @access private
  7623. * @method _btoa
  7624. */
  7625. _btoa: function(str) {
  7626. return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function(match, p1) {
  7627. return String.fromCharCode('0x' + p1);
  7628. }));
  7629. },
  7630. /**
  7631. * @since 1.10.8
  7632. * @access private
  7633. * @method _wpmedia_reset_errors
  7634. */
  7635. _wpmedia_reset_errors: function() {
  7636. $('.upload-error').remove()
  7637. $('.media-uploader-status' ).removeClass( 'errors' ).hide()
  7638. },
  7639. /* Lightboxes
  7640. ----------------------------------------------------------*/
  7641. /**
  7642. * Initializes the lightboxes for the builder interface.
  7643. *
  7644. * @since 1.0
  7645. * @access private
  7646. * @method _initLightboxes
  7647. */
  7648. _initLightboxes: function()
  7649. {
  7650. /* Main builder lightbox */
  7651. FLBuilder._lightbox = new FLLightbox({
  7652. className: 'fl-builder-lightbox fl-builder-settings-lightbox',
  7653. resizable: true
  7654. });
  7655. FLBuilder._lightbox.on('resized', FLBuilder._calculateSettingsTabsOverflow);
  7656. FLBuilder._lightbox.on('close', FLBuilder._lightboxClosed);
  7657. FLBuilder._lightbox.on('beforeCloseLightbox', FLBuilder._destroyEditorFields);
  7658. /* Actions lightbox */
  7659. FLBuilder._actionsLightbox = new FLLightbox({
  7660. className: 'fl-builder-actions-lightbox'
  7661. });
  7662. },
  7663. /**
  7664. * Shows the settings lightbox.
  7665. *
  7666. * @since 1.0
  7667. * @access private
  7668. * @method _showLightbox
  7669. */
  7670. _showLightbox: function()
  7671. {
  7672. FLBuilder._lightbox.open('<div class="fl-builder-lightbox-loading"></div>');
  7673. FLBuilder._initLightboxScrollbars();
  7674. },
  7675. /**
  7676. * Set the content for the settings lightbox.
  7677. *
  7678. * @since 1.0
  7679. * @access private
  7680. * @method _setLightboxContent
  7681. * @param {String} content The HTML content for the lightbox.
  7682. */
  7683. _setLightboxContent: function(content)
  7684. {
  7685. FLBuilder._lightbox.setContent(content);
  7686. },
  7687. /**
  7688. * Initializes the scrollbars for the settings lightbox.
  7689. *
  7690. * @since 1.0
  7691. * @access private
  7692. * @method _initLightboxScrollbars
  7693. */
  7694. _initLightboxScrollbars: function()
  7695. {
  7696. FLBuilder._initScrollbars();
  7697. FLBuilder._lightboxScrollbarTimeout = setTimeout(FLBuilder._initLightboxScrollbars, 500);
  7698. },
  7699. /**
  7700. * Callback to clean things up when the settings lightbox
  7701. * is closed.
  7702. *
  7703. * @since 1.0
  7704. * @access private
  7705. * @method _lightboxClosed
  7706. */
  7707. _lightboxClosed: function()
  7708. {
  7709. FLBuilder.triggerHook( 'settings-lightbox-closed' );
  7710. FLBuilder._lightbox.empty();
  7711. clearTimeout( FLBuilder._lightboxScrollbarTimeout );
  7712. },
  7713. /**
  7714. * Shows the actions lightbox.
  7715. *
  7716. * @since 1.0
  7717. * @access private
  7718. * @method _showActionsLightbox
  7719. * @param {Object} settings An object with settings for the lightbox buttons.
  7720. */
  7721. _showActionsLightbox: function(settings)
  7722. {
  7723. var template = wp.template( 'fl-actions-lightbox' );
  7724. // Allow extensions to modify the settings object.
  7725. FLBuilder.triggerHook( 'actions-lightbox-settings', settings );
  7726. // Open the lightbox.
  7727. FLBuilder._actionsLightbox.open( template( settings ) );
  7728. },
  7729. /* Alert Lightboxes
  7730. ----------------------------------------------------------*/
  7731. /**
  7732. * Shows the alert lightbox with a message.
  7733. *
  7734. * @since 1.0
  7735. * @method alert
  7736. * @param {String} message The message to show.
  7737. */
  7738. alert: function(message)
  7739. {
  7740. var alert = new FLLightbox({
  7741. className: 'fl-builder-alert-lightbox',
  7742. destroyOnClose: true
  7743. }),
  7744. template = wp.template( 'fl-alert-lightbox' );
  7745. alert.open( template( { message : message } ) );
  7746. },
  7747. /**
  7748. * Closes the alert lightbox when a child element is clicked.
  7749. *
  7750. * @since 1.0
  7751. * @access private
  7752. * @method _alertClose
  7753. */
  7754. _alertClose: function()
  7755. {
  7756. FLLightbox.closeParent(this);
  7757. },
  7758. /**
  7759. * Shows the confirm lightbox with a message.
  7760. *
  7761. * @since 1.10
  7762. * @method confirm
  7763. * @param {Object} o The config object that overrides the defaults.
  7764. */
  7765. confirm: function( o )
  7766. {
  7767. var defaults = {
  7768. message : '',
  7769. ok : function(){},
  7770. cancel : function(){},
  7771. strings : {
  7772. 'ok' : FLBuilderStrings.ok,
  7773. 'cancel' : FLBuilderStrings.cancel
  7774. }
  7775. },
  7776. config = $.extend( {}, defaults, ( 'undefined' == typeof o ? {} : o ) )
  7777. lightbox = new FLLightbox({
  7778. className: 'fl-builder-confirm-lightbox fl-builder-alert-lightbox',
  7779. destroyOnClose: true
  7780. }),
  7781. template = wp.template( 'fl-confirm-lightbox' );
  7782. lightbox.open( template( config ) );
  7783. lightbox._node.find( '.fl-builder-confirm-ok' ).on( 'click', config.ok );
  7784. lightbox._node.find( '.fl-builder-confirm-cancel' ).on( 'click', config.cancel );
  7785. },
  7786. /* Simple JS hooks similar to WordPress PHP hooks.
  7787. ----------------------------------------------------------*/
  7788. /**
  7789. * Trigger a hook.
  7790. *
  7791. * @since 1.8
  7792. * @method triggerHook
  7793. * @param {String} hook The hook to trigger.
  7794. * @param {Array} args An array of args to pass to the hook.
  7795. */
  7796. triggerHook: function( hook, args )
  7797. {
  7798. $( 'body' ).trigger( 'fl-builder.' + hook, args );
  7799. },
  7800. /**
  7801. * Add a hook.
  7802. *
  7803. * @since 1.8
  7804. * @method addHook
  7805. * @param {String} hook The hook to add.
  7806. * @param {Function} callback A function to call when the hook is triggered.
  7807. */
  7808. addHook: function( hook, callback )
  7809. {
  7810. $( 'body' ).on( 'fl-builder.' + hook, callback );
  7811. },
  7812. /**
  7813. * Remove a hook.
  7814. *
  7815. * @since 1.8
  7816. * @method removeHook
  7817. * @param {String} hook The hook to remove.
  7818. * @param {Function} callback The callback function to remove.
  7819. */
  7820. removeHook: function( hook, callback )
  7821. {
  7822. $( 'body' ).off( 'fl-builder.' + hook, callback );
  7823. },
  7824. /* Console Logging
  7825. ----------------------------------------------------------*/
  7826. /**
  7827. * Logs a message in the console if the console is available.
  7828. *
  7829. * @since 1.4.6
  7830. * @method log
  7831. * @param {String} message The message to log.
  7832. */
  7833. log: function( message )
  7834. {
  7835. if ( 'undefined' == typeof window.console || 'undefined' == typeof window.console.log ) {
  7836. return;
  7837. }
  7838. console.log( message );
  7839. },
  7840. /**
  7841. * Logs an error in the console if the console is available.
  7842. *
  7843. * @since 1.4.6
  7844. * @method logError
  7845. * @param {String} error The error to log.
  7846. */
  7847. logError: function( error )
  7848. {
  7849. var message = null;
  7850. if ( 'undefined' == typeof error ) {
  7851. return;
  7852. }
  7853. else if ( 'undefined' != typeof error.stack ) {
  7854. message = error.stack;
  7855. }
  7856. else if ( 'undefined' != typeof error.message ) {
  7857. message = error.message;
  7858. }
  7859. if ( message ) {
  7860. FLBuilder.log( '************************************************************************' );
  7861. FLBuilder.log( FLBuilderStrings.errorMessage );
  7862. FLBuilder.log( message );
  7863. FLBuilder.log( '************************************************************************' );
  7864. }
  7865. },
  7866. /**
  7867. * Logs a global error in the console if the console is available.
  7868. *
  7869. * @since 1.4.6
  7870. * @method logGlobalError
  7871. * @param {String} message
  7872. * @param {String} file
  7873. * @param {String} line
  7874. * @param {String} col
  7875. * @param {String} error
  7876. */
  7877. logGlobalError: function( message, file, line, col, error )
  7878. {
  7879. FLBuilder.log( '************************************************************************' );
  7880. FLBuilder.log( FLBuilderStrings.errorMessage );
  7881. FLBuilder.log( FLBuilderStrings.globalErrorMessage.replace( '{message}', message ).replace( '{line}', line ).replace( '{file}', file ) );
  7882. if ( 'undefined' != typeof error && 'undefined' != typeof error.stack ) {
  7883. FLBuilder.log( error.stack );
  7884. FLBuilder.log( '************************************************************************' );
  7885. }
  7886. },
  7887. };
  7888. /* Start the party!!! */
  7889. $(function(){
  7890. FLBuilder._init();
  7891. });
  7892. })(jQuery);