123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477 |
- /*!
- * jQuery.extendext 0.1.2
- *
- * Copyright 2014-2016 Damien "Mistic" Sorel (http://www.strangeplanet.fr)
- * Licensed under MIT (http://opensource.org/licenses/MIT)
- *
- * Based on jQuery.extend by jQuery Foundation, Inc. and other contributors
- */
-
- (function (root, factory) {
- if (typeof define === 'function' && define.amd) {
- define('jQuery.extendext', ['jquery'], factory);
- }
- else if (typeof module === 'object' && module.exports) {
- module.exports = factory(require('jquery'));
- }
- else {
- factory(root.jQuery);
- }
- }(this, function ($) {
- "use strict";
-
- $.extendext = function () {
- var options, name, src, copy, copyIsArray, clone,
- target = arguments[0] || {},
- i = 1,
- length = arguments.length,
- deep = false,
- arrayMode = 'default';
-
- // Handle a deep copy situation
- if (typeof target === "boolean") {
- deep = target;
-
- // Skip the boolean and the target
- target = arguments[i++] || {};
- }
-
- // Handle array mode parameter
- if (typeof target === "string") {
- arrayMode = target.toLowerCase();
- if (arrayMode !== 'concat' && arrayMode !== 'replace' && arrayMode !== 'extend') {
- arrayMode = 'default';
- }
-
- // Skip the string param
- target = arguments[i++] || {};
- }
-
- // Handle case when target is a string or something (possible in deep copy)
- if (typeof target !== "object" && !$.isFunction(target)) {
- target = {};
- }
-
- // Extend jQuery itself if only one argument is passed
- if (i === length) {
- target = this;
- i--;
- }
-
- for (; i < length; i++) {
- // Only deal with non-null/undefined values
- if ((options = arguments[i]) !== null) {
- // Special operations for arrays
- if ($.isArray(options) && arrayMode !== 'default') {
- clone = target && $.isArray(target) ? target : [];
-
- switch (arrayMode) {
- case 'concat':
- target = clone.concat($.extend(deep, [], options));
- break;
-
- case 'replace':
- target = $.extend(deep, [], options);
- break;
-
- case 'extend':
- options.forEach(function (e, i) {
- if (typeof e === 'object') {
- var type = $.isArray(e) ? [] : {};
- clone[i] = $.extendext(deep, arrayMode, clone[i] || type, e);
-
- } else if (clone.indexOf(e) === -1) {
- clone.push(e);
- }
- });
-
- target = clone;
- break;
- }
-
- } else {
- // Extend the base object
- for (name in options) {
- src = target[name];
- copy = options[name];
-
- // Prevent never-ending loop
- if (target === copy) {
- continue;
- }
-
- // Recurse if we're merging plain objects or arrays
- if (deep && copy && ( $.isPlainObject(copy) ||
- (copyIsArray = $.isArray(copy)) )) {
-
- if (copyIsArray) {
- copyIsArray = false;
- clone = src && $.isArray(src) ? src : [];
-
- } else {
- clone = src && $.isPlainObject(src) ? src : {};
- }
-
- // Never move original objects, clone them
- target[name] = $.extendext(deep, arrayMode, clone, copy);
-
- // Don't bring in undefined values
- } else if (copy !== undefined) {
- target[name] = copy;
- }
- }
- }
- }
- }
-
- // Return the modified object
- return target;
- };
- }));
-
- // doT.js
- // 2011-2014, Laura Doktorova, https://github.com/olado/doT
- // Licensed under the MIT license.
-
- (function () {
- "use strict";
-
- var doT = {
- name: "doT",
- version: "1.1.1",
- templateSettings: {
- evaluate: /\{\{([\s\S]+?(\}?)+)\}\}/g,
- interpolate: /\{\{=([\s\S]+?)\}\}/g,
- encode: /\{\{!([\s\S]+?)\}\}/g,
- use: /\{\{#([\s\S]+?)\}\}/g,
- useParams: /(^|[^\w$])def(?:\.|\[[\'\"])([\w$\.]+)(?:[\'\"]\])?\s*\:\s*([\w$\.]+|\"[^\"]+\"|\'[^\']+\'|\{[^\}]+\})/g,
- define: /\{\{##\s*([\w\.$]+)\s*(\:|=)([\s\S]+?)#\}\}/g,
- defineParams:/^\s*([\w$]+):([\s\S]+)/,
- conditional: /\{\{\?(\?)?\s*([\s\S]*?)\s*\}\}/g,
- iterate: /\{\{~\s*(?:\}\}|([\s\S]+?)\s*\:\s*([\w$]+)\s*(?:\:\s*([\w$]+))?\s*\}\})/g,
- varname: "it",
- strip: true,
- append: true,
- selfcontained: false,
- doNotSkipEncoded: false
- },
- template: undefined, //fn, compile template
- compile: undefined, //fn, for express
- log: true
- }, _globals;
-
- doT.encodeHTMLSource = function(doNotSkipEncoded) {
- var encodeHTMLRules = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'", "/": "/" },
- matchHTML = doNotSkipEncoded ? /[&<>"'\/]/g : /&(?!#?\w+;)|<|>|"|'|\//g;
- return function(code) {
- return code ? code.toString().replace(matchHTML, function(m) {return encodeHTMLRules[m] || m;}) : "";
- };
- };
-
- _globals = (function(){ return this || (0,eval)("this"); }());
-
- /* istanbul ignore else */
- if (typeof module !== "undefined" && module.exports) {
- module.exports = doT;
- } else if (typeof define === "function" && define.amd) {
- define('doT', function(){return doT;});
- } else {
- _globals.doT = doT;
- }
-
- var startend = {
- append: { start: "'+(", end: ")+'", startencode: "'+encodeHTML(" },
- split: { start: "';out+=(", end: ");out+='", startencode: "';out+=encodeHTML(" }
- }, skip = /$^/;
-
- function resolveDefs(c, block, def) {
- return ((typeof block === "string") ? block : block.toString())
- .replace(c.define || skip, function(m, code, assign, value) {
- if (code.indexOf("def.") === 0) {
- code = code.substring(4);
- }
- if (!(code in def)) {
- if (assign === ":") {
- if (c.defineParams) value.replace(c.defineParams, function(m, param, v) {
- def[code] = {arg: param, text: v};
- });
- if (!(code in def)) def[code]= value;
- } else {
- new Function("def", "def['"+code+"']=" + value)(def);
- }
- }
- return "";
- })
- .replace(c.use || skip, function(m, code) {
- if (c.useParams) code = code.replace(c.useParams, function(m, s, d, param) {
- if (def[d] && def[d].arg && param) {
- var rw = (d+":"+param).replace(/'|\\/g, "_");
- def.__exp = def.__exp || {};
- def.__exp[rw] = def[d].text.replace(new RegExp("(^|[^\\w$])" + def[d].arg + "([^\\w$])", "g"), "$1" + param + "$2");
- return s + "def.__exp['"+rw+"']";
- }
- });
- var v = new Function("def", "return " + code)(def);
- return v ? resolveDefs(c, v, def) : v;
- });
- }
-
- function unescape(code) {
- return code.replace(/\\('|\\)/g, "$1").replace(/[\r\t\n]/g, " ");
- }
-
- doT.template = function(tmpl, c, def) {
- c = c || doT.templateSettings;
- var cse = c.append ? startend.append : startend.split, needhtmlencode, sid = 0, indv,
- str = (c.use || c.define) ? resolveDefs(c, tmpl, def || {}) : tmpl;
-
- str = ("var out='" + (c.strip ? str.replace(/(^|\r|\n)\t* +| +\t*(\r|\n|$)/g," ")
- .replace(/\r|\n|\t|\/\*[\s\S]*?\*\//g,""): str)
- .replace(/'|\\/g, "\\$&")
- .replace(c.interpolate || skip, function(m, code) {
- return cse.start + unescape(code) + cse.end;
- })
- .replace(c.encode || skip, function(m, code) {
- needhtmlencode = true;
- return cse.startencode + unescape(code) + cse.end;
- })
- .replace(c.conditional || skip, function(m, elsecase, code) {
- return elsecase ?
- (code ? "';}else if(" + unescape(code) + "){out+='" : "';}else{out+='") :
- (code ? "';if(" + unescape(code) + "){out+='" : "';}out+='");
- })
- .replace(c.iterate || skip, function(m, iterate, vname, iname) {
- if (!iterate) return "';} } out+='";
- sid+=1; indv=iname || "i"+sid; iterate=unescape(iterate);
- return "';var arr"+sid+"="+iterate+";if(arr"+sid+"){var "+vname+","+indv+"=-1,l"+sid+"=arr"+sid+".length-1;while("+indv+"<l"+sid+"){"
- +vname+"=arr"+sid+"["+indv+"+=1];out+='";
- })
- .replace(c.evaluate || skip, function(m, code) {
- return "';" + unescape(code) + "out+='";
- })
- + "';return out;")
- .replace(/\n/g, "\\n").replace(/\t/g, '\\t').replace(/\r/g, "\\r")
- .replace(/(\s|;|\}|^|\{)out\+='';/g, '$1').replace(/\+''/g, "");
- //.replace(/(\s|;|\}|^|\{)out\+=''\+/g,'$1out+=');
-
- if (needhtmlencode) {
- if (!c.selfcontained && _globals && !_globals._encodeHTML) _globals._encodeHTML = doT.encodeHTMLSource(c.doNotSkipEncoded);
- str = "var encodeHTML = typeof _encodeHTML !== 'undefined' ? _encodeHTML : ("
- + doT.encodeHTMLSource.toString() + "(" + (c.doNotSkipEncoded || '') + "));"
- + str;
- }
- try {
- return new Function(c.varname, str);
- } catch (e) {
- /* istanbul ignore else */
- if (typeof console !== "undefined") console.log("Could not create a template function: " + str);
- throw e;
- }
- };
-
- doT.compile = function(tmpl, def) {
- return doT.template(tmpl, null, def);
- };
- }());
-
-
- /*!
- * jQuery QueryBuilder 2.5.2
- * Copyright 2014-2018 Damien "Mistic" Sorel (http://www.strangeplanet.fr)
- * Licensed under MIT (https://opensource.org/licenses/MIT)
- */
- (function (root, factory) {
- if (typeof define == 'function' && define.amd) {
- define('query-builder', ['jquery', 'dot/doT', 'jquery-extendext'], factory);
- }
- else if (typeof module === 'object' && module.exports) {
- module.exports = factory(require('jquery'), require('dot/doT'), require('jquery-extendext'));
- }
- else {
- factory(root.jQuery, root.doT);
- }
- }(this, function($, doT) {
- "use strict";
-
- /**
- * @typedef {object} Filter
- * @memberof QueryBuilder
- * @description See {@link http://querybuilder.js.org/index.html#filters}
- */
-
- /**
- * @typedef {object} Operator
- * @memberof QueryBuilder
- * @description See {@link http://querybuilder.js.org/index.html#operators}
- */
-
- /**
- * @param {jQuery} $el
- * @param {object} options - see {@link http://querybuilder.js.org/#options}
- * @constructor
- */
- var QueryBuilder = function($el, options) {
- $el[0].queryBuilder = this;
-
- /**
- * Element container
- * @member {jQuery}
- * @readonly
- */
- this.$el = $el;
-
- /**
- * Configuration object
- * @member {object}
- * @readonly
- */
- this.settings = $.extendext(true, 'replace', {}, QueryBuilder.DEFAULTS, options);
-
- /**
- * Internal model
- * @member {Model}
- * @readonly
- */
- this.model = new Model();
-
- /**
- * Internal status
- * @member {object}
- * @property {string} id - id of the container
- * @property {boolean} generated_id - if the container id has been generated
- * @property {int} group_id - current group id
- * @property {int} rule_id - current rule id
- * @property {boolean} has_optgroup - if filters have optgroups
- * @property {boolean} has_operator_optgroup - if operators have optgroups
- * @readonly
- * @private
- */
- this.status = {
- id: null,
- generated_id: false,
- group_id: 0,
- rule_id: 0,
- has_optgroup: false,
- has_operator_optgroup: false
- };
-
- /**
- * List of filters
- * @member {QueryBuilder.Filter[]}
- * @readonly
- */
- this.filters = this.settings.filters;
-
- /**
- * List of icons
- * @member {object.<string, string>}
- * @readonly
- */
- this.icons = this.settings.icons;
-
- /**
- * List of operators
- * @member {QueryBuilder.Operator[]}
- * @readonly
- */
- this.operators = this.settings.operators;
-
- /**
- * List of templates
- * @member {object.<string, function>}
- * @readonly
- */
- this.templates = this.settings.templates;
-
- /**
- * Plugins configuration
- * @member {object.<string, object>}
- * @readonly
- */
- this.plugins = this.settings.plugins;
-
- /**
- * Translations object
- * @member {object}
- * @readonly
- */
- this.lang = null;
-
- // translations : english << 'lang_code' << custom
- if (QueryBuilder.regional['en'] === undefined) {
- Utils.error('Config', '"i18n/en.js" not loaded.');
- }
- this.lang = $.extendext(true, 'replace', {}, QueryBuilder.regional['en'], QueryBuilder.regional[this.settings.lang_code], this.settings.lang);
-
- // "allow_groups" can be boolean or int
- if (this.settings.allow_groups === false) {
- this.settings.allow_groups = 0;
- }
- else if (this.settings.allow_groups === true) {
- this.settings.allow_groups = -1;
- }
-
- // init templates
- Object.keys(this.templates).forEach(function(tpl) {
- if (!this.templates[tpl]) {
- this.templates[tpl] = QueryBuilder.templates[tpl];
- }
- if (typeof this.templates[tpl] == 'string') {
- this.templates[tpl] = doT.template(this.templates[tpl]);
- }
- }, this);
-
- // ensure we have a container id
- if (!this.$el.attr('id')) {
- this.$el.attr('id', 'qb_' + Math.floor(Math.random() * 99999));
- this.status.generated_id = true;
- }
- this.status.id = this.$el.attr('id');
-
- // INIT
- this.$el.addClass('query-builder form-inline');
-
- this.filters = this.checkFilters(this.filters);
- this.operators = this.checkOperators(this.operators);
- this.bindEvents();
- this.initPlugins();
- };
-
- $.extend(QueryBuilder.prototype, /** @lends QueryBuilder.prototype */ {
- /**
- * Triggers an event on the builder container
- * @param {string} type
- * @returns {$.Event}
- */
- trigger: function(type) {
- var event = new $.Event(this._tojQueryEvent(type), {
- builder: this
- });
-
- this.$el.triggerHandler(event, Array.prototype.slice.call(arguments, 1));
-
- return event;
- },
-
- /**
- * Triggers an event on the builder container and returns the modified value
- * @param {string} type
- * @param {*} value
- * @returns {*}
- */
- change: function(type, value) {
- var event = new $.Event(this._tojQueryEvent(type, true), {
- builder: this,
- value: value
- });
-
- this.$el.triggerHandler(event, Array.prototype.slice.call(arguments, 2));
-
- return event.value;
- },
-
- /**
- * Attaches an event listener on the builder container
- * @param {string} type
- * @param {function} cb
- * @returns {QueryBuilder}
- */
- on: function(type, cb) {
- this.$el.on(this._tojQueryEvent(type), cb);
- return this;
- },
-
- /**
- * Removes an event listener from the builder container
- * @param {string} type
- * @param {function} [cb]
- * @returns {QueryBuilder}
- */
- off: function(type, cb) {
- this.$el.off(this._tojQueryEvent(type), cb);
- return this;
- },
-
- /**
- * Attaches an event listener called once on the builder container
- * @param {string} type
- * @param {function} cb
- * @returns {QueryBuilder}
- */
- once: function(type, cb) {
- this.$el.one(this._tojQueryEvent(type), cb);
- return this;
- },
-
- /**
- * Appends `.queryBuilder` and optionally `.filter` to the events names
- * @param {string} name
- * @param {boolean} [filter=false]
- * @returns {string}
- * @private
- */
- _tojQueryEvent: function(name, filter) {
- return name.split(' ').map(function(type) {
- return type + '.queryBuilder' + (filter ? '.filter' : '');
- }).join(' ');
- }
- });
-
-
- /**
- * Allowed types and their internal representation
- * @type {object.<string, string>}
- * @readonly
- * @private
- */
- QueryBuilder.types = {
- 'string': 'string',
- 'integer': 'number',
- 'double': 'number',
- 'date': 'datetime',
- 'time': 'datetime',
- 'datetime': 'datetime',
- 'boolean': 'boolean'
- };
-
- /**
- * Allowed inputs
- * @type {string[]}
- * @readonly
- * @private
- */
- QueryBuilder.inputs = [
- 'text',
- 'number',
- 'textarea',
- 'radio',
- 'checkbox',
- 'select'
- ];
-
- /**
- * Runtime modifiable options with `setOptions` method
- * @type {string[]}
- * @readonly
- * @private
- */
- QueryBuilder.modifiable_options = [
- 'display_errors',
- 'allow_groups',
- 'allow_empty',
- 'default_condition',
- 'default_filter'
- ];
-
- /**
- * CSS selectors for common components
- * @type {object.<string, string>}
- * @readonly
- */
- QueryBuilder.selectors = {
- group_container: '.rules-group-container',
- rule_container: '.rule-container',
- filter_container: '.rule-filter-container',
- operator_container: '.rule-operator-container',
- value_container: '.rule-value-container',
- error_container: '.error-container',
- condition_container: '.rules-group-header .group-conditions',
-
- rule_header: '.rule-header',
- group_header: '.rules-group-header',
- group_actions: '.group-actions',
- rule_actions: '.rule-actions',
-
- rules_list: '.rules-group-body>.rules-list',
-
- group_condition: '.rules-group-header [name$=_cond]',
- rule_filter: '.rule-filter-container [name$=_filter]',
- rule_operator: '.rule-operator-container [name$=_operator]',
- rule_value: '.rule-value-container [name*=_value_]',
-
- add_rule: '[data-add=rule]',
- delete_rule: '[data-delete=rule]',
- add_group: '[data-add=group]',
- delete_group: '[data-delete=group]'
- };
-
- /**
- * Template strings (see template.js)
- * @type {object.<string, string>}
- * @readonly
- */
- QueryBuilder.templates = {};
-
- /**
- * Localized strings (see i18n/)
- * @type {object.<string, object>}
- * @readonly
- */
- QueryBuilder.regional = {};
-
- /**
- * Default operators
- * @type {object.<string, object>}
- * @readonly
- */
- QueryBuilder.OPERATORS = {
- equal: { type: 'equal', nb_inputs: 1, multiple: false, apply_to: ['string', 'number', 'datetime', 'boolean'] },
- not_equal: { type: 'not_equal', nb_inputs: 1, multiple: false, apply_to: ['string', 'number', 'datetime', 'boolean'] },
- in: { type: 'in', nb_inputs: 1, multiple: true, apply_to: ['string', 'number', 'datetime'] },
- not_in: { type: 'not_in', nb_inputs: 1, multiple: true, apply_to: ['string', 'number', 'datetime'] },
- less: { type: 'less', nb_inputs: 1, multiple: false, apply_to: ['number', 'datetime'] },
- less_or_equal: { type: 'less_or_equal', nb_inputs: 1, multiple: false, apply_to: ['number', 'datetime'] },
- greater: { type: 'greater', nb_inputs: 1, multiple: false, apply_to: ['number', 'datetime'] },
- greater_or_equal: { type: 'greater_or_equal', nb_inputs: 1, multiple: false, apply_to: ['number', 'datetime'] },
- between: { type: 'between', nb_inputs: 2, multiple: false, apply_to: ['number', 'datetime'] },
- not_between: { type: 'not_between', nb_inputs: 2, multiple: false, apply_to: ['number', 'datetime'] },
- begins_with: { type: 'begins_with', nb_inputs: 1, multiple: false, apply_to: ['string'] },
- not_begins_with: { type: 'not_begins_with', nb_inputs: 1, multiple: false, apply_to: ['string'] },
- contains: { type: 'contains', nb_inputs: 1, multiple: false, apply_to: ['string'] },
- not_contains: { type: 'not_contains', nb_inputs: 1, multiple: false, apply_to: ['string'] },
- ends_with: { type: 'ends_with', nb_inputs: 1, multiple: false, apply_to: ['string'] },
- not_ends_with: { type: 'not_ends_with', nb_inputs: 1, multiple: false, apply_to: ['string'] },
- is_empty: { type: 'is_empty', nb_inputs: 0, multiple: false, apply_to: ['string'] },
- is_not_empty: { type: 'is_not_empty', nb_inputs: 0, multiple: false, apply_to: ['string'] },
- is_null: { type: 'is_null', nb_inputs: 0, multiple: false, apply_to: ['string', 'number', 'datetime', 'boolean'] },
- is_not_null: { type: 'is_not_null', nb_inputs: 0, multiple: false, apply_to: ['string', 'number', 'datetime', 'boolean'] }
- };
-
- /**
- * Default configuration
- * @type {object}
- * @readonly
- */
- QueryBuilder.DEFAULTS = {
- filters: [],
- plugins: [],
-
- sort_filters: false,
- display_errors: true,
- allow_groups: -1,
- allow_empty: false,
- conditions: ['AND', 'OR'],
- default_condition: 'AND',
- inputs_separator: ' , ',
- select_placeholder: '------',
- display_empty_filter: true,
- default_filter: null,
- optgroups: {},
-
- default_rule_flags: {
- filter_readonly: false,
- operator_readonly: false,
- value_readonly: false,
- no_delete: false
- },
-
- default_group_flags: {
- condition_readonly: false,
- no_add_rule: false,
- no_add_group: false,
- no_delete: false
- },
-
- templates: {
- group: null,
- rule: null,
- filterSelect: null,
- operatorSelect: null,
- ruleValueSelect: null
- },
-
- lang_code: 'en',
- lang: {},
-
- operators: [
- 'equal',
- 'not_equal',
- 'in',
- 'not_in',
- 'less',
- 'less_or_equal',
- 'greater',
- 'greater_or_equal',
- 'between',
- 'not_between',
- 'begins_with',
- 'not_begins_with',
- 'contains',
- 'not_contains',
- 'ends_with',
- 'not_ends_with',
- 'is_empty',
- 'is_not_empty',
- 'is_null',
- 'is_not_null'
- ],
-
- icons: {
- add_group: 'glyphicon glyphicon-plus-sign',
- add_rule: 'glyphicon glyphicon-plus',
- remove_group: 'glyphicon glyphicon-remove',
- remove_rule: 'glyphicon glyphicon-remove',
- error: 'glyphicon glyphicon-warning-sign'
- }
- };
-
-
- /**
- * @module plugins
- */
-
- /**
- * Definition of available plugins
- * @type {object.<String, object>}
- */
- QueryBuilder.plugins = {};
-
- /**
- * Gets or extends the default configuration
- * @param {object} [options] - new configuration
- * @returns {undefined|object} nothing or configuration object (copy)
- */
- QueryBuilder.defaults = function(options) {
- if (typeof options == 'object') {
- $.extendext(true, 'replace', QueryBuilder.DEFAULTS, options);
- }
- else if (typeof options == 'string') {
- if (typeof QueryBuilder.DEFAULTS[options] == 'object') {
- return $.extend(true, {}, QueryBuilder.DEFAULTS[options]);
- }
- else {
- return QueryBuilder.DEFAULTS[options];
- }
- }
- else {
- return $.extend(true, {}, QueryBuilder.DEFAULTS);
- }
- };
-
- /**
- * Registers a new plugin
- * @param {string} name
- * @param {function} fct - init function
- * @param {object} [def] - default options
- */
- QueryBuilder.define = function(name, fct, def) {
- QueryBuilder.plugins[name] = {
- fct: fct,
- def: def || {}
- };
- };
-
- /**
- * Adds new methods to QueryBuilder prototype
- * @param {object.<string, function>} methods
- */
- QueryBuilder.extend = function(methods) {
- $.extend(QueryBuilder.prototype, methods);
- };
-
- /**
- * Initializes plugins for an instance
- * @throws ConfigError
- * @private
- */
- QueryBuilder.prototype.initPlugins = function() {
- if (!this.plugins) {
- return;
- }
-
- if ($.isArray(this.plugins)) {
- var tmp = {};
- this.plugins.forEach(function(plugin) {
- tmp[plugin] = null;
- });
- this.plugins = tmp;
- }
-
- Object.keys(this.plugins).forEach(function(plugin) {
- if (plugin in QueryBuilder.plugins) {
- this.plugins[plugin] = $.extend(true, {},
- QueryBuilder.plugins[plugin].def,
- this.plugins[plugin] || {}
- );
-
- QueryBuilder.plugins[plugin].fct.call(this, this.plugins[plugin]);
- }
- else {
- Utils.error('Config', 'Unable to find plugin "{0}"', plugin);
- }
- }, this);
- };
-
- /**
- * Returns the config of a plugin, if the plugin is not loaded, returns the default config.
- * @param {string} name
- * @param {string} [property]
- * @throws ConfigError
- * @returns {*}
- */
- QueryBuilder.prototype.getPluginOptions = function(name, property) {
- var plugin;
- if (this.plugins && this.plugins[name]) {
- plugin = this.plugins[name];
- }
- else if (QueryBuilder.plugins[name]) {
- plugin = QueryBuilder.plugins[name].def;
- }
-
- if (plugin) {
- if (property) {
- return plugin[property];
- }
- else {
- return plugin;
- }
- }
- else {
- Utils.error('Config', 'Unable to find plugin "{0}"', name);
- }
- };
-
-
- /**
- * Final initialisation of the builder
- * @param {object} [rules]
- * @fires QueryBuilder.afterInit
- * @private
- */
- QueryBuilder.prototype.init = function(rules) {
- /**
- * When the initilization is done, just before creating the root group
- * @event afterInit
- * @memberof QueryBuilder
- */
- this.trigger('afterInit');
-
- if (rules) {
- this.setRules(rules);
- delete this.settings.rules;
- }
- else {
- this.setRoot(true);
- }
- };
-
- /**
- * Checks the configuration of each filter
- * @param {QueryBuilder.Filter[]} filters
- * @returns {QueryBuilder.Filter[]}
- * @throws ConfigError
- */
- QueryBuilder.prototype.checkFilters = function(filters) {
- var definedFilters = [];
-
- if (!filters || filters.length === 0) {
- Utils.error('Config', 'Missing filters list');
- }
-
- filters.forEach(function(filter, i) {
- if (!filter.id) {
- Utils.error('Config', 'Missing filter {0} id', i);
- }
- if (definedFilters.indexOf(filter.id) != -1) {
- Utils.error('Config', 'Filter "{0}" already defined', filter.id);
- }
- definedFilters.push(filter.id);
-
- if (!filter.type) {
- filter.type = 'string';
- }
- else if (!QueryBuilder.types[filter.type]) {
- Utils.error('Config', 'Invalid type "{0}"', filter.type);
- }
-
- if (!filter.input) {
- filter.input = QueryBuilder.types[filter.type] === 'number' ? 'number' : 'text';
- }
- else if (typeof filter.input != 'function' && QueryBuilder.inputs.indexOf(filter.input) == -1) {
- Utils.error('Config', 'Invalid input "{0}"', filter.input);
- }
-
- if (filter.operators) {
- filter.operators.forEach(function(operator) {
- if (typeof operator != 'string') {
- Utils.error('Config', 'Filter operators must be global operators types (string)');
- }
- });
- }
-
- if (!filter.field) {
- filter.field = filter.id;
- }
- if (!filter.label) {
- filter.label = filter.field;
- }
-
- if (!filter.optgroup) {
- filter.optgroup = null;
- }
- else {
- this.status.has_optgroup = true;
-
- // register optgroup if needed
- if (!this.settings.optgroups[filter.optgroup]) {
- this.settings.optgroups[filter.optgroup] = filter.optgroup;
- }
- }
-
- switch (filter.input) {
- case 'radio':
- case 'checkbox':
- if (!filter.values || filter.values.length < 1) {
- Utils.error('Config', 'Missing filter "{0}" values', filter.id);
- }
- break;
-
- case 'select':
- var cleanValues = [];
- filter.has_optgroup = false;
-
- Utils.iterateOptions(filter.values, function(value, label, optgroup) {
- cleanValues.push({
- value: value,
- label: label,
- optgroup: optgroup || null
- });
-
- if (optgroup) {
- filter.has_optgroup = true;
-
- // register optgroup if needed
- if (!this.settings.optgroups[optgroup]) {
- this.settings.optgroups[optgroup] = optgroup;
- }
- }
- }.bind(this));
-
- if (filter.has_optgroup) {
- filter.values = Utils.groupSort(cleanValues, 'optgroup');
- }
- else {
- filter.values = cleanValues;
- }
-
- if (filter.placeholder) {
- if (filter.placeholder_value === undefined) {
- filter.placeholder_value = -1;
- }
-
- filter.values.forEach(function(entry) {
- if (entry.value == filter.placeholder_value) {
- Utils.error('Config', 'Placeholder of filter "{0}" overlaps with one of its values', filter.id);
- }
- });
- }
- break;
- }
- }, this);
-
- if (this.settings.sort_filters) {
- if (typeof this.settings.sort_filters == 'function') {
- filters.sort(this.settings.sort_filters);
- }
- else {
- var self = this;
- filters.sort(function(a, b) {
- return self.translate(a.label).localeCompare(self.translate(b.label));
- });
- }
- }
-
- if (this.status.has_optgroup) {
- filters = Utils.groupSort(filters, 'optgroup');
- }
-
- return filters;
- };
-
- /**
- * Checks the configuration of each operator
- * @param {QueryBuilder.Operator[]} operators
- * @returns {QueryBuilder.Operator[]}
- * @throws ConfigError
- */
- QueryBuilder.prototype.checkOperators = function(operators) {
- var definedOperators = [];
-
- operators.forEach(function(operator, i) {
- if (typeof operator == 'string') {
- if (!QueryBuilder.OPERATORS[operator]) {
- Utils.error('Config', 'Unknown operator "{0}"', operator);
- }
-
- operators[i] = operator = $.extendext(true, 'replace', {}, QueryBuilder.OPERATORS[operator]);
- }
- else {
- if (!operator.type) {
- Utils.error('Config', 'Missing "type" for operator {0}', i);
- }
-
- if (QueryBuilder.OPERATORS[operator.type]) {
- operators[i] = operator = $.extendext(true, 'replace', {}, QueryBuilder.OPERATORS[operator.type], operator);
- }
-
- if (operator.nb_inputs === undefined || operator.apply_to === undefined) {
- Utils.error('Config', 'Missing "nb_inputs" and/or "apply_to" for operator "{0}"', operator.type);
- }
- }
-
- if (definedOperators.indexOf(operator.type) != -1) {
- Utils.error('Config', 'Operator "{0}" already defined', operator.type);
- }
- definedOperators.push(operator.type);
-
- if (!operator.optgroup) {
- operator.optgroup = null;
- }
- else {
- this.status.has_operator_optgroup = true;
-
- // register optgroup if needed
- if (!this.settings.optgroups[operator.optgroup]) {
- this.settings.optgroups[operator.optgroup] = operator.optgroup;
- }
- }
- }, this);
-
- if (this.status.has_operator_optgroup) {
- operators = Utils.groupSort(operators, 'optgroup');
- }
-
- return operators;
- };
-
- /**
- * Adds all events listeners to the builder
- * @private
- */
- QueryBuilder.prototype.bindEvents = function() {
- var self = this;
- var Selectors = QueryBuilder.selectors;
-
- // group condition change
- this.$el.on('change.queryBuilder', Selectors.group_condition, function() {
- if ($(this).is(':checked')) {
- var $group = $(this).closest(Selectors.group_container);
- self.getModel($group).condition = $(this).val();
- }
- });
-
- // rule filter change
- this.$el.on('change.queryBuilder', Selectors.rule_filter, function() {
- var $rule = $(this).closest(Selectors.rule_container);
- self.getModel($rule).filter = self.getFilterById($(this).val());
- });
-
- // rule operator change
- this.$el.on('change.queryBuilder', Selectors.rule_operator, function() {
- var $rule = $(this).closest(Selectors.rule_container);
- self.getModel($rule).operator = self.getOperatorByType($(this).val());
- });
-
- // add rule button
- this.$el.on('click.queryBuilder', Selectors.add_rule, function() {
- var $group = $(this).closest(Selectors.group_container);
- self.addRule(self.getModel($group));
- });
-
- // delete rule button
- this.$el.on('click.queryBuilder', Selectors.delete_rule, function() {
- var $rule = $(this).closest(Selectors.rule_container);
- self.deleteRule(self.getModel($rule));
- });
-
- if (this.settings.allow_groups !== 0) {
- // add group button
- this.$el.on('click.queryBuilder', Selectors.add_group, function() {
- var $group = $(this).closest(Selectors.group_container);
- self.addGroup(self.getModel($group));
- });
-
- // delete group button
- this.$el.on('click.queryBuilder', Selectors.delete_group, function() {
- var $group = $(this).closest(Selectors.group_container);
- self.deleteGroup(self.getModel($group));
- });
- }
-
- // model events
- this.model.on({
- 'drop': function(e, node) {
- node.$el.remove();
- self.refreshGroupsConditions();
- },
- 'add': function(e, parent, node, index) {
- if (index === 0) {
- node.$el.prependTo(parent.$el.find('>' + QueryBuilder.selectors.rules_list));
- }
- else {
- node.$el.insertAfter(parent.rules[index - 1].$el);
- }
- self.refreshGroupsConditions();
- },
- 'move': function(e, node, group, index) {
- node.$el.detach();
-
- if (index === 0) {
- node.$el.prependTo(group.$el.find('>' + QueryBuilder.selectors.rules_list));
- }
- else {
- node.$el.insertAfter(group.rules[index - 1].$el);
- }
- self.refreshGroupsConditions();
- },
- 'update': function(e, node, field, value, oldValue) {
- if (node instanceof Rule) {
- switch (field) {
- case 'error':
- self.updateError(node);
- break;
-
- case 'flags':
- self.applyRuleFlags(node);
- break;
-
- case 'filter':
- self.updateRuleFilter(node, oldValue);
- break;
-
- case 'operator':
- self.updateRuleOperator(node, oldValue);
- break;
-
- case 'value':
- self.updateRuleValue(node, oldValue);
- break;
- }
- }
- else {
- switch (field) {
- case 'error':
- self.updateError(node);
- break;
-
- case 'flags':
- self.applyGroupFlags(node);
- break;
-
- case 'condition':
- self.updateGroupCondition(node, oldValue);
- break;
- }
- }
- }
- });
- };
-
- /**
- * Creates the root group
- * @param {boolean} [addRule=true] - adds a default empty rule
- * @param {object} [data] - group custom data
- * @param {object} [flags] - flags to apply to the group
- * @returns {Group} root group
- * @fires QueryBuilder.afterAddGroup
- */
- QueryBuilder.prototype.setRoot = function(addRule, data, flags) {
- addRule = (addRule === undefined || addRule === true);
-
- var group_id = this.nextGroupId();
- var $group = $(this.getGroupTemplate(group_id, 1));
-
- this.$el.append($group);
- this.model.root = new Group(null, $group);
- this.model.root.model = this.model;
-
- this.model.root.data = data;
- this.model.root.flags = $.extend({}, this.settings.default_group_flags, flags);
- this.model.root.condition = this.settings.default_condition;
-
- this.trigger('afterAddGroup', this.model.root);
-
- if (addRule) {
- this.addRule(this.model.root);
- }
-
- return this.model.root;
- };
-
- /**
- * Adds a new group
- * @param {Group} parent
- * @param {boolean} [addRule=true] - adds a default empty rule
- * @param {object} [data] - group custom data
- * @param {object} [flags] - flags to apply to the group
- * @returns {Group}
- * @fires QueryBuilder.beforeAddGroup
- * @fires QueryBuilder.afterAddGroup
- */
- QueryBuilder.prototype.addGroup = function(parent, addRule, data, flags) {
- addRule = (addRule === undefined || addRule === true);
-
- var level = parent.level + 1;
-
- /**
- * Just before adding a group, can be prevented.
- * @event beforeAddGroup
- * @memberof QueryBuilder
- * @param {Group} parent
- * @param {boolean} addRule - if an empty rule will be added in the group
- * @param {int} level - nesting level of the group, 1 is the root group
- */
- var e = this.trigger('beforeAddGroup', parent, addRule, level);
- if (e.isDefaultPrevented()) {
- return null;
- }
-
- var group_id = this.nextGroupId();
- var $group = $(this.getGroupTemplate(group_id, level));
- var model = parent.addGroup($group);
-
- model.data = data;
- model.flags = $.extend({}, this.settings.default_group_flags, flags);
- model.condition = this.settings.default_condition;
-
- /**
- * Just after adding a group
- * @event afterAddGroup
- * @memberof QueryBuilder
- * @param {Group} group
- */
- this.trigger('afterAddGroup', model);
-
- /**
- * After any change in the rules
- * @event rulesChanged
- * @memberof QueryBuilder
- */
- this.trigger('rulesChanged');
-
- if (addRule) {
- this.addRule(model);
- }
-
- return model;
- };
-
- /**
- * Tries to delete a group. The group is not deleted if at least one rule is flagged `no_delete`.
- * @param {Group} group
- * @returns {boolean} if the group has been deleted
- * @fires QueryBuilder.beforeDeleteGroup
- * @fires QueryBuilder.afterDeleteGroup
- */
- QueryBuilder.prototype.deleteGroup = function(group) {
- if (group.isRoot()) {
- return false;
- }
-
- /**
- * Just before deleting a group, can be prevented
- * @event beforeDeleteGroup
- * @memberof QueryBuilder
- * @param {Group} parent
- */
- var e = this.trigger('beforeDeleteGroup', group);
- if (e.isDefaultPrevented()) {
- return false;
- }
-
- var del = true;
-
- group.each('reverse', function(rule) {
- del &= this.deleteRule(rule);
- }, function(group) {
- del &= this.deleteGroup(group);
- }, this);
-
- if (del) {
- group.drop();
-
- /**
- * Just after deleting a group
- * @event afterDeleteGroup
- * @memberof QueryBuilder
- */
- this.trigger('afterDeleteGroup');
-
- this.trigger('rulesChanged');
- }
-
- return del;
- };
-
- /**
- * Performs actions when a group's condition changes
- * @param {Group} group
- * @param {object} previousCondition
- * @fires QueryBuilder.afterUpdateGroupCondition
- * @private
- */
- QueryBuilder.prototype.updateGroupCondition = function(group, previousCondition) {
- group.$el.find('>' + QueryBuilder.selectors.group_condition).each(function() {
- var $this = $(this);
- $this.prop('checked', $this.val() === group.condition);
- $this.parent().toggleClass('active', $this.val() === group.condition);
- });
-
- /**
- * After the group condition has been modified
- * @event afterUpdateGroupCondition
- * @memberof QueryBuilder
- * @param {Group} group
- * @param {object} previousCondition
- */
- this.trigger('afterUpdateGroupCondition', group, previousCondition);
-
- this.trigger('rulesChanged');
- };
-
- /**
- * Updates the visibility of conditions based on number of rules inside each group
- * @private
- */
- QueryBuilder.prototype.refreshGroupsConditions = function() {
- (function walk(group) {
- if (!group.flags || (group.flags && !group.flags.condition_readonly)) {
- group.$el.find('>' + QueryBuilder.selectors.group_condition).prop('disabled', group.rules.length <= 1)
- .parent().toggleClass('disabled', group.rules.length <= 1);
- }
-
- group.each(null, function(group) {
- walk(group);
- }, this);
- }(this.model.root));
- };
-
- /**
- * Adds a new rule
- * @param {Group} parent
- * @param {object} [data] - rule custom data
- * @param {object} [flags] - flags to apply to the rule
- * @returns {Rule}
- * @fires QueryBuilder.beforeAddRule
- * @fires QueryBuilder.afterAddRule
- * @fires QueryBuilder.changer:getDefaultFilter
- */
- QueryBuilder.prototype.addRule = function(parent, data, flags) {
- /**
- * Just before adding a rule, can be prevented
- * @event beforeAddRule
- * @memberof QueryBuilder
- * @param {Group} parent
- */
- var e = this.trigger('beforeAddRule', parent);
- if (e.isDefaultPrevented()) {
- return null;
- }
-
- var rule_id = this.nextRuleId();
- var $rule = $(this.getRuleTemplate(rule_id));
- var model = parent.addRule($rule);
-
- model.data = data;
- model.flags = $.extend({}, this.settings.default_rule_flags, flags);
-
- /**
- * Just after adding a rule
- * @event afterAddRule
- * @memberof QueryBuilder
- * @param {Rule} rule
- */
- this.trigger('afterAddRule', model);
-
- this.trigger('rulesChanged');
-
- this.createRuleFilters(model);
-
- if (this.settings.default_filter || !this.settings.display_empty_filter) {
- /**
- * Modifies the default filter for a rule
- * @event changer:getDefaultFilter
- * @memberof QueryBuilder
- * @param {QueryBuilder.Filter} filter
- * @param {Rule} rule
- * @returns {QueryBuilder.Filter}
- */
- model.filter = this.change('getDefaultFilter',
- this.getFilterById(this.settings.default_filter || this.filters[0].id),
- model
- );
- }
-
- return model;
- };
-
- /**
- * Tries to delete a rule
- * @param {Rule} rule
- * @returns {boolean} if the rule has been deleted
- * @fires QueryBuilder.beforeDeleteRule
- * @fires QueryBuilder.afterDeleteRule
- */
- QueryBuilder.prototype.deleteRule = function(rule) {
- if (rule.flags.no_delete) {
- return false;
- }
-
- /**
- * Just before deleting a rule, can be prevented
- * @event beforeDeleteRule
- * @memberof QueryBuilder
- * @param {Rule} rule
- */
- var e = this.trigger('beforeDeleteRule', rule);
- if (e.isDefaultPrevented()) {
- return false;
- }
-
- rule.drop();
-
- /**
- * Just after deleting a rule
- * @event afterDeleteRule
- * @memberof QueryBuilder
- */
- this.trigger('afterDeleteRule');
-
- this.trigger('rulesChanged');
-
- return true;
- };
-
- /**
- * Creates the filters for a rule
- * @param {Rule} rule
- * @fires QueryBuilder.changer:getRuleFilters
- * @fires QueryBuilder.afterCreateRuleFilters
- * @private
- */
- QueryBuilder.prototype.createRuleFilters = function(rule) {
- /**
- * Modifies the list a filters available for a rule
- * @event changer:getRuleFilters
- * @memberof QueryBuilder
- * @param {QueryBuilder.Filter[]} filters
- * @param {Rule} rule
- * @returns {QueryBuilder.Filter[]}
- */
- var filters = this.change('getRuleFilters', this.filters, rule);
- var $filterSelect = $(this.getRuleFilterSelect(rule, filters));
-
- rule.$el.find(QueryBuilder.selectors.filter_container).html($filterSelect);
-
- /**
- * After creating the dropdown for filters
- * @event afterCreateRuleFilters
- * @memberof QueryBuilder
- * @param {Rule} rule
- */
- this.trigger('afterCreateRuleFilters', rule);
-
- this.applyRuleFlags(rule);
- };
-
- /**
- * Creates the operators for a rule and init the rule operator
- * @param {Rule} rule
- * @fires QueryBuilder.afterCreateRuleOperators
- * @private
- */
- QueryBuilder.prototype.createRuleOperators = function(rule) {
- var $operatorContainer = rule.$el.find(QueryBuilder.selectors.operator_container).empty();
-
- if (!rule.filter) {
- return;
- }
-
- var operators = this.getOperators(rule.filter);
- var $operatorSelect = $(this.getRuleOperatorSelect(rule, operators));
-
- $operatorContainer.html($operatorSelect);
-
- // set the operator without triggering update event
- if (rule.filter.default_operator) {
- rule.__.operator = this.getOperatorByType(rule.filter.default_operator);
- }
- else {
- rule.__.operator = operators[0];
- }
-
- rule.$el.find(QueryBuilder.selectors.rule_operator).val(rule.operator.type);
-
- /**
- * After creating the dropdown for operators
- * @event afterCreateRuleOperators
- * @memberof QueryBuilder
- * @param {Rule} rule
- * @param {QueryBuilder.Operator[]} operators - allowed operators for this rule
- */
- this.trigger('afterCreateRuleOperators', rule, operators);
-
- this.applyRuleFlags(rule);
- };
-
- /**
- * Creates the main input for a rule
- * @param {Rule} rule
- * @fires QueryBuilder.afterCreateRuleInput
- * @private
- */
- QueryBuilder.prototype.createRuleInput = function(rule) {
- var $valueContainer = rule.$el.find(QueryBuilder.selectors.value_container).empty();
-
- rule.__.value = undefined;
-
- if (!rule.filter || !rule.operator || rule.operator.nb_inputs === 0) {
- return;
- }
-
- var self = this;
- var $inputs = $();
- var filter = rule.filter;
-
- for (var i = 0; i < rule.operator.nb_inputs; i++) {
- var $ruleInput = $(this.getRuleInput(rule, i));
- if (i > 0) $valueContainer.append(this.settings.inputs_separator);
- $valueContainer.append($ruleInput);
- $inputs = $inputs.add($ruleInput);
- }
-
- $valueContainer.css('display', '');
-
- $inputs.on('change ' + (filter.input_event || ''), function() {
- if (!rule._updating_input) {
- rule._updating_value = true;
- rule.value = self.getRuleInputValue(rule);
- rule._updating_value = false;
- }
- });
-
- if (filter.plugin) {
- $inputs[filter.plugin](filter.plugin_config || {});
- }
-
- /**
- * After creating the input for a rule and initializing optional plugin
- * @event afterCreateRuleInput
- * @memberof QueryBuilder
- * @param {Rule} rule
- */
- this.trigger('afterCreateRuleInput', rule);
-
- if (filter.default_value !== undefined) {
- rule.value = filter.default_value;
- }
- else {
- rule._updating_value = true;
- rule.value = self.getRuleInputValue(rule);
- rule._updating_value = false;
- }
-
- this.applyRuleFlags(rule);
- };
-
- /**
- * Performs action when a rule's filter changes
- * @param {Rule} rule
- * @param {object} previousFilter
- * @fires QueryBuilder.afterUpdateRuleFilter
- * @private
- */
- QueryBuilder.prototype.updateRuleFilter = function(rule, previousFilter) {
- this.createRuleOperators(rule);
- this.createRuleInput(rule);
-
- rule.$el.find(QueryBuilder.selectors.rule_filter).val(rule.filter ? rule.filter.id : '-1');
-
- // clear rule data if the filter changed
- if (previousFilter && rule.filter && previousFilter.id !== rule.filter.id) {
- rule.data = undefined;
- }
-
- /**
- * After the filter has been updated and the operators and input re-created
- * @event afterUpdateRuleFilter
- * @memberof QueryBuilder
- * @param {Rule} rule
- * @param {object} previousFilter
- */
- this.trigger('afterUpdateRuleFilter', rule, previousFilter);
-
- this.trigger('rulesChanged');
- };
-
- /**
- * Performs actions when a rule's operator changes
- * @param {Rule} rule
- * @param {object} previousOperator
- * @fires QueryBuilder.afterUpdateRuleOperator
- * @private
- */
- QueryBuilder.prototype.updateRuleOperator = function(rule, previousOperator) {
- var $valueContainer = rule.$el.find(QueryBuilder.selectors.value_container);
-
- if (!rule.operator || rule.operator.nb_inputs === 0) {
- $valueContainer.hide();
-
- rule.__.value = undefined;
- }
- else {
- $valueContainer.css('display', '');
-
- if ($valueContainer.is(':empty') || !previousOperator ||
- rule.operator.nb_inputs !== previousOperator.nb_inputs ||
- rule.operator.optgroup !== previousOperator.optgroup
- ) {
- this.createRuleInput(rule);
- }
- }
-
- if (rule.operator) {
- rule.$el.find(QueryBuilder.selectors.rule_operator).val(rule.operator.type);
-
- // refresh value if the format changed for this operator
- rule.__.value = this.getRuleInputValue(rule);
- }
-
- /**
- * After the operator has been updated and the input optionally re-created
- * @event afterUpdateRuleOperator
- * @memberof QueryBuilder
- * @param {Rule} rule
- * @param {object} previousOperator
- */
- this.trigger('afterUpdateRuleOperator', rule, previousOperator);
-
- this.trigger('rulesChanged');
- };
-
- /**
- * Performs actions when rule's value changes
- * @param {Rule} rule
- * @param {object} previousValue
- * @fires QueryBuilder.afterUpdateRuleValue
- * @private
- */
- QueryBuilder.prototype.updateRuleValue = function(rule, previousValue) {
- if (!rule._updating_value) {
- this.setRuleInputValue(rule, rule.value);
- }
-
- /**
- * After the rule value has been modified
- * @event afterUpdateRuleValue
- * @memberof QueryBuilder
- * @param {Rule} rule
- * @param {*} previousValue
- */
- this.trigger('afterUpdateRuleValue', rule, previousValue);
-
- this.trigger('rulesChanged');
- };
-
- /**
- * Changes a rule's properties depending on its flags
- * @param {Rule} rule
- * @fires QueryBuilder.afterApplyRuleFlags
- * @private
- */
- QueryBuilder.prototype.applyRuleFlags = function(rule) {
- var flags = rule.flags;
- var Selectors = QueryBuilder.selectors;
-
- rule.$el.find(Selectors.rule_filter).prop('disabled', flags.filter_readonly);
- rule.$el.find(Selectors.rule_operator).prop('disabled', flags.operator_readonly);
- rule.$el.find(Selectors.rule_value).prop('disabled', flags.value_readonly);
-
- if (flags.no_delete) {
- rule.$el.find(Selectors.delete_rule).remove();
- }
-
- /**
- * After rule's flags has been applied
- * @event afterApplyRuleFlags
- * @memberof QueryBuilder
- * @param {Rule} rule
- */
- this.trigger('afterApplyRuleFlags', rule);
- };
-
- /**
- * Changes group's properties depending on its flags
- * @param {Group} group
- * @fires QueryBuilder.afterApplyGroupFlags
- * @private
- */
- QueryBuilder.prototype.applyGroupFlags = function(group) {
- var flags = group.flags;
- var Selectors = QueryBuilder.selectors;
-
- group.$el.find('>' + Selectors.group_condition).prop('disabled', flags.condition_readonly)
- .parent().toggleClass('readonly', flags.condition_readonly);
-
- if (flags.no_add_rule) {
- group.$el.find(Selectors.add_rule).remove();
- }
- if (flags.no_add_group) {
- group.$el.find(Selectors.add_group).remove();
- }
- if (flags.no_delete) {
- group.$el.find(Selectors.delete_group).remove();
- }
-
- /**
- * After group's flags has been applied
- * @event afterApplyGroupFlags
- * @memberof QueryBuilder
- * @param {Group} group
- */
- this.trigger('afterApplyGroupFlags', group);
- };
-
- /**
- * Clears all errors markers
- * @param {Node} [node] default is root Group
- */
- QueryBuilder.prototype.clearErrors = function(node) {
- node = node || this.model.root;
-
- if (!node) {
- return;
- }
-
- node.error = null;
-
- if (node instanceof Group) {
- node.each(function(rule) {
- rule.error = null;
- }, function(group) {
- this.clearErrors(group);
- }, this);
- }
- };
-
- /**
- * Adds/Removes error on a Rule or Group
- * @param {Node} node
- * @fires QueryBuilder.changer:displayError
- * @private
- */
- QueryBuilder.prototype.updateError = function(node) {
- if (this.settings.display_errors) {
- if (node.error === null) {
- node.$el.removeClass('has-error');
- }
- else {
- var errorMessage = this.translate('errors', node.error[0]);
- errorMessage = Utils.fmt(errorMessage, node.error.slice(1));
-
- /**
- * Modifies an error message before display
- * @event changer:displayError
- * @memberof QueryBuilder
- * @param {string} errorMessage - the error message (translated and formatted)
- * @param {array} error - the raw error array (error code and optional arguments)
- * @param {Node} node
- * @returns {string}
- */
- errorMessage = this.change('displayError', errorMessage, node.error, node);
-
- node.$el.addClass('has-error')
- .find(QueryBuilder.selectors.error_container).eq(0)
- .attr('title', errorMessage);
- }
- }
- };
-
- /**
- * Triggers a validation error event
- * @param {Node} node
- * @param {string|array} error
- * @param {*} value
- * @fires QueryBuilder.validationError
- * @private
- */
- QueryBuilder.prototype.triggerValidationError = function(node, error, value) {
- if (!$.isArray(error)) {
- error = [error];
- }
-
- /**
- * Fired when a validation error occurred, can be prevented
- * @event validationError
- * @memberof QueryBuilder
- * @param {Node} node
- * @param {string} error
- * @param {*} value
- */
- var e = this.trigger('validationError', node, error, value);
- if (!e.isDefaultPrevented()) {
- node.error = error;
- }
- };
-
-
- /**
- * Destroys the builder
- * @fires QueryBuilder.beforeDestroy
- */
- QueryBuilder.prototype.destroy = function() {
- /**
- * Before the {@link QueryBuilder#destroy} method
- * @event beforeDestroy
- * @memberof QueryBuilder
- */
- this.trigger('beforeDestroy');
-
- if (this.status.generated_id) {
- this.$el.removeAttr('id');
- }
-
- this.clear();
- this.model = null;
-
- this.$el
- .off('.queryBuilder')
- .removeClass('query-builder')
- .removeData('queryBuilder');
-
- delete this.$el[0].queryBuilder;
- };
-
- /**
- * Clear all rules and resets the root group
- * @fires QueryBuilder.beforeReset
- * @fires QueryBuilder.afterReset
- */
- QueryBuilder.prototype.reset = function() {
- /**
- * Before the {@link QueryBuilder#reset} method, can be prevented
- * @event beforeReset
- * @memberof QueryBuilder
- */
- var e = this.trigger('beforeReset');
- if (e.isDefaultPrevented()) {
- return;
- }
-
- this.status.group_id = 1;
- this.status.rule_id = 0;
-
- this.model.root.empty();
-
- this.model.root.data = undefined;
- this.model.root.flags = $.extend({}, this.settings.default_group_flags);
- this.model.root.condition = this.settings.default_condition;
-
- this.addRule(this.model.root);
-
- /**
- * After the {@link QueryBuilder#reset} method
- * @event afterReset
- * @memberof QueryBuilder
- */
- this.trigger('afterReset');
-
- this.trigger('rulesChanged');
- };
-
- /**
- * Clears all rules and removes the root group
- * @fires QueryBuilder.beforeClear
- * @fires QueryBuilder.afterClear
- */
- QueryBuilder.prototype.clear = function() {
- /**
- * Before the {@link QueryBuilder#clear} method, can be prevented
- * @event beforeClear
- * @memberof QueryBuilder
- */
- var e = this.trigger('beforeClear');
- if (e.isDefaultPrevented()) {
- return;
- }
-
- this.status.group_id = 0;
- this.status.rule_id = 0;
-
- if (this.model.root) {
- this.model.root.drop();
- this.model.root = null;
- }
-
- /**
- * After the {@link QueryBuilder#clear} method
- * @event afterClear
- * @memberof QueryBuilder
- */
- this.trigger('afterClear');
-
- this.trigger('rulesChanged');
- };
-
- /**
- * Modifies the builder configuration.<br>
- * Only options defined in QueryBuilder.modifiable_options are modifiable
- * @param {object} options
- */
- QueryBuilder.prototype.setOptions = function(options) {
- $.each(options, function(opt, value) {
- if (QueryBuilder.modifiable_options.indexOf(opt) !== -1) {
- this.settings[opt] = value;
- }
- }.bind(this));
- };
-
- /**
- * Returns the model associated to a DOM object, or the root model
- * @param {jQuery} [target]
- * @returns {Node}
- */
- QueryBuilder.prototype.getModel = function(target) {
- if (!target) {
- return this.model.root;
- }
- else if (target instanceof Node) {
- return target;
- }
- else {
- return $(target).data('queryBuilderModel');
- }
- };
-
- /**
- * Validates the whole builder
- * @param {object} [options]
- * @param {boolean} [options.skip_empty=false] - skips validating rules that have no filter selected
- * @returns {boolean}
- * @fires QueryBuilder.changer:validate
- */
- QueryBuilder.prototype.validate = function(options) {
- options = $.extend({
- skip_empty: false
- }, options);
-
- this.clearErrors();
-
- var self = this;
-
- var valid = (function parse(group) {
- var done = 0;
- var errors = 0;
-
- group.each(function(rule) {
- if (!rule.filter && options.skip_empty) {
- return;
- }
-
- if (!rule.filter) {
- self.triggerValidationError(rule, 'no_filter', null);
- errors++;
- return;
- }
-
- if (!rule.operator) {
- self.triggerValidationError(rule, 'no_operator', null);
- errors++;
- return;
- }
-
- if (rule.operator.nb_inputs !== 0) {
- var valid = self.validateValue(rule, rule.value);
-
- if (valid !== true) {
- self.triggerValidationError(rule, valid, rule.value);
- errors++;
- return;
- }
- }
-
- done++;
-
- }, function(group) {
- var res = parse(group);
- if (res === true) {
- done++;
- }
- else if (res === false) {
- errors++;
- }
- });
-
- if (errors > 0) {
- return false;
- }
- else if (done === 0 && !group.isRoot() && options.skip_empty) {
- return null;
- }
- else if (done === 0 && (!self.settings.allow_empty || !group.isRoot())) {
- self.triggerValidationError(group, 'empty_group', null);
- return false;
- }
-
- return true;
-
- }(this.model.root));
-
- /**
- * Modifies the result of the {@link QueryBuilder#validate} method
- * @event changer:validate
- * @memberof QueryBuilder
- * @param {boolean} valid
- * @returns {boolean}
- */
- return this.change('validate', valid);
- };
-
- /**
- * Gets an object representing current rules
- * @param {object} [options]
- * @param {boolean|string} [options.get_flags=false] - export flags, true: only changes from default flags or 'all'
- * @param {boolean} [options.allow_invalid=false] - returns rules even if they are invalid
- * @param {boolean} [options.skip_empty=false] - remove rules that have no filter selected
- * @returns {object}
- * @fires QueryBuilder.changer:ruleToJson
- * @fires QueryBuilder.changer:groupToJson
- * @fires QueryBuilder.changer:getRules
- */
- QueryBuilder.prototype.getRules = function(options) {
- options = $.extend({
- get_flags: false,
- allow_invalid: false,
- skip_empty: false
- }, options);
-
- var valid = this.validate(options);
- if (!valid && !options.allow_invalid) {
- return null;
- }
-
- var self = this;
-
- var out = (function parse(group) {
- var groupData = {
- condition: group.condition,
- rules: []
- };
-
- if (group.data) {
- groupData.data = $.extendext(true, 'replace', {}, group.data);
- }
-
- if (options.get_flags) {
- var flags = self.getGroupFlags(group.flags, options.get_flags === 'all');
- if (!$.isEmptyObject(flags)) {
- groupData.flags = flags;
- }
- }
-
- group.each(function(rule) {
- if (!rule.filter && options.skip_empty) {
- return;
- }
-
- var value = null;
- if (!rule.operator || rule.operator.nb_inputs !== 0) {
- value = rule.value;
- }
-
- var ruleData = {
- id: rule.filter ? rule.filter.id : null,
- field: rule.filter ? rule.filter.field : null,
- type: rule.filter ? rule.filter.type : null,
- input: rule.filter ? rule.filter.input : null,
- operator: rule.operator ? rule.operator.type : null,
- value: value
- };
-
- if (rule.filter && rule.filter.data || rule.data) {
- ruleData.data = $.extendext(true, 'replace', {}, rule.filter.data, rule.data);
- }
-
- if (options.get_flags) {
- var flags = self.getRuleFlags(rule.flags, options.get_flags === 'all');
- if (!$.isEmptyObject(flags)) {
- ruleData.flags = flags;
- }
- }
-
- /**
- * Modifies the JSON generated from a Rule object
- * @event changer:ruleToJson
- * @memberof QueryBuilder
- * @param {object} json
- * @param {Rule} rule
- * @returns {object}
- */
- groupData.rules.push(self.change('ruleToJson', ruleData, rule));
-
- }, function(model) {
- var data = parse(model);
- if (data.rules.length !== 0 || !options.skip_empty) {
- groupData.rules.push(data);
- }
- }, this);
-
- /**
- * Modifies the JSON generated from a Group object
- * @event changer:groupToJson
- * @memberof QueryBuilder
- * @param {object} json
- * @param {Group} group
- * @returns {object}
- */
- return self.change('groupToJson', groupData, group);
-
- }(this.model.root));
-
- out.valid = valid;
-
- /**
- * Modifies the result of the {@link QueryBuilder#getRules} method
- * @event changer:getRules
- * @memberof QueryBuilder
- * @param {object} json
- * @returns {object}
- */
- return this.change('getRules', out);
- };
-
- /**
- * Sets rules from object
- * @param {object} data
- * @param {object} [options]
- * @param {boolean} [options.allow_invalid=false] - silent-fail if the data are invalid
- * @throws RulesError, UndefinedConditionError
- * @fires QueryBuilder.changer:setRules
- * @fires QueryBuilder.changer:jsonToRule
- * @fires QueryBuilder.changer:jsonToGroup
- * @fires QueryBuilder.afterSetRules
- */
- QueryBuilder.prototype.setRules = function(data, options) {
- options = $.extend({
- allow_invalid: false
- }, options);
-
- if ($.isArray(data)) {
- data = {
- condition: this.settings.default_condition,
- rules: data
- };
- }
-
- if (!data || !data.rules || (data.rules.length === 0 && !this.settings.allow_empty)) {
- Utils.error('RulesParse', 'Incorrect data object passed');
- }
-
- this.clear();
- this.setRoot(false, data.data, this.parseGroupFlags(data));
-
- /**
- * Modifies data before the {@link QueryBuilder#setRules} method
- * @event changer:setRules
- * @memberof QueryBuilder
- * @param {object} json
- * @param {object} options
- * @returns {object}
- */
- data = this.change('setRules', data, options);
-
- var self = this;
-
- (function add(data, group) {
- if (group === null) {
- return;
- }
-
- if (data.condition === undefined) {
- data.condition = self.settings.default_condition;
- }
- else if (self.settings.conditions.indexOf(data.condition) == -1) {
- Utils.error(!options.allow_invalid, 'UndefinedCondition', 'Invalid condition "{0}"', data.condition);
- data.condition = self.settings.default_condition;
- }
-
- group.condition = data.condition;
-
- data.rules.forEach(function(item) {
- var model;
-
- if (item.rules !== undefined) {
- if (self.settings.allow_groups !== -1 && self.settings.allow_groups < group.level) {
- Utils.error(!options.allow_invalid, 'RulesParse', 'No more than {0} groups are allowed', self.settings.allow_groups);
- self.reset();
- }
- else {
- model = self.addGroup(group, false, item.data, self.parseGroupFlags(item));
- if (model === null) {
- return;
- }
-
- add(item, model);
- }
- }
- else {
- if (!item.empty) {
- if (item.id === undefined) {
- Utils.error(!options.allow_invalid, 'RulesParse', 'Missing rule field id');
- item.empty = true;
- }
- if (item.operator === undefined) {
- item.operator = 'equal';
- }
- }
-
- model = self.addRule(group, item.data, self.parseRuleFlags(item));
- if (model === null) {
- return;
- }
-
- if (!item.empty) {
- model.filter = self.getFilterById(item.id, !options.allow_invalid);
- }
-
- if (model.filter) {
- model.operator = self.getOperatorByType(item.operator, !options.allow_invalid);
-
- if (!model.operator) {
- model.operator = self.getOperators(model.filter)[0];
- }
- }
-
- if (model.operator && model.operator.nb_inputs !== 0) {
- if (item.value !== undefined) {
- model.value = item.value;
- }
- else if (model.filter.default_value !== undefined) {
- model.value = model.filter.default_value;
- }
- }
-
- /**
- * Modifies the Rule object generated from the JSON
- * @event changer:jsonToRule
- * @memberof QueryBuilder
- * @param {Rule} rule
- * @param {object} json
- * @returns {Rule} the same rule
- */
- if (self.change('jsonToRule', model, item) != model) {
- Utils.error('RulesParse', 'Plugin tried to change rule reference');
- }
- }
- });
-
- /**
- * Modifies the Group object generated from the JSON
- * @event changer:jsonToGroup
- * @memberof QueryBuilder
- * @param {Group} group
- * @param {object} json
- * @returns {Group} the same group
- */
- if (self.change('jsonToGroup', group, data) != group) {
- Utils.error('RulesParse', 'Plugin tried to change group reference');
- }
-
- }(data, this.model.root));
-
- /**
- * After the {@link QueryBuilder#setRules} method
- * @event afterSetRules
- * @memberof QueryBuilder
- */
- this.trigger('afterSetRules');
- };
-
-
- /**
- * Performs value validation
- * @param {Rule} rule
- * @param {string|string[]} value
- * @returns {array|boolean} true or error array
- * @fires QueryBuilder.changer:validateValue
- */
- QueryBuilder.prototype.validateValue = function(rule, value) {
- var validation = rule.filter.validation || {};
- var result = true;
-
- if (validation.callback) {
- result = validation.callback.call(this, value, rule);
- }
- else {
- result = this._validateValue(rule, value);
- }
-
- /**
- * Modifies the result of the rule validation method
- * @event changer:validateValue
- * @memberof QueryBuilder
- * @param {array|boolean} result - true or an error array
- * @param {*} value
- * @param {Rule} rule
- * @returns {array|boolean}
- */
- return this.change('validateValue', result, value, rule);
- };
-
- /**
- * Default validation function
- * @param {Rule} rule
- * @param {string|string[]} value
- * @returns {array|boolean} true or error array
- * @throws ConfigError
- * @private
- */
- QueryBuilder.prototype._validateValue = function(rule, value) {
- var filter = rule.filter;
- var operator = rule.operator;
- var validation = filter.validation || {};
- var result = true;
- var tmp, tempValue;
-
- if (rule.operator.nb_inputs === 1) {
- value = [value];
- }
-
- for (var i = 0; i < operator.nb_inputs; i++) {
- if (!operator.multiple && $.isArray(value[i]) && value[i].length > 1) {
- result = ['operator_not_multiple', operator.type, this.translate('operators', operator.type)];
- break;
- }
-
- switch (filter.input) {
- case 'radio':
- if (value[i] === undefined || value[i].length === 0) {
- if (!validation.allow_empty_value) {
- result = ['radio_empty'];
- }
- break;
- }
- break;
-
- case 'checkbox':
- if (value[i] === undefined || value[i].length === 0) {
- if (!validation.allow_empty_value) {
- result = ['checkbox_empty'];
- }
- break;
- }
- break;
-
- case 'select':
- if (value[i] === undefined || value[i].length === 0 || (filter.placeholder && value[i] == filter.placeholder_value)) {
- if (!validation.allow_empty_value) {
- result = ['select_empty'];
- }
- break;
- }
- break;
-
- default:
- tempValue = $.isArray(value[i]) ? value[i] : [value[i]];
-
- for (var j = 0; j < tempValue.length; j++) {
- switch (QueryBuilder.types[filter.type]) {
- case 'string':
- if (tempValue[j] === undefined || tempValue[j].length === 0) {
- if (!validation.allow_empty_value) {
- result = ['string_empty'];
- }
- break;
- }
- if (validation.min !== undefined) {
- if (tempValue[j].length < parseInt(validation.min)) {
- result = [this.getValidationMessage(validation, 'min', 'string_exceed_min_length'), validation.min];
- break;
- }
- }
- if (validation.max !== undefined) {
- if (tempValue[j].length > parseInt(validation.max)) {
- result = [this.getValidationMessage(validation, 'max', 'string_exceed_max_length'), validation.max];
- break;
- }
- }
- if (validation.format) {
- if (typeof validation.format == 'string') {
- validation.format = new RegExp(validation.format);
- }
- if (!validation.format.test(tempValue[j])) {
- result = [this.getValidationMessage(validation, 'format', 'string_invalid_format'), validation.format];
- break;
- }
- }
- break;
-
- case 'number':
- if (tempValue[j] === undefined || tempValue[j].length === 0) {
- if (!validation.allow_empty_value) {
- result = ['number_nan'];
- }
- break;
- }
- if (isNaN(tempValue[j])) {
- result = ['number_nan'];
- break;
- }
- if (filter.type == 'integer') {
- if (parseInt(tempValue[j]) != tempValue[j]) {
- result = ['number_not_integer'];
- break;
- }
- }
- else {
- if (parseFloat(tempValue[j]) != tempValue[j]) {
- result = ['number_not_double'];
- break;
- }
- }
- if (validation.min !== undefined) {
- if (tempValue[j] < parseFloat(validation.min)) {
- result = [this.getValidationMessage(validation, 'min', 'number_exceed_min'), validation.min];
- break;
- }
- }
- if (validation.max !== undefined) {
- if (tempValue[j] > parseFloat(validation.max)) {
- result = [this.getValidationMessage(validation, 'max', 'number_exceed_max'), validation.max];
- break;
- }
- }
- if (validation.step !== undefined && validation.step !== 'any') {
- var v = (tempValue[j] / validation.step).toPrecision(14);
- if (parseInt(v) != v) {
- result = [this.getValidationMessage(validation, 'step', 'number_wrong_step'), validation.step];
- break;
- }
- }
- break;
-
- case 'datetime':
- if (tempValue[j] === undefined || tempValue[j].length === 0) {
- if (!validation.allow_empty_value) {
- result = ['datetime_empty'];
- }
- break;
- }
-
- // we need MomentJS
- if (validation.format) {
- if (!('moment' in window)) {
- Utils.error('MissingLibrary', 'MomentJS is required for Date/Time validation. Get it here http://momentjs.com');
- }
-
- var datetime = moment(tempValue[j], validation.format);
- if (!datetime.isValid()) {
- result = [this.getValidationMessage(validation, 'format', 'datetime_invalid'), validation.format];
- break;
- }
- else {
- if (validation.min) {
- if (datetime < moment(validation.min, validation.format)) {
- result = [this.getValidationMessage(validation, 'min', 'datetime_exceed_min'), validation.min];
- break;
- }
- }
- if (validation.max) {
- if (datetime > moment(validation.max, validation.format)) {
- result = [this.getValidationMessage(validation, 'max', 'datetime_exceed_max'), validation.max];
- break;
- }
- }
- }
- }
- break;
-
- case 'boolean':
- if (tempValue[j] === undefined || tempValue[j].length === 0) {
- if (!validation.allow_empty_value) {
- result = ['boolean_not_valid'];
- }
- break;
- }
- tmp = ('' + tempValue[j]).trim().toLowerCase();
- if (tmp !== 'true' && tmp !== 'false' && tmp !== '1' && tmp !== '0' && tempValue[j] !== 1 && tempValue[j] !== 0) {
- result = ['boolean_not_valid'];
- break;
- }
- }
-
- if (result !== true) {
- break;
- }
- }
- }
-
- if (result !== true) {
- break;
- }
- }
-
- if ((rule.operator.type === 'between' || rule.operator.type === 'not_between') && value.length === 2) {
- switch (QueryBuilder.types[filter.type]) {
- case 'number':
- if (value[0] > value[1]) {
- result = ['number_between_invalid', value[0], value[1]];
- }
- break;
-
- case 'datetime':
- // we need MomentJS
- if (validation.format) {
- if (!('moment' in window)) {
- Utils.error('MissingLibrary', 'MomentJS is required for Date/Time validation. Get it here http://momentjs.com');
- }
-
- if (moment(value[0], validation.format).isAfter(moment(value[1], validation.format))) {
- result = ['datetime_between_invalid', value[0], value[1]];
- }
- }
- break;
- }
- }
-
- return result;
- };
-
- /**
- * Returns an incremented group ID
- * @returns {string}
- * @private
- */
- QueryBuilder.prototype.nextGroupId = function() {
- return this.status.id + '_group_' + (this.status.group_id++);
- };
-
- /**
- * Returns an incremented rule ID
- * @returns {string}
- * @private
- */
- QueryBuilder.prototype.nextRuleId = function() {
- return this.status.id + '_rule_' + (this.status.rule_id++);
- };
-
- /**
- * Returns the operators for a filter
- * @param {string|object} filter - filter id or filter object
- * @returns {object[]}
- * @fires QueryBuilder.changer:getOperators
- * @private
- */
- QueryBuilder.prototype.getOperators = function(filter) {
- if (typeof filter == 'string') {
- filter = this.getFilterById(filter);
- }
-
- var result = [];
-
- for (var i = 0, l = this.operators.length; i < l; i++) {
- // filter operators check
- if (filter.operators) {
- if (filter.operators.indexOf(this.operators[i].type) == -1) {
- continue;
- }
- }
- // type check
- else if (this.operators[i].apply_to.indexOf(QueryBuilder.types[filter.type]) == -1) {
- continue;
- }
-
- result.push(this.operators[i]);
- }
-
- // keep sort order defined for the filter
- if (filter.operators) {
- result.sort(function(a, b) {
- return filter.operators.indexOf(a.type) - filter.operators.indexOf(b.type);
- });
- }
-
- /**
- * Modifies the operators available for a filter
- * @event changer:getOperators
- * @memberof QueryBuilder
- * @param {QueryBuilder.Operator[]} operators
- * @param {QueryBuilder.Filter} filter
- * @returns {QueryBuilder.Operator[]}
- */
- return this.change('getOperators', result, filter);
- };
-
- /**
- * Returns a particular filter by its id
- * @param {string} id
- * @param {boolean} [doThrow=true]
- * @returns {object|null}
- * @throws UndefinedFilterError
- * @private
- */
- QueryBuilder.prototype.getFilterById = function(id, doThrow) {
- if (id == '-1') {
- return null;
- }
-
- for (var i = 0, l = this.filters.length; i < l; i++) {
- if (this.filters[i].id == id) {
- return this.filters[i];
- }
- }
-
- Utils.error(doThrow !== false, 'UndefinedFilter', 'Undefined filter "{0}"', id);
-
- return null;
- };
-
- /**
- * Returns a particular operator by its type
- * @param {string} type
- * @param {boolean} [doThrow=true]
- * @returns {object|null}
- * @throws UndefinedOperatorError
- * @private
- */
- QueryBuilder.prototype.getOperatorByType = function(type, doThrow) {
- if (type == '-1') {
- return null;
- }
-
- for (var i = 0, l = this.operators.length; i < l; i++) {
- if (this.operators[i].type == type) {
- return this.operators[i];
- }
- }
-
- Utils.error(doThrow !== false, 'UndefinedOperator', 'Undefined operator "{0}"', type);
-
- return null;
- };
-
- /**
- * Returns rule's current input value
- * @param {Rule} rule
- * @returns {*}
- * @fires QueryBuilder.changer:getRuleValue
- * @private
- */
- QueryBuilder.prototype.getRuleInputValue = function(rule) {
- var filter = rule.filter;
- var operator = rule.operator;
- var value = [];
-
- if (filter.valueGetter) {
- value = filter.valueGetter.call(this, rule);
- }
- else {
- var $value = rule.$el.find(QueryBuilder.selectors.value_container);
-
- for (var i = 0; i < operator.nb_inputs; i++) {
- var name = Utils.escapeElementId(rule.id + '_value_' + i);
- var tmp;
-
- switch (filter.input) {
- case 'radio':
- value.push($value.find('[name=' + name + ']:checked').val());
- break;
-
- case 'checkbox':
- tmp = [];
- // jshint loopfunc:true
- $value.find('[name=' + name + ']:checked').each(function() {
- tmp.push($(this).val());
- });
- // jshint loopfunc:false
- value.push(tmp);
- break;
-
- case 'select':
- if (filter.multiple) {
- tmp = [];
- // jshint loopfunc:true
- $value.find('[name=' + name + '] option:selected').each(function() {
- tmp.push($(this).val());
- });
- // jshint loopfunc:false
- value.push(tmp);
- }
- else {
- value.push($value.find('[name=' + name + '] option:selected').val());
- }
- break;
-
- default:
- value.push($value.find('[name=' + name + ']').val());
- }
- }
-
- value = value.map(function(val) {
- if (operator.multiple && filter.value_separator && typeof val == 'string') {
- val = val.split(filter.value_separator);
- }
-
- if ($.isArray(val)) {
- return val.map(function(subval) {
- return Utils.changeType(subval, filter.type);
- });
- }
- else {
- return Utils.changeType(val, filter.type);
- }
- });
-
- if (operator.nb_inputs === 1) {
- value = value[0];
- }
-
- // @deprecated
- if (filter.valueParser) {
- value = filter.valueParser.call(this, rule, value);
- }
- }
-
- /**
- * Modifies the rule's value grabbed from the DOM
- * @event changer:getRuleValue
- * @memberof QueryBuilder
- * @param {*} value
- * @param {Rule} rule
- * @returns {*}
- */
- return this.change('getRuleValue', value, rule);
- };
-
- /**
- * Sets the value of a rule's input
- * @param {Rule} rule
- * @param {*} value
- * @private
- */
- QueryBuilder.prototype.setRuleInputValue = function(rule, value) {
- var filter = rule.filter;
- var operator = rule.operator;
-
- if (!filter || !operator) {
- return;
- }
-
- rule._updating_input = true;
-
- if (filter.valueSetter) {
- filter.valueSetter.call(this, rule, value);
- }
- else {
- var $value = rule.$el.find(QueryBuilder.selectors.value_container);
-
- if (operator.nb_inputs == 1) {
- value = [value];
- }
-
- for (var i = 0; i < operator.nb_inputs; i++) {
- var name = Utils.escapeElementId(rule.id + '_value_' + i);
-
- switch (filter.input) {
- case 'radio':
- $value.find('[name=' + name + '][value="' + value[i] + '"]').prop('checked', true).trigger('change');
- break;
-
- case 'checkbox':
- if (!$.isArray(value[i])) {
- value[i] = [value[i]];
- }
- // jshint loopfunc:true
- value[i].forEach(function(value) {
- $value.find('[name=' + name + '][value="' + value + '"]').prop('checked', true).trigger('change');
- });
- // jshint loopfunc:false
- break;
-
- default:
- if (operator.multiple && filter.value_separator && $.isArray(value[i])) {
- value[i] = value[i].join(filter.value_separator);
- }
- $value.find('[name=' + name + ']').val(value[i]).trigger('change');
- break;
- }
- }
- }
-
- rule._updating_input = false;
- };
-
- /**
- * Parses rule flags
- * @param {object} rule
- * @returns {object}
- * @fires QueryBuilder.changer:parseRuleFlags
- * @private
- */
- QueryBuilder.prototype.parseRuleFlags = function(rule) {
- var flags = $.extend({}, this.settings.default_rule_flags);
-
- if (rule.readonly) {
- $.extend(flags, {
- filter_readonly: true,
- operator_readonly: true,
- value_readonly: true,
- no_delete: true
- });
- }
-
- if (rule.flags) {
- $.extend(flags, rule.flags);
- }
-
- /**
- * Modifies the consolidated rule's flags
- * @event changer:parseRuleFlags
- * @memberof QueryBuilder
- * @param {object} flags
- * @param {object} rule - <b>not</b> a Rule object
- * @returns {object}
- */
- return this.change('parseRuleFlags', flags, rule);
- };
-
- /**
- * Gets a copy of flags of a rule
- * @param {object} flags
- * @param {boolean} [all=false] - return all flags or only changes from default flags
- * @returns {object}
- * @private
- */
- QueryBuilder.prototype.getRuleFlags = function(flags, all) {
- if (all) {
- return $.extend({}, flags);
- }
- else {
- var ret = {};
- $.each(this.settings.default_rule_flags, function(key, value) {
- if (flags[key] !== value) {
- ret[key] = flags[key];
- }
- });
- return ret;
- }
- };
-
- /**
- * Parses group flags
- * @param {object} group
- * @returns {object}
- * @fires QueryBuilder.changer:parseGroupFlags
- * @private
- */
- QueryBuilder.prototype.parseGroupFlags = function(group) {
- var flags = $.extend({}, this.settings.default_group_flags);
-
- if (group.readonly) {
- $.extend(flags, {
- condition_readonly: true,
- no_add_rule: true,
- no_add_group: true,
- no_delete: true
- });
- }
-
- if (group.flags) {
- $.extend(flags, group.flags);
- }
-
- /**
- * Modifies the consolidated group's flags
- * @event changer:parseGroupFlags
- * @memberof QueryBuilder
- * @param {object} flags
- * @param {object} group - <b>not</b> a Group object
- * @returns {object}
- */
- return this.change('parseGroupFlags', flags, group);
- };
-
- /**
- * Gets a copy of flags of a group
- * @param {object} flags
- * @param {boolean} [all=false] - return all flags or only changes from default flags
- * @returns {object}
- * @private
- */
- QueryBuilder.prototype.getGroupFlags = function(flags, all) {
- if (all) {
- return $.extend({}, flags);
- }
- else {
- var ret = {};
- $.each(this.settings.default_group_flags, function(key, value) {
- if (flags[key] !== value) {
- ret[key] = flags[key];
- }
- });
- return ret;
- }
- };
-
- /**
- * Translate a label either by looking in the `lang` object or in itself if it's an object where keys are language codes
- * @param {string} [category]
- * @param {string|object} key
- * @returns {string}
- * @fires QueryBuilder.changer:translate
- */
- QueryBuilder.prototype.translate = function(category, key) {
- if (!key) {
- key = category;
- category = undefined;
- }
-
- var translation;
- if (typeof key === 'object') {
- translation = key[this.settings.lang_code] || key['en'];
- }
- else {
- translation = (category ? this.lang[category] : this.lang)[key] || key;
- }
-
- /**
- * Modifies the translated label
- * @event changer:translate
- * @memberof QueryBuilder
- * @param {string} translation
- * @param {string|object} key
- * @param {string} [category]
- * @returns {string}
- */
- return this.change('translate', translation, key, category);
- };
-
- /**
- * Returns a validation message
- * @param {object} validation
- * @param {string} type
- * @param {string} def
- * @returns {string}
- * @private
- */
- QueryBuilder.prototype.getValidationMessage = function(validation, type, def) {
- return validation.messages && validation.messages[type] || def;
- };
-
-
- QueryBuilder.templates.group = '\
- <div id="{{= it.group_id }}" class="rules-group-container"> \
- <div class="rules-group-header"> \
- <div class="btn-group pull-right group-actions"> \
- <button type="button" class="btn btn-xs btn-success" data-add="rule"> \
- <i class="{{= it.icons.add_rule }}"></i> {{= it.translate("add_rule") }} \
- </button> \
- {{? it.settings.allow_groups===-1 || it.settings.allow_groups>=it.level }} \
- <button type="button" class="btn btn-xs btn-success" data-add="group"> \
- <i class="{{= it.icons.add_group }}"></i> {{= it.translate("add_group") }} \
- </button> \
- {{?}} \
- {{? it.level>1 }} \
- <button type="button" class="btn btn-xs btn-danger" data-delete="group"> \
- <i class="{{= it.icons.remove_group }}"></i> {{= it.translate("delete_group") }} \
- </button> \
- {{?}} \
- </div> \
- <div class="btn-group group-conditions"> \
- {{~ it.conditions: condition }} \
- <label class="btn btn-xs btn-primary"> \
- <input type="radio" name="{{= it.group_id }}_cond" value="{{= condition }}"> {{= it.translate("conditions", condition) }} \
- </label> \
- {{~}} \
- </div> \
- {{? it.settings.display_errors }} \
- <div class="error-container"><i class="{{= it.icons.error }}"></i></div> \
- {{?}} \
- </div> \
- <div class=rules-group-body> \
- <div class=rules-list></div> \
- </div> \
- </div>';
-
- QueryBuilder.templates.rule = '\
- <div id="{{= it.rule_id }}" class="rule-container"> \
- <div class="rule-header"> \
- <div class="btn-group pull-right rule-actions"> \
- <button type="button" class="btn btn-xs btn-danger" data-delete="rule"> \
- <i class="{{= it.icons.remove_rule }}"></i> {{= it.translate("delete_rule") }} \
- </button> \
- </div> \
- </div> \
- {{? it.settings.display_errors }} \
- <div class="error-container"><i class="{{= it.icons.error }}"></i></div> \
- {{?}} \
- <div class="rule-filter-container"></div> \
- <div class="rule-operator-container"></div> \
- <div class="rule-value-container"></div> \
- </div>';
-
- QueryBuilder.templates.filterSelect = '\
- {{ var optgroup = null; }} \
- <select class="form-control" name="{{= it.rule.id }}_filter"> \
- {{? it.settings.display_empty_filter }} \
- <option value="-1">{{= it.settings.select_placeholder }}</option> \
- {{?}} \
- {{~ it.filters: filter }} \
- {{? optgroup !== filter.optgroup }} \
- {{? optgroup !== null }}</optgroup>{{?}} \
- {{? (optgroup = filter.optgroup) !== null }} \
- <optgroup label="{{= it.translate(it.settings.optgroups[optgroup]) }}"> \
- {{?}} \
- {{?}} \
- <option value="{{= filter.id }}" {{? filter.icon}}data-icon="{{= filter.icon}}"{{?}}>{{= it.translate(filter.label) }}</option> \
- {{~}} \
- {{? optgroup !== null }}</optgroup>{{?}} \
- </select>';
-
- QueryBuilder.templates.operatorSelect = '\
- {{? it.operators.length === 1 }} \
- <span> \
- {{= it.translate("operators", it.operators[0].type) }} \
- </span> \
- {{?}} \
- {{ var optgroup = null; }} \
- <select class="form-control {{? it.operators.length === 1 }}hide{{?}}" name="{{= it.rule.id }}_operator"> \
- {{~ it.operators: operator }} \
- {{? optgroup !== operator.optgroup }} \
- {{? optgroup !== null }}</optgroup>{{?}} \
- {{? (optgroup = operator.optgroup) !== null }} \
- <optgroup label="{{= it.translate(it.settings.optgroups[optgroup]) }}"> \
- {{?}} \
- {{?}} \
- <option value="{{= operator.type }}" {{? operator.icon}}data-icon="{{= operator.icon}}"{{?}}>{{= it.translate("operators", operator.type) }}</option> \
- {{~}} \
- {{? optgroup !== null }}</optgroup>{{?}} \
- </select>';
-
- QueryBuilder.templates.ruleValueSelect = '\
- {{ var optgroup = null; }} \
- <select class="form-control" name="{{= it.name }}" {{? it.rule.filter.multiple }}multiple{{?}}> \
- {{? it.rule.filter.placeholder }} \
- <option value="{{= it.rule.filter.placeholder_value }}" disabled selected>{{= it.rule.filter.placeholder }}</option> \
- {{?}} \
- {{~ it.rule.filter.values: entry }} \
- {{? optgroup !== entry.optgroup }} \
- {{? optgroup !== null }}</optgroup>{{?}} \
- {{? (optgroup = entry.optgroup) !== null }} \
- <optgroup label="{{= it.translate(it.settings.optgroups[optgroup]) }}"> \
- {{?}} \
- {{?}} \
- <option value="{{= entry.value }}">{{= entry.label }}</option> \
- {{~}} \
- {{? optgroup !== null }}</optgroup>{{?}} \
- </select>';
-
- /**
- * Returns group's HTML
- * @param {string} group_id
- * @param {int} level
- * @returns {string}
- * @fires QueryBuilder.changer:getGroupTemplate
- * @private
- */
- QueryBuilder.prototype.getGroupTemplate = function(group_id, level) {
- var h = this.templates.group({
- builder: this,
- group_id: group_id,
- level: level,
- conditions: this.settings.conditions,
- icons: this.icons,
- settings: this.settings,
- translate: this.translate.bind(this)
- });
-
- /**
- * Modifies the raw HTML of a group
- * @event changer:getGroupTemplate
- * @memberof QueryBuilder
- * @param {string} html
- * @param {int} level
- * @returns {string}
- */
- return this.change('getGroupTemplate', h, level);
- };
-
- /**
- * Returns rule's HTML
- * @param {string} rule_id
- * @returns {string}
- * @fires QueryBuilder.changer:getRuleTemplate
- * @private
- */
- QueryBuilder.prototype.getRuleTemplate = function(rule_id) {
- var h = this.templates.rule({
- builder: this,
- rule_id: rule_id,
- icons: this.icons,
- settings: this.settings,
- translate: this.translate.bind(this)
- });
-
- /**
- * Modifies the raw HTML of a rule
- * @event changer:getRuleTemplate
- * @memberof QueryBuilder
- * @param {string} html
- * @returns {string}
- */
- return this.change('getRuleTemplate', h);
- };
-
- /**
- * Returns rule's filter HTML
- * @param {Rule} rule
- * @param {object[]} filters
- * @returns {string}
- * @fires QueryBuilder.changer:getRuleFilterTemplate
- * @private
- */
- QueryBuilder.prototype.getRuleFilterSelect = function(rule, filters) {
- var h = this.templates.filterSelect({
- builder: this,
- rule: rule,
- filters: filters,
- icons: this.icons,
- settings: this.settings,
- translate: this.translate.bind(this)
- });
-
- /**
- * Modifies the raw HTML of the rule's filter dropdown
- * @event changer:getRuleFilterSelect
- * @memberof QueryBuilder
- * @param {string} html
- * @param {Rule} rule
- * @param {QueryBuilder.Filter[]} filters
- * @returns {string}
- */
- return this.change('getRuleFilterSelect', h, rule, filters);
- };
-
- /**
- * Returns rule's operator HTML
- * @param {Rule} rule
- * @param {object[]} operators
- * @returns {string}
- * @fires QueryBuilder.changer:getRuleOperatorTemplate
- * @private
- */
- QueryBuilder.prototype.getRuleOperatorSelect = function(rule, operators) {
- var h = this.templates.operatorSelect({
- builder: this,
- rule: rule,
- operators: operators,
- icons: this.icons,
- settings: this.settings,
- translate: this.translate.bind(this)
- });
-
- /**
- * Modifies the raw HTML of the rule's operator dropdown
- * @event changer:getRuleOperatorSelect
- * @memberof QueryBuilder
- * @param {string} html
- * @param {Rule} rule
- * @param {QueryBuilder.Operator[]} operators
- * @returns {string}
- */
- return this.change('getRuleOperatorSelect', h, rule, operators);
- };
-
- /**
- * Returns the rule's value select HTML
- * @param {string} name
- * @param {Rule} rule
- * @returns {string}
- * @fires QueryBuilder.changer:getRuleValueSelect
- * @private
- */
- QueryBuilder.prototype.getRuleValueSelect = function(name, rule) {
- var h = this.templates.ruleValueSelect({
- builder: this,
- name: name,
- rule: rule,
- icons: this.icons,
- settings: this.settings,
- translate: this.translate.bind(this)
- });
-
- /**
- * Modifies the raw HTML of the rule's value dropdown (in case of a "select filter)
- * @event changer:getRuleValueSelect
- * @memberof QueryBuilder
- * @param {string} html
- * @param [string} name
- * @param {Rule} rule
- * @returns {string}
- */
- return this.change('getRuleValueSelect', h, name, rule);
- };
-
- /**
- * Returns the rule's value HTML
- * @param {Rule} rule
- * @param {int} value_id
- * @returns {string}
- * @fires QueryBuilder.changer:getRuleInput
- * @private
- */
- QueryBuilder.prototype.getRuleInput = function(rule, value_id) {
- var filter = rule.filter;
- var validation = rule.filter.validation || {};
- var name = rule.id + '_value_' + value_id;
- var c = filter.vertical ? ' class=block' : '';
- var h = '';
-
- if (typeof filter.input == 'function') {
- h = filter.input.call(this, rule, name);
- }
- else {
- switch (filter.input) {
- case 'radio':
- case 'checkbox':
- Utils.iterateOptions(filter.values, function(key, val) {
- h += '<label' + c + '><input type="' + filter.input + '" name="' + name + '" value="' + key + '"> ' + val + '</label> ';
- });
- break;
-
- case 'select':
- h = this.getRuleValueSelect(name, rule);
- break;
-
- case 'textarea':
- h += '<textarea class="form-control" name="' + name + '"';
- if (filter.size) h += ' cols="' + filter.size + '"';
- if (filter.rows) h += ' rows="' + filter.rows + '"';
- if (validation.min !== undefined) h += ' minlength="' + validation.min + '"';
- if (validation.max !== undefined) h += ' maxlength="' + validation.max + '"';
- if (filter.placeholder) h += ' placeholder="' + filter.placeholder + '"';
- h += '></textarea>';
- break;
-
- case 'number':
- h += '<input class="form-control" type="number" name="' + name + '"';
- if (validation.step !== undefined) h += ' step="' + validation.step + '"';
- if (validation.min !== undefined) h += ' min="' + validation.min + '"';
- if (validation.max !== undefined) h += ' max="' + validation.max + '"';
- if (filter.placeholder) h += ' placeholder="' + filter.placeholder + '"';
- if (filter.size) h += ' size="' + filter.size + '"';
- h += '>';
- break;
-
- default:
- h += '<input class="form-control" type="text" name="' + name + '"';
- if (filter.placeholder) h += ' placeholder="' + filter.placeholder + '"';
- if (filter.type === 'string' && validation.min !== undefined) h += ' minlength="' + validation.min + '"';
- if (filter.type === 'string' && validation.max !== undefined) h += ' maxlength="' + validation.max + '"';
- if (filter.size) h += ' size="' + filter.size + '"';
- h += '>';
- }
- }
-
- /**
- * Modifies the raw HTML of the rule's input
- * @event changer:getRuleInput
- * @memberof QueryBuilder
- * @param {string} html
- * @param {Rule} rule
- * @param {string} name - the name that the input must have
- * @returns {string}
- */
- return this.change('getRuleInput', h, rule, name);
- };
-
-
- /**
- * @namespace
- */
- var Utils = {};
-
- /**
- * @member {object}
- * @memberof QueryBuilder
- * @see Utils
- */
- QueryBuilder.utils = Utils;
-
- /**
- * @callback Utils#OptionsIteratee
- * @param {string} key
- * @param {string} value
- * @param {string} [optgroup]
- */
-
- /**
- * Iterates over radio/checkbox/selection options, it accept four formats
- *
- * @example
- * // array of values
- * options = ['one', 'two', 'three']
- * @example
- * // simple key-value map
- * options = {1: 'one', 2: 'two', 3: 'three'}
- * @example
- * // array of 1-element maps
- * options = [{1: 'one'}, {2: 'two'}, {3: 'three'}]
- * @example
- * // array of elements
- * options = [{value: 1, label: 'one', optgroup: 'group'}, {value: 2, label: 'two'}]
- *
- * @param {object|array} options
- * @param {Utils#OptionsIteratee} tpl
- */
- Utils.iterateOptions = function(options, tpl) {
- if (options) {
- if ($.isArray(options)) {
- options.forEach(function(entry) {
- if ($.isPlainObject(entry)) {
- // array of elements
- if ('value' in entry) {
- tpl(entry.value, entry.label || entry.value, entry.optgroup);
- }
- // array of one-element maps
- else {
- $.each(entry, function(key, val) {
- tpl(key, val);
- return false; // break after first entry
- });
- }
- }
- // array of values
- else {
- tpl(entry, entry);
- }
- });
- }
- // unordered map
- else {
- $.each(options, function(key, val) {
- tpl(key, val);
- });
- }
- }
- };
-
- /**
- * Replaces {0}, {1}, ... in a string
- * @param {string} str
- * @param {...*} args
- * @returns {string}
- */
- Utils.fmt = function(str, args) {
- if (!Array.isArray(args)) {
- args = Array.prototype.slice.call(arguments, 1);
- }
-
- return str.replace(/{([0-9]+)}/g, function(m, i) {
- return args[parseInt(i)];
- });
- };
-
- /**
- * Throws an Error object with custom name or logs an error
- * @param {boolean} [doThrow=true]
- * @param {string} type
- * @param {string} message
- * @param {...*} args
- */
- Utils.error = function() {
- var i = 0;
- var doThrow = typeof arguments[i] === 'boolean' ? arguments[i++] : true;
- var type = arguments[i++];
- var message = arguments[i++];
- var args = Array.isArray(arguments[i]) ? arguments[i] : Array.prototype.slice.call(arguments, i);
-
- if (doThrow) {
- var err = new Error(Utils.fmt(message, args));
- err.name = type + 'Error';
- err.args = args;
- throw err;
- }
- else {
- console.error(type + 'Error: ' + Utils.fmt(message, args));
- }
- };
-
- /**
- * Changes the type of a value to int, float or bool
- * @param {*} value
- * @param {string} type - 'integer', 'double', 'boolean' or anything else (passthrough)
- * @returns {*}
- */
- Utils.changeType = function(value, type) {
- if (value === '' || value === undefined) {
- return undefined;
- }
-
- switch (type) {
- // @formatter:off
- case 'integer':
- if (typeof value === 'string' && !/^-?\d+$/.test(value)) {
- return value;
- }
- return parseInt(value);
- case 'double':
- if (typeof value === 'string' && !/^-?\d+\.?\d*$/.test(value)) {
- return value;
- }
- return parseFloat(value);
- case 'boolean':
- if (typeof value === 'string' && !/^(0|1|true|false){1}$/i.test(value)) {
- return value;
- }
- return value === true || value === 1 || value.toLowerCase() === 'true' || value === '1';
- default: return value;
- // @formatter:on
- }
- };
-
- /**
- * Escapes a string like PHP's mysql_real_escape_string does
- * @param {string} value
- * @returns {string}
- */
- Utils.escapeString = function(value) {
- if (typeof value != 'string') {
- return value;
- }
-
- return value
- .replace(/[\0\n\r\b\\\'\"]/g, function(s) {
- switch (s) {
- // @formatter:off
- case '\0': return '\\0';
- case '\n': return '\\n';
- case '\r': return '\\r';
- case '\b': return '\\b';
- default: return '\\' + s;
- // @formatter:off
- }
- })
- // uglify compliant
- .replace(/\t/g, '\\t')
- .replace(/\x1a/g, '\\Z');
- };
-
- /**
- * Escapes a string for use in regex
- * @param {string} str
- * @returns {string}
- */
- Utils.escapeRegExp = function(str) {
- return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
- };
-
- /**
- * Escapes a string for use in HTML element id
- * @param {string} str
- * @returns {string}
- */
- Utils.escapeElementId = function(str) {
- // Regex based on that suggested by:
- // https://learn.jquery.com/using-jquery-core/faq/how-do-i-select-an-element-by-an-id-that-has-characters-used-in-css-notation/
- // - escapes : . [ ] ,
- // - avoids escaping already escaped values
- return (str) ? str.replace(/(\\)?([:.\[\],])/g,
- function( $0, $1, $2 ) { return $1 ? $0 : '\\' + $2; }) : str;
- };
-
- /**
- * Sorts objects by grouping them by `key`, preserving initial order when possible
- * @param {object[]} items
- * @param {string} key
- * @returns {object[]}
- */
- Utils.groupSort = function(items, key) {
- var optgroups = [];
- var newItems = [];
-
- items.forEach(function(item) {
- var idx;
-
- if (item[key]) {
- idx = optgroups.lastIndexOf(item[key]);
-
- if (idx == -1) {
- idx = optgroups.length;
- }
- else {
- idx++;
- }
- }
- else {
- idx = optgroups.length;
- }
-
- optgroups.splice(idx, 0, item[key]);
- newItems.splice(idx, 0, item);
- });
-
- return newItems;
- };
-
- /**
- * Defines properties on an Node prototype with getter and setter.<br>
- * Update events are emitted in the setter through root Model (if any).<br>
- * The object must have a `__` object, non enumerable property to store values.
- * @param {function} obj
- * @param {string[]} fields
- */
- Utils.defineModelProperties = function(obj, fields) {
- fields.forEach(function(field) {
- Object.defineProperty(obj.prototype, field, {
- enumerable: true,
- get: function() {
- return this.__[field];
- },
- set: function(value) {
- var previousValue = (this.__[field] !== null && typeof this.__[field] == 'object') ?
- $.extend({}, this.__[field]) :
- this.__[field];
-
- this.__[field] = value;
-
- if (this.model !== null) {
- /**
- * After a value of the model changed
- * @event model:update
- * @memberof Model
- * @param {Node} node
- * @param {string} field
- * @param {*} value
- * @param {*} previousValue
- */
- this.model.trigger('update', this, field, value, previousValue);
- }
- }
- });
- });
- };
-
-
- /**
- * Main object storing data model and emitting model events
- * @constructor
- */
- function Model() {
- /**
- * @member {Group}
- * @readonly
- */
- this.root = null;
-
- /**
- * Base for event emitting
- * @member {jQuery}
- * @readonly
- * @private
- */
- this.$ = $(this);
- }
-
- $.extend(Model.prototype, /** @lends Model.prototype */ {
- /**
- * Triggers an event on the model
- * @param {string} type
- * @returns {$.Event}
- */
- trigger: function(type) {
- var event = new $.Event(type);
- this.$.triggerHandler(event, Array.prototype.slice.call(arguments, 1));
- return event;
- },
-
- /**
- * Attaches an event listener on the model
- * @param {string} type
- * @param {function} cb
- * @returns {Model}
- */
- on: function() {
- this.$.on.apply(this.$, Array.prototype.slice.call(arguments));
- return this;
- },
-
- /**
- * Removes an event listener from the model
- * @param {string} type
- * @param {function} [cb]
- * @returns {Model}
- */
- off: function() {
- this.$.off.apply(this.$, Array.prototype.slice.call(arguments));
- return this;
- },
-
- /**
- * Attaches an event listener called once on the model
- * @param {string} type
- * @param {function} cb
- * @returns {Model}
- */
- once: function() {
- this.$.one.apply(this.$, Array.prototype.slice.call(arguments));
- return this;
- }
- });
-
-
- /**
- * Root abstract object
- * @constructor
- * @param {Node} [parent]
- * @param {jQuery} $el
- */
- var Node = function(parent, $el) {
- if (!(this instanceof Node)) {
- return new Node(parent, $el);
- }
-
- Object.defineProperty(this, '__', { value: {} });
-
- $el.data('queryBuilderModel', this);
-
- /**
- * @name level
- * @member {int}
- * @memberof Node
- * @instance
- * @readonly
- */
- this.__.level = 1;
-
- /**
- * @name error
- * @member {string}
- * @memberof Node
- * @instance
- */
- this.__.error = null;
-
- /**
- * @name flags
- * @member {object}
- * @memberof Node
- * @instance
- * @readonly
- */
- this.__.flags = {};
-
- /**
- * @name data
- * @member {object}
- * @memberof Node
- * @instance
- */
- this.__.data = undefined;
-
- /**
- * @member {jQuery}
- * @readonly
- */
- this.$el = $el;
-
- /**
- * @member {string}
- * @readonly
- */
- this.id = $el[0].id;
-
- /**
- * @member {Model}
- * @readonly
- */
- this.model = null;
-
- /**
- * @member {Group}
- * @readonly
- */
- this.parent = parent;
- };
-
- Utils.defineModelProperties(Node, ['level', 'error', 'data', 'flags']);
-
- Object.defineProperty(Node.prototype, 'parent', {
- enumerable: true,
- get: function() {
- return this.__.parent;
- },
- set: function(value) {
- this.__.parent = value;
- this.level = value === null ? 1 : value.level + 1;
- this.model = value === null ? null : value.model;
- }
- });
-
- /**
- * Checks if this Node is the root
- * @returns {boolean}
- */
- Node.prototype.isRoot = function() {
- return (this.level === 1);
- };
-
- /**
- * Returns the node position inside its parent
- * @returns {int}
- */
- Node.prototype.getPos = function() {
- if (this.isRoot()) {
- return -1;
- }
- else {
- return this.parent.getNodePos(this);
- }
- };
-
- /**
- * Deletes self
- * @fires Model.model:drop
- */
- Node.prototype.drop = function() {
- var model = this.model;
-
- if (!!this.parent) {
- this.parent.removeNode(this);
- }
-
- this.$el.removeData('queryBuilderModel');
-
- if (model !== null) {
- /**
- * After a node of the model has been removed
- * @event model:drop
- * @memberof Model
- * @param {Node} node
- */
- model.trigger('drop', this);
- }
- };
-
- /**
- * Moves itself after another Node
- * @param {Node} target
- * @fires Model.model:move
- */
- Node.prototype.moveAfter = function(target) {
- if (!this.isRoot()) {
- this.move(target.parent, target.getPos() + 1);
- }
- };
-
- /**
- * Moves itself at the beginning of parent or another Group
- * @param {Group} [target]
- * @fires Model.model:move
- */
- Node.prototype.moveAtBegin = function(target) {
- if (!this.isRoot()) {
- if (target === undefined) {
- target = this.parent;
- }
-
- this.move(target, 0);
- }
- };
-
- /**
- * Moves itself at the end of parent or another Group
- * @param {Group} [target]
- * @fires Model.model:move
- */
- Node.prototype.moveAtEnd = function(target) {
- if (!this.isRoot()) {
- if (target === undefined) {
- target = this.parent;
- }
-
- this.move(target, target.length() === 0 ? 0 : target.length() - 1);
- }
- };
-
- /**
- * Moves itself at specific position of Group
- * @param {Group} target
- * @param {int} index
- * @fires Model.model:move
- */
- Node.prototype.move = function(target, index) {
- if (!this.isRoot()) {
- if (typeof target === 'number') {
- index = target;
- target = this.parent;
- }
-
- this.parent.removeNode(this);
- target.insertNode(this, index, false);
-
- if (this.model !== null) {
- /**
- * After a node of the model has been moved
- * @event model:move
- * @memberof Model
- * @param {Node} node
- * @param {Node} target
- * @param {int} index
- */
- this.model.trigger('move', this, target, index);
- }
- }
- };
-
-
- /**
- * Group object
- * @constructor
- * @extends Node
- * @param {Group} [parent]
- * @param {jQuery} $el
- */
- var Group = function(parent, $el) {
- if (!(this instanceof Group)) {
- return new Group(parent, $el);
- }
-
- Node.call(this, parent, $el);
-
- /**
- * @member {object[]}
- * @readonly
- */
- this.rules = [];
-
- /**
- * @name condition
- * @member {string}
- * @memberof Group
- * @instance
- */
- this.__.condition = null;
- };
-
- Group.prototype = Object.create(Node.prototype);
- Group.prototype.constructor = Group;
-
- Utils.defineModelProperties(Group, ['condition']);
-
- /**
- * Removes group's content
- */
- Group.prototype.empty = function() {
- this.each('reverse', function(rule) {
- rule.drop();
- }, function(group) {
- group.drop();
- });
- };
-
- /**
- * Deletes self
- */
- Group.prototype.drop = function() {
- this.empty();
- Node.prototype.drop.call(this);
- };
-
- /**
- * Returns the number of children
- * @returns {int}
- */
- Group.prototype.length = function() {
- return this.rules.length;
- };
-
- /**
- * Adds a Node at specified index
- * @param {Node} node
- * @param {int} [index=end]
- * @param {boolean} [trigger=false] - fire 'add' event
- * @returns {Node} the inserted node
- * @fires Model.model:add
- */
- Group.prototype.insertNode = function(node, index, trigger) {
- if (index === undefined) {
- index = this.length();
- }
-
- this.rules.splice(index, 0, node);
- node.parent = this;
-
- if (trigger && this.model !== null) {
- /**
- * After a node of the model has been added
- * @event model:add
- * @memberof Model
- * @param {Node} parent
- * @param {Node} node
- * @param {int} index
- */
- this.model.trigger('add', this, node, index);
- }
-
- return node;
- };
-
- /**
- * Adds a new Group at specified index
- * @param {jQuery} $el
- * @param {int} [index=end]
- * @returns {Group}
- * @fires Model.model:add
- */
- Group.prototype.addGroup = function($el, index) {
- return this.insertNode(new Group(this, $el), index, true);
- };
-
- /**
- * Adds a new Rule at specified index
- * @param {jQuery} $el
- * @param {int} [index=end]
- * @returns {Rule}
- * @fires Model.model:add
- */
- Group.prototype.addRule = function($el, index) {
- return this.insertNode(new Rule(this, $el), index, true);
- };
-
- /**
- * Deletes a specific Node
- * @param {Node} node
- */
- Group.prototype.removeNode = function(node) {
- var index = this.getNodePos(node);
- if (index !== -1) {
- node.parent = null;
- this.rules.splice(index, 1);
- }
- };
-
- /**
- * Returns the position of a child Node
- * @param {Node} node
- * @returns {int}
- */
- Group.prototype.getNodePos = function(node) {
- return this.rules.indexOf(node);
- };
-
- /**
- * @callback Model#GroupIteratee
- * @param {Node} node
- * @returns {boolean} stop the iteration
- */
-
- /**
- * Iterate over all Nodes
- * @param {boolean} [reverse=false] - iterate in reverse order, required if you delete nodes
- * @param {Model#GroupIteratee} cbRule - callback for Rules (can be `null` but not omitted)
- * @param {Model#GroupIteratee} [cbGroup] - callback for Groups
- * @param {object} [context] - context for callbacks
- * @returns {boolean} if the iteration has been stopped by a callback
- */
- Group.prototype.each = function(reverse, cbRule, cbGroup, context) {
- if (typeof reverse !== 'boolean' && typeof reverse !== 'string') {
- context = cbGroup;
- cbGroup = cbRule;
- cbRule = reverse;
- reverse = false;
- }
- context = context === undefined ? null : context;
-
- var i = reverse ? this.rules.length - 1 : 0;
- var l = reverse ? 0 : this.rules.length - 1;
- var c = reverse ? -1 : 1;
- var next = function() {
- return reverse ? i >= l : i <= l;
- };
- var stop = false;
-
- for (; next(); i += c) {
- if (this.rules[i] instanceof Group) {
- if (!!cbGroup) {
- stop = cbGroup.call(context, this.rules[i]) === false;
- }
- }
- else if (!!cbRule) {
- stop = cbRule.call(context, this.rules[i]) === false;
- }
-
- if (stop) {
- break;
- }
- }
-
- return !stop;
- };
-
- /**
- * Checks if the group contains a particular Node
- * @param {Node} node
- * @param {boolean} [recursive=false]
- * @returns {boolean}
- */
- Group.prototype.contains = function(node, recursive) {
- if (this.getNodePos(node) !== -1) {
- return true;
- }
- else if (!recursive) {
- return false;
- }
- else {
- // the loop will return with false as soon as the Node is found
- return !this.each(function() {
- return true;
- }, function(group) {
- return !group.contains(node, true);
- });
- }
- };
-
-
- /**
- * Rule object
- * @constructor
- * @extends Node
- * @param {Group} parent
- * @param {jQuery} $el
- */
- var Rule = function(parent, $el) {
- if (!(this instanceof Rule)) {
- return new Rule(parent, $el);
- }
-
- Node.call(this, parent, $el);
-
- this._updating_value = false;
- this._updating_input = false;
-
- /**
- * @name filter
- * @member {QueryBuilder.Filter}
- * @memberof Rule
- * @instance
- */
- this.__.filter = null;
-
- /**
- * @name operator
- * @member {QueryBuilder.Operator}
- * @memberof Rule
- * @instance
- */
- this.__.operator = null;
-
- /**
- * @name value
- * @member {*}
- * @memberof Rule
- * @instance
- */
- this.__.value = undefined;
- };
-
- Rule.prototype = Object.create(Node.prototype);
- Rule.prototype.constructor = Rule;
-
- Utils.defineModelProperties(Rule, ['filter', 'operator', 'value']);
-
- /**
- * Checks if this Node is the root
- * @returns {boolean} always false
- */
- Rule.prototype.isRoot = function() {
- return false;
- };
-
-
- /**
- * @member {function}
- * @memberof QueryBuilder
- * @see Group
- */
- QueryBuilder.Group = Group;
-
- /**
- * @member {function}
- * @memberof QueryBuilder
- * @see Rule
- */
- QueryBuilder.Rule = Rule;
-
-
- /**
- * The {@link http://learn.jquery.com/plugins/|jQuery Plugins} namespace
- * @external "jQuery.fn"
- */
-
- /**
- * Instanciates or accesses the {@link QueryBuilder} on an element
- * @function
- * @memberof external:"jQuery.fn"
- * @param {*} option - initial configuration or method name
- * @param {...*} args - method arguments
- *
- * @example
- * $('#builder').queryBuilder({ /** configuration object *\/ });
- * @example
- * $('#builder').queryBuilder('methodName', methodParam1, methodParam2);
- */
- $.fn.queryBuilder = function(option) {
- if (this.length === 0) {
- Utils.error('Config', 'No target defined');
- }
- if (this.length > 1) {
- Utils.error('Config', 'Unable to initialize on multiple target');
- }
-
- var data = this.data('queryBuilder');
- var options = (typeof option == 'object' && option) || {};
-
- if (!data && option == 'destroy') {
- return this;
- }
- if (!data) {
- var builder = new QueryBuilder(this, options);
- this.data('queryBuilder', builder);
- builder.init(options.rules);
- }
- if (typeof option == 'string') {
- return data[option].apply(data, Array.prototype.slice.call(arguments, 1));
- }
-
- return this;
- };
-
- /**
- * @function
- * @memberof external:"jQuery.fn"
- * @see QueryBuilder
- */
- $.fn.queryBuilder.constructor = QueryBuilder;
-
- /**
- * @function
- * @memberof external:"jQuery.fn"
- * @see QueryBuilder.defaults
- */
- $.fn.queryBuilder.defaults = QueryBuilder.defaults;
-
- /**
- * @function
- * @memberof external:"jQuery.fn"
- * @see QueryBuilder.defaults
- */
- $.fn.queryBuilder.extend = QueryBuilder.extend;
-
- /**
- * @function
- * @memberof external:"jQuery.fn"
- * @see QueryBuilder.define
- */
- $.fn.queryBuilder.define = QueryBuilder.define;
-
- /**
- * @function
- * @memberof external:"jQuery.fn"
- * @see QueryBuilder.regional
- */
- $.fn.queryBuilder.regional = QueryBuilder.regional;
-
-
- /**
- * @class BtCheckbox
- * @memberof module:plugins
- * @description Applies Awesome Bootstrap Checkbox for checkbox and radio inputs.
- * @param {object} [options]
- * @param {string} [options.font='glyphicons']
- * @param {string} [options.color='default']
- */
- QueryBuilder.define('bt-checkbox', function(options) {
- if (options.font == 'glyphicons') {
- this.$el.addClass('bt-checkbox-glyphicons');
- }
-
- this.on('getRuleInput.filter', function(h, rule, name) {
- var filter = rule.filter;
-
- if ((filter.input === 'radio' || filter.input === 'checkbox') && !filter.plugin) {
- h.value = '';
-
- if (!filter.colors) {
- filter.colors = {};
- }
- if (filter.color) {
- filter.colors._def_ = filter.color;
- }
-
- var style = filter.vertical ? ' style="display:block"' : '';
- var i = 0;
-
- Utils.iterateOptions(filter.values, function(key, val) {
- var color = filter.colors[key] || filter.colors._def_ || options.color;
- var id = name + '_' + (i++);
-
- h.value+= '\
- <div' + style + ' class="' + filter.input + ' ' + filter.input + '-' + color + '"> \
- <input type="' + filter.input + '" name="' + name + '" id="' + id + '" value="' + key + '"> \
- <label for="' + id + '">' + val + '</label> \
- </div>';
- });
- }
- });
- }, {
- font: 'glyphicons',
- color: 'default'
- });
-
-
- /**
- * @class BtSelectpicker
- * @memberof module:plugins
- * @descriptioon Applies Bootstrap Select on filters and operators combo-boxes.
- * @param {object} [options]
- * @param {string} [options.container='body']
- * @param {string} [options.style='btn-inverse btn-xs']
- * @param {int|string} [options.width='auto']
- * @param {boolean} [options.showIcon=false]
- * @throws MissingLibraryError
- */
- QueryBuilder.define('bt-selectpicker', function(options) {
- if (!$.fn.selectpicker || !$.fn.selectpicker.Constructor) {
- Utils.error('MissingLibrary', 'Bootstrap Select is required to use "bt-selectpicker" plugin. Get it here: http://silviomoreto.github.io/bootstrap-select');
- }
-
- var Selectors = QueryBuilder.selectors;
-
- // init selectpicker
- this.on('afterCreateRuleFilters', function(e, rule) {
- rule.$el.find(Selectors.rule_filter).removeClass('form-control').selectpicker(options);
- });
-
- this.on('afterCreateRuleOperators', function(e, rule) {
- rule.$el.find(Selectors.rule_operator).removeClass('form-control').selectpicker(options);
- });
-
- // update selectpicker on change
- this.on('afterUpdateRuleFilter', function(e, rule) {
- rule.$el.find(Selectors.rule_filter).selectpicker('render');
- });
-
- this.on('afterUpdateRuleOperator', function(e, rule) {
- rule.$el.find(Selectors.rule_operator).selectpicker('render');
- });
-
- this.on('beforeDeleteRule', function(e, rule) {
- rule.$el.find(Selectors.rule_filter).selectpicker('destroy');
- rule.$el.find(Selectors.rule_operator).selectpicker('destroy');
- });
- }, {
- container: 'body',
- style: 'btn-inverse btn-xs',
- width: 'auto',
- showIcon: false
- });
-
-
- /**
- * @class BtTooltipErrors
- * @memberof module:plugins
- * @description Applies Bootstrap Tooltips on validation error messages.
- * @param {object} [options]
- * @param {string} [options.placement='right']
- * @throws MissingLibraryError
- */
- QueryBuilder.define('bt-tooltip-errors', function(options) {
- if (!$.fn.tooltip || !$.fn.tooltip.Constructor || !$.fn.tooltip.Constructor.prototype.fixTitle) {
- Utils.error('MissingLibrary', 'Bootstrap Tooltip is required to use "bt-tooltip-errors" plugin. Get it here: http://getbootstrap.com');
- }
-
- var self = this;
-
- // add BT Tooltip data
- this.on('getRuleTemplate.filter getGroupTemplate.filter', function(h) {
- var $h = $(h.value);
- $h.find(QueryBuilder.selectors.error_container).attr('data-toggle', 'tooltip');
- h.value = $h.prop('outerHTML');
- });
-
- // init/refresh tooltip when title changes
- this.model.on('update', function(e, node, field) {
- if (field == 'error' && self.settings.display_errors) {
- node.$el.find(QueryBuilder.selectors.error_container).eq(0)
- .tooltip(options)
- .tooltip('hide')
- .tooltip('fixTitle');
- }
- });
- }, {
- placement: 'right'
- });
-
-
- /**
- * @class ChangeFilters
- * @memberof module:plugins
- * @description Allows to change available filters after plugin initialization.
- */
-
- QueryBuilder.extend(/** @lends module:plugins.ChangeFilters.prototype */ {
- /**
- * Change the filters of the builder
- * @param {boolean} [deleteOrphans=false] - delete rules using old filters
- * @param {QueryBuilder[]} filters
- * @fires module:plugins.ChangeFilters.changer:setFilters
- * @fires module:plugins.ChangeFilters.afterSetFilters
- * @throws ChangeFilterError
- */
- setFilters: function(deleteOrphans, filters) {
- var self = this;
-
- if (filters === undefined) {
- filters = deleteOrphans;
- deleteOrphans = false;
- }
-
- filters = this.checkFilters(filters);
-
- /**
- * Modifies the filters before {@link module:plugins.ChangeFilters.setFilters} method
- * @event changer:setFilters
- * @memberof module:plugins.ChangeFilters
- * @param {QueryBuilder.Filter[]} filters
- * @returns {QueryBuilder.Filter[]}
- */
- filters = this.change('setFilters', filters);
-
- var filtersIds = filters.map(function(filter) {
- return filter.id;
- });
-
- // check for orphans
- if (!deleteOrphans) {
- (function checkOrphans(node) {
- node.each(
- function(rule) {
- if (rule.filter && filtersIds.indexOf(rule.filter.id) === -1) {
- Utils.error('ChangeFilter', 'A rule is using filter "{0}"', rule.filter.id);
- }
- },
- checkOrphans
- );
- }(this.model.root));
- }
-
- // replace filters
- this.filters = filters;
-
- // apply on existing DOM
- (function updateBuilder(node) {
- node.each(true,
- function(rule) {
- if (rule.filter && filtersIds.indexOf(rule.filter.id) === -1) {
- rule.drop();
-
- self.trigger('rulesChanged');
- }
- else {
- self.createRuleFilters(rule);
-
- rule.$el.find(QueryBuilder.selectors.rule_filter).val(rule.filter ? rule.filter.id : '-1');
- self.trigger('afterUpdateRuleFilter', rule);
- }
- },
- updateBuilder
- );
- }(this.model.root));
-
- // update plugins
- if (this.settings.plugins) {
- if (this.settings.plugins['unique-filter']) {
- this.updateDisabledFilters();
- }
- if (this.settings.plugins['bt-selectpicker']) {
- this.$el.find(QueryBuilder.selectors.rule_filter).selectpicker('render');
- }
- }
-
- // reset the default_filter if does not exist anymore
- if (this.settings.default_filter) {
- try {
- this.getFilterById(this.settings.default_filter);
- }
- catch (e) {
- this.settings.default_filter = null;
- }
- }
-
- /**
- * After {@link module:plugins.ChangeFilters.setFilters} method
- * @event afterSetFilters
- * @memberof module:plugins.ChangeFilters
- * @param {QueryBuilder.Filter[]} filters
- */
- this.trigger('afterSetFilters', filters);
- },
-
- /**
- * Adds a new filter to the builder
- * @param {QueryBuilder.Filter|Filter[]} newFilters
- * @param {int|string} [position=#end] - index or '#start' or '#end'
- * @fires module:plugins.ChangeFilters.changer:setFilters
- * @fires module:plugins.ChangeFilters.afterSetFilters
- * @throws ChangeFilterError
- */
- addFilter: function(newFilters, position) {
- if (position === undefined || position == '#end') {
- position = this.filters.length;
- }
- else if (position == '#start') {
- position = 0;
- }
-
- if (!$.isArray(newFilters)) {
- newFilters = [newFilters];
- }
-
- var filters = $.extend(true, [], this.filters);
-
- // numeric position
- if (parseInt(position) == position) {
- Array.prototype.splice.apply(filters, [position, 0].concat(newFilters));
- }
- else {
- // after filter by its id
- if (this.filters.some(function(filter, index) {
- if (filter.id == position) {
- position = index + 1;
- return true;
- }
- })
- ) {
- Array.prototype.splice.apply(filters, [position, 0].concat(newFilters));
- }
- // defaults to end of list
- else {
- Array.prototype.push.apply(filters, newFilters);
- }
- }
-
- this.setFilters(filters);
- },
-
- /**
- * Removes a filter from the builder
- * @param {string|string[]} filterIds
- * @param {boolean} [deleteOrphans=false] delete rules using old filters
- * @fires module:plugins.ChangeFilters.changer:setFilters
- * @fires module:plugins.ChangeFilters.afterSetFilters
- * @throws ChangeFilterError
- */
- removeFilter: function(filterIds, deleteOrphans) {
- var filters = $.extend(true, [], this.filters);
- if (typeof filterIds === 'string') {
- filterIds = [filterIds];
- }
-
- filters = filters.filter(function(filter) {
- return filterIds.indexOf(filter.id) === -1;
- });
-
- this.setFilters(deleteOrphans, filters);
- }
- });
-
-
- /**
- * @class ChosenSelectpicker
- * @memberof module:plugins
- * @descriptioon Applies chosen-js Select on filters and operators combo-boxes.
- * @param {object} [options] Supports all the options for chosen
- * @throws MissingLibraryError
- */
- QueryBuilder.define('chosen-selectpicker', function(options) {
-
- if (!$.fn.chosen) {
- Utils.error('MissingLibrary', 'chosen is required to use "chosen-selectpicker" plugin. Get it here: https://github.com/harvesthq/chosen');
- }
-
- if (this.settings.plugins['bt-selectpicker']) {
- Utils.error('Conflict', 'bt-selectpicker is already selected as the dropdown plugin. Please remove chosen-selectpicker from the plugin list');
- }
-
- var Selectors = QueryBuilder.selectors;
-
- // init selectpicker
- this.on('afterCreateRuleFilters', function(e, rule) {
- rule.$el.find(Selectors.rule_filter).removeClass('form-control').chosen(options);
- });
-
- this.on('afterCreateRuleOperators', function(e, rule) {
- rule.$el.find(Selectors.rule_operator).removeClass('form-control').chosen(options);
- });
-
- // update selectpicker on change
- this.on('afterUpdateRuleFilter', function(e, rule) {
- rule.$el.find(Selectors.rule_filter).trigger('chosen:updated');
- });
-
- this.on('afterUpdateRuleOperator', function(e, rule) {
- rule.$el.find(Selectors.rule_operator).trigger('chosen:updated');
- });
-
- this.on('beforeDeleteRule', function(e, rule) {
- rule.$el.find(Selectors.rule_filter).chosen('destroy');
- rule.$el.find(Selectors.rule_operator).chosen('destroy');
- });
- });
-
-
- /**
- * @class FilterDescription
- * @memberof module:plugins
- * @description Provides three ways to display a description about a filter: inline, Bootsrap Popover or Bootbox.
- * @param {object} [options]
- * @param {string} [options.icon='glyphicon glyphicon-info-sign']
- * @param {string} [options.mode='popover'] - inline, popover or bootbox
- * @throws ConfigError
- */
- QueryBuilder.define('filter-description', function(options) {
- // INLINE
- if (options.mode === 'inline') {
- this.on('afterUpdateRuleFilter afterUpdateRuleOperator', function(e, rule) {
- var $p = rule.$el.find('p.filter-description');
- var description = e.builder.getFilterDescription(rule.filter, rule);
-
- if (!description) {
- $p.hide();
- }
- else {
- if ($p.length === 0) {
- $p = $('<p class="filter-description"></p>');
- $p.appendTo(rule.$el);
- }
- else {
- $p.css('display', '');
- }
-
- $p.html('<i class="' + options.icon + '"></i> ' + description);
- }
- });
- }
- // POPOVER
- else if (options.mode === 'popover') {
- if (!$.fn.popover || !$.fn.popover.Constructor || !$.fn.popover.Constructor.prototype.fixTitle) {
- Utils.error('MissingLibrary', 'Bootstrap Popover is required to use "filter-description" plugin. Get it here: http://getbootstrap.com');
- }
-
- this.on('afterUpdateRuleFilter afterUpdateRuleOperator', function(e, rule) {
- var $b = rule.$el.find('button.filter-description');
- var description = e.builder.getFilterDescription(rule.filter, rule);
-
- if (!description) {
- $b.hide();
-
- if ($b.data('bs.popover')) {
- $b.popover('hide');
- }
- }
- else {
- if ($b.length === 0) {
- $b = $('<button type="button" class="btn btn-xs btn-info filter-description" data-toggle="popover"><i class="' + options.icon + '"></i></button>');
- $b.prependTo(rule.$el.find(QueryBuilder.selectors.rule_actions));
-
- $b.popover({
- placement: 'left',
- container: 'body',
- html: true
- });
-
- $b.on('mouseout', function() {
- $b.popover('hide');
- });
- }
- else {
- $b.css('display', '');
- }
-
- $b.data('bs.popover').options.content = description;
-
- if ($b.attr('aria-describedby')) {
- $b.popover('show');
- }
- }
- });
- }
- // BOOTBOX
- else if (options.mode === 'bootbox') {
- if (!('bootbox' in window)) {
- Utils.error('MissingLibrary', 'Bootbox is required to use "filter-description" plugin. Get it here: http://bootboxjs.com');
- }
-
- this.on('afterUpdateRuleFilter afterUpdateRuleOperator', function(e, rule) {
- var $b = rule.$el.find('button.filter-description');
- var description = e.builder.getFilterDescription(rule.filter, rule);
-
- if (!description) {
- $b.hide();
- }
- else {
- if ($b.length === 0) {
- $b = $('<button type="button" class="btn btn-xs btn-info filter-description" data-toggle="bootbox"><i class="' + options.icon + '"></i></button>');
- $b.prependTo(rule.$el.find(QueryBuilder.selectors.rule_actions));
-
- $b.on('click', function() {
- bootbox.alert($b.data('description'));
- });
- }
- else {
- $b.css('display', '');
- }
-
- $b.data('description', description);
- }
- });
- }
- }, {
- icon: 'glyphicon glyphicon-info-sign',
- mode: 'popover'
- });
-
- QueryBuilder.extend(/** @lends module:plugins.FilterDescription.prototype */ {
- /**
- * Returns the description of a filter for a particular rule (if present)
- * @param {object} filter
- * @param {Rule} [rule]
- * @returns {string}
- * @private
- */
- getFilterDescription: function(filter, rule) {
- if (!filter) {
- return undefined;
- }
- else if (typeof filter.description == 'function') {
- return filter.description.call(this, rule);
- }
- else {
- return filter.description;
- }
- }
- });
-
-
- /**
- * @class Invert
- * @memberof module:plugins
- * @description Allows to invert a rule operator, a group condition or the entire builder.
- * @param {object} [options]
- * @param {string} [options.icon='glyphicon glyphicon-random']
- * @param {boolean} [options.recursive=true]
- * @param {boolean} [options.invert_rules=true]
- * @param {boolean} [options.display_rules_button=false]
- * @param {boolean} [options.silent_fail=false]
- */
- QueryBuilder.define('invert', function(options) {
- var self = this;
- var Selectors = QueryBuilder.selectors;
-
- // Bind events
- this.on('afterInit', function() {
- self.$el.on('click.queryBuilder', '[data-invert=group]', function() {
- var $group = $(this).closest(Selectors.group_container);
- self.invert(self.getModel($group), options);
- });
-
- if (options.display_rules_button && options.invert_rules) {
- self.$el.on('click.queryBuilder', '[data-invert=rule]', function() {
- var $rule = $(this).closest(Selectors.rule_container);
- self.invert(self.getModel($rule), options);
- });
- }
- });
-
- // Modify templates
- if (!options.disable_template) {
- this.on('getGroupTemplate.filter', function(h) {
- var $h = $(h.value);
- $h.find(Selectors.condition_container).after(
- '<button type="button" class="btn btn-xs btn-default" data-invert="group">' +
- '<i class="' + options.icon + '"></i> ' + self.translate('invert') +
- '</button>'
- );
- h.value = $h.prop('outerHTML');
- });
-
- if (options.display_rules_button && options.invert_rules) {
- this.on('getRuleTemplate.filter', function(h) {
- var $h = $(h.value);
- $h.find(Selectors.rule_actions).prepend(
- '<button type="button" class="btn btn-xs btn-default" data-invert="rule">' +
- '<i class="' + options.icon + '"></i> ' + self.translate('invert') +
- '</button>'
- );
- h.value = $h.prop('outerHTML');
- });
- }
- }
- }, {
- icon: 'glyphicon glyphicon-random',
- recursive: true,
- invert_rules: true,
- display_rules_button: false,
- silent_fail: false,
- disable_template: false
- });
-
- QueryBuilder.defaults({
- operatorOpposites: {
- 'equal': 'not_equal',
- 'not_equal': 'equal',
- 'in': 'not_in',
- 'not_in': 'in',
- 'less': 'greater_or_equal',
- 'less_or_equal': 'greater',
- 'greater': 'less_or_equal',
- 'greater_or_equal': 'less',
- 'between': 'not_between',
- 'not_between': 'between',
- 'begins_with': 'not_begins_with',
- 'not_begins_with': 'begins_with',
- 'contains': 'not_contains',
- 'not_contains': 'contains',
- 'ends_with': 'not_ends_with',
- 'not_ends_with': 'ends_with',
- 'is_empty': 'is_not_empty',
- 'is_not_empty': 'is_empty',
- 'is_null': 'is_not_null',
- 'is_not_null': 'is_null'
- },
-
- conditionOpposites: {
- 'AND': 'OR',
- 'OR': 'AND'
- }
- });
-
- QueryBuilder.extend(/** @lends module:plugins.Invert.prototype */ {
- /**
- * Invert a Group, a Rule or the whole builder
- * @param {Node} [node]
- * @param {object} [options] {@link module:plugins.Invert}
- * @fires module:plugins.Invert.afterInvert
- * @throws InvertConditionError, InvertOperatorError
- */
- invert: function(node, options) {
- if (!(node instanceof Node)) {
- if (!this.model.root) return;
- options = node;
- node = this.model.root;
- }
-
- if (typeof options != 'object') options = {};
- if (options.recursive === undefined) options.recursive = true;
- if (options.invert_rules === undefined) options.invert_rules = true;
- if (options.silent_fail === undefined) options.silent_fail = false;
- if (options.trigger === undefined) options.trigger = true;
-
- if (node instanceof Group) {
- // invert group condition
- if (this.settings.conditionOpposites[node.condition]) {
- node.condition = this.settings.conditionOpposites[node.condition];
- }
- else if (!options.silent_fail) {
- Utils.error('InvertCondition', 'Unknown inverse of condition "{0}"', node.condition);
- }
-
- // recursive call
- if (options.recursive) {
- var tempOpts = $.extend({}, options, { trigger: false });
- node.each(function(rule) {
- if (options.invert_rules) {
- this.invert(rule, tempOpts);
- }
- }, function(group) {
- this.invert(group, tempOpts);
- }, this);
- }
- }
- else if (node instanceof Rule) {
- if (node.operator && !node.filter.no_invert) {
- // invert rule operator
- if (this.settings.operatorOpposites[node.operator.type]) {
- var invert = this.settings.operatorOpposites[node.operator.type];
- // check if the invert is "authorized"
- if (!node.filter.operators || node.filter.operators.indexOf(invert) != -1) {
- node.operator = this.getOperatorByType(invert);
- }
- }
- else if (!options.silent_fail) {
- Utils.error('InvertOperator', 'Unknown inverse of operator "{0}"', node.operator.type);
- }
- }
- }
-
- if (options.trigger) {
- /**
- * After {@link module:plugins.Invert.invert} method
- * @event afterInvert
- * @memberof module:plugins.Invert
- * @param {Node} node - the main group or rule that has been modified
- * @param {object} options
- */
- this.trigger('afterInvert', node, options);
-
- this.trigger('rulesChanged');
- }
- }
- });
-
-
- /**
- * @class MongoDbSupport
- * @memberof module:plugins
- * @description Allows to export rules as a MongoDB find object as well as populating the builder from a MongoDB object.
- */
-
- QueryBuilder.defaults({
- mongoOperators: {
- // @formatter:off
- equal: function(v) { return v[0]; },
- not_equal: function(v) { return { '$ne': v[0] }; },
- in: function(v) { return { '$in': v }; },
- not_in: function(v) { return { '$nin': v }; },
- less: function(v) { return { '$lt': v[0] }; },
- less_or_equal: function(v) { return { '$lte': v[0] }; },
- greater: function(v) { return { '$gt': v[0] }; },
- greater_or_equal: function(v) { return { '$gte': v[0] }; },
- between: function(v) { return { '$gte': v[0], '$lte': v[1] }; },
- not_between: function(v) { return { '$lt': v[0], '$gt': v[1] }; },
- begins_with: function(v) { return { '$regex': '^' + Utils.escapeRegExp(v[0]) }; },
- not_begins_with: function(v) { return { '$regex': '^(?!' + Utils.escapeRegExp(v[0]) + ')' }; },
- contains: function(v) { return { '$regex': Utils.escapeRegExp(v[0]) }; },
- not_contains: function(v) { return { '$regex': '^((?!' + Utils.escapeRegExp(v[0]) + ').)*$', '$options': 's' }; },
- ends_with: function(v) { return { '$regex': Utils.escapeRegExp(v[0]) + '$' }; },
- not_ends_with: function(v) { return { '$regex': '(?<!' + Utils.escapeRegExp(v[0]) + ')$' }; },
- is_empty: function(v) { return ''; },
- is_not_empty: function(v) { return { '$ne': '' }; },
- is_null: function(v) { return null; },
- is_not_null: function(v) { return { '$ne': null }; }
- // @formatter:on
- },
-
- mongoRuleOperators: {
- $eq: function(v) {
- return {
- 'val': v,
- 'op': v === null ? 'is_null' : (v === '' ? 'is_empty' : 'equal')
- };
- },
- $ne: function(v) {
- v = v.$ne;
- return {
- 'val': v,
- 'op': v === null ? 'is_not_null' : (v === '' ? 'is_not_empty' : 'not_equal')
- };
- },
- $regex: function(v) {
- v = v.$regex;
- if (v.slice(0, 4) == '^(?!' && v.slice(-1) == ')') {
- return { 'val': v.slice(4, -1), 'op': 'not_begins_with' };
- }
- else if (v.slice(0, 5) == '^((?!' && v.slice(-5) == ').)*$') {
- return { 'val': v.slice(5, -5), 'op': 'not_contains' };
- }
- else if (v.slice(0, 4) == '(?<!' && v.slice(-2) == ')$') {
- return { 'val': v.slice(4, -2), 'op': 'not_ends_with' };
- }
- else if (v.slice(-1) == '$') {
- return { 'val': v.slice(0, -1), 'op': 'ends_with' };
- }
- else if (v.slice(0, 1) == '^') {
- return { 'val': v.slice(1), 'op': 'begins_with' };
- }
- else {
- return { 'val': v, 'op': 'contains' };
- }
- },
- between: function(v) {
- return { 'val': [v.$gte, v.$lte], 'op': 'between' };
- },
- not_between: function(v) {
- return { 'val': [v.$lt, v.$gt], 'op': 'not_between' };
- },
- $in: function(v) {
- return { 'val': v.$in, 'op': 'in' };
- },
- $nin: function(v) {
- return { 'val': v.$nin, 'op': 'not_in' };
- },
- $lt: function(v) {
- return { 'val': v.$lt, 'op': 'less' };
- },
- $lte: function(v) {
- return { 'val': v.$lte, 'op': 'less_or_equal' };
- },
- $gt: function(v) {
- return { 'val': v.$gt, 'op': 'greater' };
- },
- $gte: function(v) {
- return { 'val': v.$gte, 'op': 'greater_or_equal' };
- }
- }
- });
-
- QueryBuilder.extend(/** @lends module:plugins.MongoDbSupport.prototype */ {
- /**
- * Returns rules as a MongoDB query
- * @param {object} [data] - current rules by default
- * @returns {object}
- * @fires module:plugins.MongoDbSupport.changer:getMongoDBField
- * @fires module:plugins.MongoDbSupport.changer:ruleToMongo
- * @fires module:plugins.MongoDbSupport.changer:groupToMongo
- * @throws UndefinedMongoConditionError, UndefinedMongoOperatorError
- */
- getMongo: function(data) {
- data = (data === undefined) ? this.getRules() : data;
-
- if (!data) {
- return null;
- }
-
- var self = this;
-
- return (function parse(group) {
- if (!group.condition) {
- group.condition = self.settings.default_condition;
- }
- if (['AND', 'OR'].indexOf(group.condition.toUpperCase()) === -1) {
- Utils.error('UndefinedMongoCondition', 'Unable to build MongoDB query with condition "{0}"', group.condition);
- }
-
- if (!group.rules) {
- return {};
- }
-
- var parts = [];
-
- group.rules.forEach(function(rule) {
- if (rule.rules && rule.rules.length > 0) {
- parts.push(parse(rule));
- }
- else {
- var mdb = self.settings.mongoOperators[rule.operator];
- var ope = self.getOperatorByType(rule.operator);
-
- if (mdb === undefined) {
- Utils.error('UndefinedMongoOperator', 'Unknown MongoDB operation for operator "{0}"', rule.operator);
- }
-
- if (ope.nb_inputs !== 0) {
- if (!(rule.value instanceof Array)) {
- rule.value = [rule.value];
- }
- }
-
- /**
- * Modifies the MongoDB field used by a rule
- * @event changer:getMongoDBField
- * @memberof module:plugins.MongoDbSupport
- * @param {string} field
- * @param {Rule} rule
- * @returns {string}
- */
- var field = self.change('getMongoDBField', rule.field, rule);
-
- var ruleExpression = {};
- ruleExpression[field] = mdb.call(self, rule.value);
-
- /**
- * Modifies the MongoDB expression generated for a rul
- * @event changer:ruleToMongo
- * @memberof module:plugins.MongoDbSupport
- * @param {object} expression
- * @param {Rule} rule
- * @param {*} value
- * @param {function} valueWrapper - function that takes the value and adds the operator
- * @returns {object}
- */
- parts.push(self.change('ruleToMongo', ruleExpression, rule, rule.value, mdb));
- }
- });
-
- var groupExpression = {};
- groupExpression['$' + group.condition.toLowerCase()] = parts;
-
- /**
- * Modifies the MongoDB expression generated for a group
- * @event changer:groupToMongo
- * @memberof module:plugins.MongoDbSupport
- * @param {object} expression
- * @param {Group} group
- * @returns {object}
- */
- return self.change('groupToMongo', groupExpression, group);
- }(data));
- },
-
- /**
- * Converts a MongoDB query to rules
- * @param {object} query
- * @returns {object}
- * @fires module:plugins.MongoDbSupport.changer:parseMongoNode
- * @fires module:plugins.MongoDbSupport.changer:getMongoDBFieldID
- * @fires module:plugins.MongoDbSupport.changer:mongoToRule
- * @fires module:plugins.MongoDbSupport.changer:mongoToGroup
- * @throws MongoParseError, UndefinedMongoConditionError, UndefinedMongoOperatorError
- */
- getRulesFromMongo: function(query) {
- if (query === undefined || query === null) {
- return null;
- }
-
- var self = this;
-
- /**
- * Custom parsing of a MongoDB expression, you can return a sub-part of the expression, or a well formed group or rule JSON
- * @event changer:parseMongoNode
- * @memberof module:plugins.MongoDbSupport
- * @param {object} expression
- * @returns {object} expression, rule or group
- */
- query = self.change('parseMongoNode', query);
-
- // a plugin returned a group
- if ('rules' in query && 'condition' in query) {
- return query;
- }
-
- // a plugin returned a rule
- if ('id' in query && 'operator' in query && 'value' in query) {
- return {
- condition: this.settings.default_condition,
- rules: [query]
- };
- }
-
- var key = self.getMongoCondition(query);
- if (!key) {
- Utils.error('MongoParse', 'Invalid MongoDB query format');
- }
-
- return (function parse(data, topKey) {
- var rules = data[topKey];
- var parts = [];
-
- rules.forEach(function(data) {
- // allow plugins to manually parse or handle special cases
- data = self.change('parseMongoNode', data);
-
- // a plugin returned a group
- if ('rules' in data && 'condition' in data) {
- parts.push(data);
- return;
- }
-
- // a plugin returned a rule
- if ('id' in data && 'operator' in data && 'value' in data) {
- parts.push(data);
- return;
- }
-
- var key = self.getMongoCondition(data);
- if (key) {
- parts.push(parse(data, key));
- }
- else {
- var field = Object.keys(data)[0];
- var value = data[field];
-
- var operator = self.getMongoOperator(value);
- if (operator === undefined) {
- Utils.error('MongoParse', 'Invalid MongoDB query format');
- }
-
- var mdbrl = self.settings.mongoRuleOperators[operator];
- if (mdbrl === undefined) {
- Utils.error('UndefinedMongoOperator', 'JSON Rule operation unknown for operator "{0}"', operator);
- }
-
- var opVal = mdbrl.call(self, value);
-
- var id = self.getMongoDBFieldID(field, value);
-
- /**
- * Modifies the rule generated from the MongoDB expression
- * @event changer:mongoToRule
- * @memberof module:plugins.MongoDbSupport
- * @param {object} rule
- * @param {object} expression
- * @returns {object}
- */
- var rule = self.change('mongoToRule', {
- id: id,
- field: field,
- operator: opVal.op,
- value: opVal.val
- }, data);
-
- parts.push(rule);
- }
- });
-
- /**
- * Modifies the group generated from the MongoDB expression
- * @event changer:mongoToGroup
- * @memberof module:plugins.MongoDbSupport
- * @param {object} group
- * @param {object} expression
- * @returns {object}
- */
- return self.change('mongoToGroup', {
- condition: topKey.replace('$', '').toUpperCase(),
- rules: parts
- }, data);
- }(query, key));
- },
-
- /**
- * Sets rules a from MongoDB query
- * @see module:plugins.MongoDbSupport.getRulesFromMongo
- */
- setRulesFromMongo: function(query) {
- this.setRules(this.getRulesFromMongo(query));
- },
-
- /**
- * Returns a filter identifier from the MongoDB field.
- * Automatically use the only one filter with a matching field, fires a changer otherwise.
- * @param {string} field
- * @param {*} value
- * @fires module:plugins.MongoDbSupport:changer:getMongoDBFieldID
- * @returns {string}
- * @private
- */
- getMongoDBFieldID: function(field, value) {
- var matchingFilters = this.filters.filter(function(filter) {
- return filter.field === field;
- });
-
- var id;
- if (matchingFilters.length === 1) {
- id = matchingFilters[0].id;
- }
- else {
- /**
- * Returns a filter identifier from the MongoDB field
- * @event changer:getMongoDBFieldID
- * @memberof module:plugins.MongoDbSupport
- * @param {string} field
- * @param {*} value
- * @returns {string}
- */
- id = this.change('getMongoDBFieldID', field, value);
- }
-
- return id;
- },
-
- /**
- * Finds which operator is used in a MongoDB sub-object
- * @param {*} data
- * @returns {string|undefined}
- * @private
- */
- getMongoOperator: function(data) {
- if (data !== null && typeof data === 'object') {
- if (data.$gte !== undefined && data.$lte !== undefined) {
- return 'between';
- }
- if (data.$lt !== undefined && data.$gt !== undefined) {
- return 'not_between';
- }
-
- var knownKeys = Object.keys(data).filter(function(key) {
- return !!this.settings.mongoRuleOperators[key];
- }.bind(this));
-
- if (knownKeys.length === 1) {
- return knownKeys[0];
- }
- }
- else {
- return '$eq';
- }
- },
-
-
- /**
- * Returns the key corresponding to "$or" or "$and"
- * @param {object} data
- * @returns {string|undefined}
- * @private
- */
- getMongoCondition: function(data) {
- var keys = Object.keys(data);
-
- for (var i = 0, l = keys.length; i < l; i++) {
- if (keys[i].toLowerCase() === '$or' || keys[i].toLowerCase() === '$and') {
- return keys[i];
- }
- }
- }
- });
-
-
- /**
- * @class NotGroup
- * @memberof module:plugins
- * @description Adds a "Not" checkbox in front of group conditions.
- * @param {object} [options]
- * @param {string} [options.icon_checked='glyphicon glyphicon-checked']
- * @param {string} [options.icon_unchecked='glyphicon glyphicon-unchecked']
- */
- QueryBuilder.define('not-group', function(options) {
- var self = this;
-
- // Bind events
- this.on('afterInit', function() {
- self.$el.on('click.queryBuilder', '[data-not=group]', function() {
- var $group = $(this).closest(QueryBuilder.selectors.group_container);
- var group = self.getModel($group);
- group.not = !group.not;
- });
-
- self.model.on('update', function(e, node, field) {
- if (node instanceof Group && field === 'not') {
- self.updateGroupNot(node);
- }
- });
- });
-
- // Init "not" property
- this.on('afterAddGroup', function(e, group) {
- group.__.not = false;
- });
-
- // Modify templates
- if (!options.disable_template) {
- this.on('getGroupTemplate.filter', function(h) {
- var $h = $(h.value);
- $h.find(QueryBuilder.selectors.condition_container).prepend(
- '<button type="button" class="btn btn-xs btn-default" data-not="group">' +
- '<i class="' + options.icon_unchecked + '"></i> ' + self.translate('NOT') +
- '</button>'
- );
- h.value = $h.prop('outerHTML');
- });
- }
-
- // Export "not" to JSON
- this.on('groupToJson.filter', function(e, group) {
- e.value.not = group.not;
- });
-
- // Read "not" from JSON
- this.on('jsonToGroup.filter', function(e, json) {
- e.value.not = !!json.not;
- });
-
- // Export "not" to SQL
- this.on('groupToSQL.filter', function(e, group) {
- if (group.not) {
- e.value = 'NOT ( ' + e.value + ' )';
- }
- });
-
- // Parse "NOT" function from sqlparser
- this.on('parseSQLNode.filter', function(e) {
- if (e.value.name && e.value.name.toUpperCase() == 'NOT') {
- e.value = e.value.arguments.value[0];
-
- // if the there is no sub-group, create one
- if (['AND', 'OR'].indexOf(e.value.operation.toUpperCase()) === -1) {
- e.value = new SQLParser.nodes.Op(
- self.settings.default_condition,
- e.value,
- null
- );
- }
-
- e.value.not = true;
- }
- });
-
- // Request to create sub-group if the "not" flag is set
- this.on('sqlGroupsDistinct.filter', function(e, group, data, i) {
- if (data.not && i > 0) {
- e.value = true;
- }
- });
-
- // Read "not" from parsed SQL
- this.on('sqlToGroup.filter', function(e, data) {
- e.value.not = !!data.not;
- });
-
- // Export "not" to Mongo
- this.on('groupToMongo.filter', function(e, group) {
- var key = '$' + group.condition.toLowerCase();
- if (group.not && e.value[key]) {
- e.value = { '$nor': [e.value] };
- }
- });
-
- // Parse "$nor" operator from Mongo
- this.on('parseMongoNode.filter', function(e) {
- var keys = Object.keys(e.value);
-
- if (keys[0] == '$nor') {
- e.value = e.value[keys[0]][0];
- e.value.not = true;
- }
- });
-
- // Read "not" from parsed Mongo
- this.on('mongoToGroup.filter', function(e, data) {
- e.value.not = !!data.not;
- });
- }, {
- icon_unchecked: 'glyphicon glyphicon-unchecked',
- icon_checked: 'glyphicon glyphicon-check',
- disable_template: false
- });
-
- /**
- * From {@link module:plugins.NotGroup}
- * @name not
- * @member {boolean}
- * @memberof Group
- * @instance
- */
- Utils.defineModelProperties(Group, ['not']);
-
- QueryBuilder.selectors.group_not = QueryBuilder.selectors.group_header + ' [data-not=group]';
-
- QueryBuilder.extend(/** @lends module:plugins.NotGroup.prototype */ {
- /**
- * Performs actions when a group's not changes
- * @param {Group} group
- * @fires module:plugins.NotGroup.afterUpdateGroupNot
- * @private
- */
- updateGroupNot: function(group) {
- var options = this.plugins['not-group'];
- group.$el.find('>' + QueryBuilder.selectors.group_not)
- .toggleClass('active', group.not)
- .find('i').attr('class', group.not ? options.icon_checked : options.icon_unchecked);
-
- /**
- * After the group's not flag has been modified
- * @event afterUpdateGroupNot
- * @memberof module:plugins.NotGroup
- * @param {Group} group
- */
- this.trigger('afterUpdateGroupNot', group);
-
- this.trigger('rulesChanged');
- }
- });
-
-
- /**
- * @class Sortable
- * @memberof module:plugins
- * @description Enables drag & drop sort of rules.
- * @param {object} [options]
- * @param {boolean} [options.inherit_no_drop=true]
- * @param {boolean} [options.inherit_no_sortable=true]
- * @param {string} [options.icon='glyphicon glyphicon-sort']
- * @throws MissingLibraryError, ConfigError
- */
- QueryBuilder.define('sortable', function(options) {
- if (!('interact' in window)) {
- Utils.error('MissingLibrary', 'interact.js is required to use "sortable" plugin. Get it here: http://interactjs.io');
- }
-
- if (options.default_no_sortable !== undefined) {
- Utils.error(false, 'Config', 'Sortable plugin : "default_no_sortable" options is deprecated, use standard "default_rule_flags" and "default_group_flags" instead');
- this.settings.default_rule_flags.no_sortable = this.settings.default_group_flags.no_sortable = options.default_no_sortable;
- }
-
- // recompute drop-zones during drag (when a rule is hidden)
- interact.dynamicDrop(true);
-
- // set move threshold to 10px
- interact.pointerMoveTolerance(10);
-
- var placeholder;
- var ghost;
- var src;
- var moved;
-
- // Init drag and drop
- this.on('afterAddRule afterAddGroup', function(e, node) {
- if (node == placeholder) {
- return;
- }
-
- var self = e.builder;
-
- // Inherit flags
- if (options.inherit_no_sortable && node.parent && node.parent.flags.no_sortable) {
- node.flags.no_sortable = true;
- }
- if (options.inherit_no_drop && node.parent && node.parent.flags.no_drop) {
- node.flags.no_drop = true;
- }
-
- // Configure drag
- if (!node.flags.no_sortable) {
- interact(node.$el[0])
- .draggable({
- allowFrom: QueryBuilder.selectors.drag_handle,
- onstart: function(event) {
- moved = false;
-
- // get model of dragged element
- src = self.getModel(event.target);
-
- // create ghost
- ghost = src.$el.clone()
- .appendTo(src.$el.parent())
- .width(src.$el.outerWidth())
- .addClass('dragging');
-
- // create drop placeholder
- var ph = $('<div class="rule-placeholder"> </div>')
- .height(src.$el.outerHeight());
-
- placeholder = src.parent.addRule(ph, src.getPos());
-
- // hide dragged element
- src.$el.hide();
- },
- onmove: function(event) {
- // make the ghost follow the cursor
- ghost[0].style.top = event.clientY - 15 + 'px';
- ghost[0].style.left = event.clientX - 15 + 'px';
- },
- onend: function(event) {
- // starting from Interact 1.3.3, onend is called before ondrop
- if (event.dropzone) {
- moveSortableToTarget(src, $(event.relatedTarget), self);
- moved = true;
- }
-
- // remove ghost
- ghost.remove();
- ghost = undefined;
-
- // remove placeholder
- placeholder.drop();
- placeholder = undefined;
-
- // show element
- src.$el.css('display', '');
-
- /**
- * After a node has been moved with {@link module:plugins.Sortable}
- * @event afterMove
- * @memberof module:plugins.Sortable
- * @param {Node} node
- */
- self.trigger('afterMove', src);
-
- self.trigger('rulesChanged');
- }
- });
- }
-
- if (!node.flags.no_drop) {
- // Configure drop on groups and rules
- interact(node.$el[0])
- .dropzone({
- accept: QueryBuilder.selectors.rule_and_group_containers,
- ondragenter: function(event) {
- moveSortableToTarget(placeholder, $(event.target), self);
- },
- ondrop: function(event) {
- if (!moved) {
- moveSortableToTarget(src, $(event.target), self);
- }
- }
- });
-
- // Configure drop on group headers
- if (node instanceof Group) {
- interact(node.$el.find(QueryBuilder.selectors.group_header)[0])
- .dropzone({
- accept: QueryBuilder.selectors.rule_and_group_containers,
- ondragenter: function(event) {
- moveSortableToTarget(placeholder, $(event.target), self);
- },
- ondrop: function(event) {
- if (!moved) {
- moveSortableToTarget(src, $(event.target), self);
- }
- }
- });
- }
- }
- });
-
- // Detach interactables
- this.on('beforeDeleteRule beforeDeleteGroup', function(e, node) {
- if (!e.isDefaultPrevented()) {
- interact(node.$el[0]).unset();
-
- if (node instanceof Group) {
- interact(node.$el.find(QueryBuilder.selectors.group_header)[0]).unset();
- }
- }
- });
-
- // Remove drag handle from non-sortable items
- this.on('afterApplyRuleFlags afterApplyGroupFlags', function(e, node) {
- if (node.flags.no_sortable) {
- node.$el.find('.drag-handle').remove();
- }
- });
-
- // Modify templates
- if (!options.disable_template) {
- this.on('getGroupTemplate.filter', function(h, level) {
- if (level > 1) {
- var $h = $(h.value);
- $h.find(QueryBuilder.selectors.condition_container).after('<div class="drag-handle"><i class="' + options.icon + '"></i></div>');
- h.value = $h.prop('outerHTML');
- }
- });
-
- this.on('getRuleTemplate.filter', function(h) {
- var $h = $(h.value);
- $h.find(QueryBuilder.selectors.rule_header).after('<div class="drag-handle"><i class="' + options.icon + '"></i></div>');
- h.value = $h.prop('outerHTML');
- });
- }
- }, {
- inherit_no_sortable: true,
- inherit_no_drop: true,
- icon: 'glyphicon glyphicon-sort',
- disable_template: false
- });
-
- QueryBuilder.selectors.rule_and_group_containers = QueryBuilder.selectors.rule_container + ', ' + QueryBuilder.selectors.group_container;
- QueryBuilder.selectors.drag_handle = '.drag-handle';
-
- QueryBuilder.defaults({
- default_rule_flags: {
- no_sortable: false,
- no_drop: false
- },
- default_group_flags: {
- no_sortable: false,
- no_drop: false
- }
- });
-
- /**
- * Moves an element (placeholder or actual object) depending on active target
- * @memberof module:plugins.Sortable
- * @param {Node} node
- * @param {jQuery} target
- * @param {QueryBuilder} [builder]
- * @private
- */
- function moveSortableToTarget(node, target, builder) {
- var parent, method;
- var Selectors = QueryBuilder.selectors;
-
- // on rule
- parent = target.closest(Selectors.rule_container);
- if (parent.length) {
- method = 'moveAfter';
- }
-
- // on group header
- if (!method) {
- parent = target.closest(Selectors.group_header);
- if (parent.length) {
- parent = target.closest(Selectors.group_container);
- method = 'moveAtBegin';
- }
- }
-
- // on group
- if (!method) {
- parent = target.closest(Selectors.group_container);
- if (parent.length) {
- method = 'moveAtEnd';
- }
- }
-
- if (method) {
- node[method](builder.getModel(parent));
-
- // refresh radio value
- if (builder && node instanceof Rule) {
- builder.setRuleInputValue(node, node.value);
- }
- }
- }
-
-
- /**
- * @class SqlSupport
- * @memberof module:plugins
- * @description Allows to export rules as a SQL WHERE statement as well as populating the builder from an SQL query.
- * @param {object} [options]
- * @param {boolean} [options.boolean_as_integer=true] - `true` to convert boolean values to integer in the SQL output
- */
- QueryBuilder.define('sql-support', function(options) {
-
- }, {
- boolean_as_integer: true
- });
-
- QueryBuilder.defaults({
- // operators for internal -> SQL conversion
- sqlOperators: {
- equal: { op: '= ?' },
- not_equal: { op: '!= ?' },
- in: { op: 'IN(?)', sep: ', ' },
- not_in: { op: 'NOT IN(?)', sep: ', ' },
- less: { op: '< ?' },
- less_or_equal: { op: '<= ?' },
- greater: { op: '> ?' },
- greater_or_equal: { op: '>= ?' },
- between: { op: 'BETWEEN ?', sep: ' AND ' },
- not_between: { op: 'NOT BETWEEN ?', sep: ' AND ' },
- begins_with: { op: 'LIKE(?)', mod: '{0}%' },
- not_begins_with: { op: 'NOT LIKE(?)', mod: '{0}%' },
- contains: { op: 'LIKE(?)', mod: '%{0}%' },
- not_contains: { op: 'NOT LIKE(?)', mod: '%{0}%' },
- ends_with: { op: 'LIKE(?)', mod: '%{0}' },
- not_ends_with: { op: 'NOT LIKE(?)', mod: '%{0}' },
- is_empty: { op: '= \'\'' },
- is_not_empty: { op: '!= \'\'' },
- is_null: { op: 'IS NULL' },
- is_not_null: { op: 'IS NOT NULL' }
- },
-
- // operators for SQL -> internal conversion
- sqlRuleOperator: {
- '=': function(v) {
- return {
- val: v,
- op: v === '' ? 'is_empty' : 'equal'
- };
- },
- '!=': function(v) {
- return {
- val: v,
- op: v === '' ? 'is_not_empty' : 'not_equal'
- };
- },
- 'LIKE': function(v) {
- if (v.slice(0, 1) == '%' && v.slice(-1) == '%') {
- return {
- val: v.slice(1, -1),
- op: 'contains'
- };
- }
- else if (v.slice(0, 1) == '%') {
- return {
- val: v.slice(1),
- op: 'ends_with'
- };
- }
- else if (v.slice(-1) == '%') {
- return {
- val: v.slice(0, -1),
- op: 'begins_with'
- };
- }
- else {
- Utils.error('SQLParse', 'Invalid value for LIKE operator "{0}"', v);
- }
- },
- 'NOT LIKE': function(v) {
- if (v.slice(0, 1) == '%' && v.slice(-1) == '%') {
- return {
- val: v.slice(1, -1),
- op: 'not_contains'
- };
- }
- else if (v.slice(0, 1) == '%') {
- return {
- val: v.slice(1),
- op: 'not_ends_with'
- };
- }
- else if (v.slice(-1) == '%') {
- return {
- val: v.slice(0, -1),
- op: 'not_begins_with'
- };
- }
- else {
- Utils.error('SQLParse', 'Invalid value for NOT LIKE operator "{0}"', v);
- }
- },
- 'IN': function(v) {
- return { val: v, op: 'in' };
- },
- 'NOT IN': function(v) {
- return { val: v, op: 'not_in' };
- },
- '<': function(v) {
- return { val: v, op: 'less' };
- },
- '<=': function(v) {
- return { val: v, op: 'less_or_equal' };
- },
- '>': function(v) {
- return { val: v, op: 'greater' };
- },
- '>=': function(v) {
- return { val: v, op: 'greater_or_equal' };
- },
- 'BETWEEN': function(v) {
- return { val: v, op: 'between' };
- },
- 'NOT BETWEEN': function(v) {
- return { val: v, op: 'not_between' };
- },
- 'IS': function(v) {
- if (v !== null) {
- Utils.error('SQLParse', 'Invalid value for IS operator');
- }
- return { val: null, op: 'is_null' };
- },
- 'IS NOT': function(v) {
- if (v !== null) {
- Utils.error('SQLParse', 'Invalid value for IS operator');
- }
- return { val: null, op: 'is_not_null' };
- }
- },
-
- // statements for internal -> SQL conversion
- sqlStatements: {
- 'question_mark': function() {
- var params = [];
- return {
- add: function(rule, value) {
- params.push(value);
- return '?';
- },
- run: function() {
- return params;
- }
- };
- },
-
- 'numbered': function(char) {
- if (!char || char.length > 1) char = '$';
- var index = 0;
- var params = [];
- return {
- add: function(rule, value) {
- params.push(value);
- index++;
- return char + index;
- },
- run: function() {
- return params;
- }
- };
- },
-
- 'named': function(char) {
- if (!char || char.length > 1) char = ':';
- var indexes = {};
- var params = {};
- return {
- add: function(rule, value) {
- if (!indexes[rule.field]) indexes[rule.field] = 1;
- var key = rule.field + '_' + (indexes[rule.field]++);
- params[key] = value;
- return char + key;
- },
- run: function() {
- return params;
- }
- };
- }
- },
-
- // statements for SQL -> internal conversion
- sqlRuleStatement: {
- 'question_mark': function(values) {
- var index = 0;
- return {
- parse: function(v) {
- return v == '?' ? values[index++] : v;
- },
- esc: function(sql) {
- return sql.replace(/\?/g, '\'?\'');
- }
- };
- },
-
- 'numbered': function(values, char) {
- if (!char || char.length > 1) char = '$';
- var regex1 = new RegExp('^\\' + char + '[0-9]+$');
- var regex2 = new RegExp('\\' + char + '([0-9]+)', 'g');
- return {
- parse: function(v) {
- return regex1.test(v) ? values[v.slice(1) - 1] : v;
- },
- esc: function(sql) {
- return sql.replace(regex2, '\'' + (char == '$' ? '$$' : char) + '$1\'');
- }
- };
- },
-
- 'named': function(values, char) {
- if (!char || char.length > 1) char = ':';
- var regex1 = new RegExp('^\\' + char);
- var regex2 = new RegExp('\\' + char + '(' + Object.keys(values).join('|') + ')', 'g');
- return {
- parse: function(v) {
- return regex1.test(v) ? values[v.slice(1)] : v;
- },
- esc: function(sql) {
- return sql.replace(regex2, '\'' + (char == '$' ? '$$' : char) + '$1\'');
- }
- };
- }
- }
- });
-
- /**
- * @typedef {object} SqlQuery
- * @memberof module:plugins.SqlSupport
- * @property {string} sql
- * @property {object} params
- */
-
- QueryBuilder.extend(/** @lends module:plugins.SqlSupport.prototype */ {
- /**
- * Returns rules as a SQL query
- * @param {boolean|string} [stmt] - use prepared statements: false, 'question_mark', 'numbered', 'numbered(@)', 'named', 'named(@)'
- * @param {boolean} [nl=false] output with new lines
- * @param {object} [data] - current rules by default
- * @returns {module:plugins.SqlSupport.SqlQuery}
- * @fires module:plugins.SqlSupport.changer:getSQLField
- * @fires module:plugins.SqlSupport.changer:ruleToSQL
- * @fires module:plugins.SqlSupport.changer:groupToSQL
- * @throws UndefinedSQLConditionError, UndefinedSQLOperatorError
- */
- getSQL: function(stmt, nl, data) {
- data = (data === undefined) ? this.getRules() : data;
-
- if (!data) {
- return null;
- }
-
- nl = !!nl ? '\n' : ' ';
- var boolean_as_integer = this.getPluginOptions('sql-support', 'boolean_as_integer');
-
- if (stmt === true) {
- stmt = 'question_mark';
- }
- if (typeof stmt == 'string') {
- var config = getStmtConfig(stmt);
- stmt = this.settings.sqlStatements[config[1]](config[2]);
- }
-
- var self = this;
-
- var sql = (function parse(group) {
- if (!group.condition) {
- group.condition = self.settings.default_condition;
- }
- if (['AND', 'OR'].indexOf(group.condition.toUpperCase()) === -1) {
- Utils.error('UndefinedSQLCondition', 'Unable to build SQL query with condition "{0}"', group.condition);
- }
-
- if (!group.rules) {
- return '';
- }
-
- var parts = [];
-
- group.rules.forEach(function(rule) {
- if (rule.rules && rule.rules.length > 0) {
- parts.push('(' + nl + parse(rule) + nl + ')' + nl);
- }
- else {
- var sql = self.settings.sqlOperators[rule.operator];
- var ope = self.getOperatorByType(rule.operator);
- var value = '';
-
- if (sql === undefined) {
- Utils.error('UndefinedSQLOperator', 'Unknown SQL operation for operator "{0}"', rule.operator);
- }
-
- if (ope.nb_inputs !== 0) {
- if (!(rule.value instanceof Array)) {
- rule.value = [rule.value];
- }
-
- rule.value.forEach(function(v, i) {
- if (i > 0) {
- value += sql.sep;
- }
-
- if (rule.type == 'boolean' && boolean_as_integer) {
- v = v ? 1 : 0;
- }
- else if (!stmt && rule.type !== 'integer' && rule.type !== 'double' && rule.type !== 'boolean') {
- v = Utils.escapeString(v);
- }
-
- if (sql.mod) {
- v = Utils.fmt(sql.mod, v);
- }
-
- if (stmt) {
- value += stmt.add(rule, v);
- }
- else {
- if (typeof v == 'string') {
- v = '\'' + v + '\'';
- }
-
- value += v;
- }
- });
- }
-
- var sqlFn = function(v) {
- return sql.op.replace('?', function() {
- return v;
- });
- };
-
- /**
- * Modifies the SQL field used by a rule
- * @event changer:getSQLField
- * @memberof module:plugins.SqlSupport
- * @param {string} field
- * @param {Rule} rule
- * @returns {string}
- */
- var field = self.change('getSQLField', rule.field, rule);
-
- var ruleExpression = field + ' ' + sqlFn(value);
-
- /**
- * Modifies the SQL generated for a rule
- * @event changer:ruleToSQL
- * @memberof module:plugins.SqlSupport
- * @param {string} expression
- * @param {Rule} rule
- * @param {*} value
- * @param {function} valueWrapper - function that takes the value and adds the operator
- * @returns {string}
- */
- parts.push(self.change('ruleToSQL', ruleExpression, rule, value, sqlFn));
- }
- });
-
- var groupExpression = parts.join(' ' + group.condition + nl);
-
- /**
- * Modifies the SQL generated for a group
- * @event changer:groupToSQL
- * @memberof module:plugins.SqlSupport
- * @param {string} expression
- * @param {Group} group
- * @returns {string}
- */
- return self.change('groupToSQL', groupExpression, group);
- }(data));
-
- if (stmt) {
- return {
- sql: sql,
- params: stmt.run()
- };
- }
- else {
- return {
- sql: sql
- };
- }
- },
-
- /**
- * Convert a SQL query to rules
- * @param {string|module:plugins.SqlSupport.SqlQuery} query
- * @param {boolean|string} stmt
- * @returns {object}
- * @fires module:plugins.SqlSupport.changer:parseSQLNode
- * @fires module:plugins.SqlSupport.changer:getSQLFieldID
- * @fires module:plugins.SqlSupport.changer:sqlToRule
- * @fires module:plugins.SqlSupport.changer:sqlToGroup
- * @throws MissingLibraryError, SQLParseError, UndefinedSQLOperatorError
- */
- getRulesFromSQL: function(query, stmt) {
- if (!('SQLParser' in window)) {
- Utils.error('MissingLibrary', 'SQLParser is required to parse SQL queries. Get it here https://github.com/mistic100/sql-parser');
- }
-
- var self = this;
-
- if (typeof query == 'string') {
- query = { sql: query };
- }
-
- if (stmt === true) stmt = 'question_mark';
- if (typeof stmt == 'string') {
- var config = getStmtConfig(stmt);
- stmt = this.settings.sqlRuleStatement[config[1]](query.params, config[2]);
- }
-
- if (stmt) {
- query.sql = stmt.esc(query.sql);
- }
-
- if (query.sql.toUpperCase().indexOf('SELECT') !== 0) {
- query.sql = 'SELECT * FROM table WHERE ' + query.sql;
- }
-
- var parsed = SQLParser.parse(query.sql);
-
- if (!parsed.where) {
- Utils.error('SQLParse', 'No WHERE clause found');
- }
-
- /**
- * Custom parsing of an AST node generated by SQLParser, you can return a sub-part of the tree, or a well formed group or rule JSON
- * @event changer:parseSQLNode
- * @memberof module:plugins.SqlSupport
- * @param {object} AST node
- * @returns {object} tree, rule or group
- */
- var data = self.change('parseSQLNode', parsed.where.conditions);
-
- // a plugin returned a group
- if ('rules' in data && 'condition' in data) {
- return data;
- }
-
- // a plugin returned a rule
- if ('id' in data && 'operator' in data && 'value' in data) {
- return {
- condition: this.settings.default_condition,
- rules: [data]
- };
- }
-
- // create root group
- var out = self.change('sqlToGroup', {
- condition: this.settings.default_condition,
- rules: []
- }, data);
-
- // keep track of current group
- var curr = out;
-
- (function flatten(data, i) {
- if (data === null) {
- return;
- }
-
- // allow plugins to manually parse or handle special cases
- data = self.change('parseSQLNode', data);
-
- // a plugin returned a group
- if ('rules' in data && 'condition' in data) {
- curr.rules.push(data);
- return;
- }
-
- // a plugin returned a rule
- if ('id' in data && 'operator' in data && 'value' in data) {
- curr.rules.push(data);
- return;
- }
-
- // data must be a SQL parser node
- if (!('left' in data) || !('right' in data) || !('operation' in data)) {
- Utils.error('SQLParse', 'Unable to parse WHERE clause');
- }
-
- // it's a node
- if (['AND', 'OR'].indexOf(data.operation.toUpperCase()) !== -1) {
- // create a sub-group if the condition is not the same and it's not the first level
-
- /**
- * Given an existing group and an AST node, determines if a sub-group must be created
- * @event changer:sqlGroupsDistinct
- * @memberof module:plugins.SqlSupport
- * @param {boolean} create - true by default if the group condition is different
- * @param {object} group
- * @param {object} AST
- * @param {int} current group level
- * @returns {boolean}
- */
- var createGroup = self.change('sqlGroupsDistinct', i > 0 && curr.condition != data.operation.toUpperCase(), curr, data, i);
-
- if (createGroup) {
- /**
- * Modifies the group generated from the SQL expression (this is called before the group is filled with rules)
- * @event changer:sqlToGroup
- * @memberof module:plugins.SqlSupport
- * @param {object} group
- * @param {object} AST
- * @returns {object}
- */
- var group = self.change('sqlToGroup', {
- condition: self.settings.default_condition,
- rules: []
- }, data);
-
- curr.rules.push(group);
- curr = group;
- }
-
- curr.condition = data.operation.toUpperCase();
- i++;
-
- // some magic !
- var next = curr;
- flatten(data.left, i);
-
- curr = next;
- flatten(data.right, i);
- }
- // it's a leaf
- else {
- if ($.isPlainObject(data.right.value)) {
- Utils.error('SQLParse', 'Value format not supported for {0}.', data.left.value);
- }
-
- // convert array
- var value;
- if ($.isArray(data.right.value)) {
- value = data.right.value.map(function(v) {
- return v.value;
- });
- }
- else {
- value = data.right.value;
- }
-
- // get actual values
- if (stmt) {
- if ($.isArray(value)) {
- value = value.map(stmt.parse);
- }
- else {
- value = stmt.parse(value);
- }
- }
-
- // convert operator
- var operator = data.operation.toUpperCase();
- if (operator == '<>') {
- operator = '!=';
- }
-
- var sqlrl = self.settings.sqlRuleOperator[operator];
- if (sqlrl === undefined) {
- Utils.error('UndefinedSQLOperator', 'Invalid SQL operation "{0}".', data.operation);
- }
-
- var opVal = sqlrl.call(this, value, data.operation);
-
- // find field name
- var field;
- if ('values' in data.left) {
- field = data.left.values.join('.');
- }
- else if ('value' in data.left) {
- field = data.left.value;
- }
- else {
- Utils.error('SQLParse', 'Cannot find field name in {0}', JSON.stringify(data.left));
- }
-
- var id = self.getSQLFieldID(field, value);
-
- /**
- * Modifies the rule generated from the SQL expression
- * @event changer:sqlToRule
- * @memberof module:plugins.SqlSupport
- * @param {object} rule
- * @param {object} AST
- * @returns {object}
- */
- var rule = self.change('sqlToRule', {
- id: id,
- field: field,
- operator: opVal.op,
- value: opVal.val
- }, data);
-
- curr.rules.push(rule);
- }
- }(data, 0));
-
- return out;
- },
-
- /**
- * Sets the builder's rules from a SQL query
- * @see module:plugins.SqlSupport.getRulesFromSQL
- */
- setRulesFromSQL: function(query, stmt) {
- this.setRules(this.getRulesFromSQL(query, stmt));
- },
-
- /**
- * Returns a filter identifier from the SQL field.
- * Automatically use the only one filter with a matching field, fires a changer otherwise.
- * @param {string} field
- * @param {*} value
- * @fires module:plugins.SqlSupport:changer:getSQLFieldID
- * @returns {string}
- * @private
- */
- getSQLFieldID: function(field, value) {
- var matchingFilters = this.filters.filter(function(filter) {
- return filter.field.toLowerCase() === field.toLowerCase();
- });
-
- var id;
- if (matchingFilters.length === 1) {
- id = matchingFilters[0].id;
- }
- else {
- /**
- * Returns a filter identifier from the SQL field
- * @event changer:getSQLFieldID
- * @memberof module:plugins.SqlSupport
- * @param {string} field
- * @param {*} value
- * @returns {string}
- */
- id = this.change('getSQLFieldID', field, value);
- }
-
- return id;
- }
- });
-
- /**
- * Parses the statement configuration
- * @memberof module:plugins.SqlSupport
- * @param {string} stmt
- * @returns {Array} null, mode, option
- * @private
- */
- function getStmtConfig(stmt) {
- var config = stmt.match(/(question_mark|numbered|named)(?:\((.)\))?/);
- if (!config) config = [null, 'question_mark', undefined];
- return config;
- }
-
-
- /**
- * @class UniqueFilter
- * @memberof module:plugins
- * @description Allows to define some filters as "unique": ie which can be used for only one rule, globally or in the same group.
- */
- QueryBuilder.define('unique-filter', function() {
- this.status.used_filters = {};
-
- this.on('afterUpdateRuleFilter', this.updateDisabledFilters);
- this.on('afterDeleteRule', this.updateDisabledFilters);
- this.on('afterCreateRuleFilters', this.applyDisabledFilters);
- this.on('afterReset', this.clearDisabledFilters);
- this.on('afterClear', this.clearDisabledFilters);
-
- // Ensure that the default filter is not already used if unique
- this.on('getDefaultFilter.filter', function(e, model) {
- var self = e.builder;
-
- self.updateDisabledFilters();
-
- if (e.value.id in self.status.used_filters) {
- var found = self.filters.some(function(filter) {
- if (!(filter.id in self.status.used_filters) || self.status.used_filters[filter.id].length > 0 && self.status.used_filters[filter.id].indexOf(model.parent) === -1) {
- e.value = filter;
- return true;
- }
- });
-
- if (!found) {
- Utils.error(false, 'UniqueFilter', 'No more non-unique filters available');
- e.value = undefined;
- }
- }
- });
- });
-
- QueryBuilder.extend(/** @lends module:plugins.UniqueFilter.prototype */ {
- /**
- * Updates the list of used filters
- * @param {$.Event} [e]
- * @private
- */
- updateDisabledFilters: function(e) {
- var self = e ? e.builder : this;
-
- self.status.used_filters = {};
-
- if (!self.model) {
- return;
- }
-
- // get used filters
- (function walk(group) {
- group.each(function(rule) {
- if (rule.filter && rule.filter.unique) {
- if (!self.status.used_filters[rule.filter.id]) {
- self.status.used_filters[rule.filter.id] = [];
- }
- if (rule.filter.unique == 'group') {
- self.status.used_filters[rule.filter.id].push(rule.parent);
- }
- }
- }, function(group) {
- walk(group);
- });
- }(self.model.root));
-
- self.applyDisabledFilters(e);
- },
-
- /**
- * Clear the list of used filters
- * @param {$.Event} [e]
- * @private
- */
- clearDisabledFilters: function(e) {
- var self = e ? e.builder : this;
-
- self.status.used_filters = {};
-
- self.applyDisabledFilters(e);
- },
-
- /**
- * Disabled filters depending on the list of used ones
- * @param {$.Event} [e]
- * @private
- */
- applyDisabledFilters: function(e) {
- var self = e ? e.builder : this;
-
- // re-enable everything
- self.$el.find(QueryBuilder.selectors.filter_container + ' option').prop('disabled', false);
-
- // disable some
- $.each(self.status.used_filters, function(filterId, groups) {
- if (groups.length === 0) {
- self.$el.find(QueryBuilder.selectors.filter_container + ' option[value="' + filterId + '"]:not(:selected)').prop('disabled', true);
- }
- else {
- groups.forEach(function(group) {
- group.each(function(rule) {
- rule.$el.find(QueryBuilder.selectors.filter_container + ' option[value="' + filterId + '"]:not(:selected)').prop('disabled', true);
- });
- });
- }
- });
-
- // update Selectpicker
- if (self.settings.plugins && self.settings.plugins['bt-selectpicker']) {
- self.$el.find(QueryBuilder.selectors.rule_filter).selectpicker('render');
- }
- }
- });
-
-
- /*!
- * jQuery QueryBuilder 2.5.2
- * Locale: English (en)
- * Author: Damien "Mistic" Sorel, http://www.strangeplanet.fr
- * Licensed under MIT (https://opensource.org/licenses/MIT)
- */
-
- QueryBuilder.regional['en'] = {
- "__locale": "English (en)",
- "__author": "Damien \"Mistic\" Sorel, http://www.strangeplanet.fr",
- "add_rule": "Add rule",
- "add_group": "Add group",
- "delete_rule": "Delete",
- "delete_group": "Delete",
- "conditions": {
- "AND": "AND",
- "OR": "OR"
- },
- "operators": {
- "equal": "equal",
- "not_equal": "not equal",
- "in": "in",
- "not_in": "not in",
- "less": "less",
- "less_or_equal": "less or equal",
- "greater": "greater",
- "greater_or_equal": "greater or equal",
- "between": "between",
- "not_between": "not between",
- "begins_with": "begins with",
- "not_begins_with": "doesn't begin with",
- "contains": "contains",
- "not_contains": "doesn't contain",
- "ends_with": "ends with",
- "not_ends_with": "doesn't end with",
- "is_empty": "is empty",
- "is_not_empty": "is not empty",
- "is_null": "is null",
- "is_not_null": "is not null"
- },
- "errors": {
- "no_filter": "No filter selected",
- "empty_group": "The group is empty",
- "radio_empty": "No value selected",
- "checkbox_empty": "No value selected",
- "select_empty": "No value selected",
- "string_empty": "Empty value",
- "string_exceed_min_length": "Must contain at least {0} characters",
- "string_exceed_max_length": "Must not contain more than {0} characters",
- "string_invalid_format": "Invalid format ({0})",
- "number_nan": "Not a number",
- "number_not_integer": "Not an integer",
- "number_not_double": "Not a real number",
- "number_exceed_min": "Must be greater than {0}",
- "number_exceed_max": "Must be lower than {0}",
- "number_wrong_step": "Must be a multiple of {0}",
- "number_between_invalid": "Invalid values, {0} is greater than {1}",
- "datetime_empty": "Empty value",
- "datetime_invalid": "Invalid date format ({0})",
- "datetime_exceed_min": "Must be after {0}",
- "datetime_exceed_max": "Must be before {0}",
- "datetime_between_invalid": "Invalid values, {0} is greater than {1}",
- "boolean_not_valid": "Not a boolean",
- "operator_not_multiple": "Operator \"{1}\" cannot accept multiple values"
- },
- "invert": "Invert",
- "NOT": "NOT"
- };
-
- QueryBuilder.defaults({ lang_code: 'en' });
- return QueryBuilder;
-
- }));
|