Built files from Bizgaze WebServer
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

query-builder.standalone.js 196KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477
  1. /*!
  2. * jQuery.extendext 0.1.2
  3. *
  4. * Copyright 2014-2016 Damien "Mistic" Sorel (http://www.strangeplanet.fr)
  5. * Licensed under MIT (http://opensource.org/licenses/MIT)
  6. *
  7. * Based on jQuery.extend by jQuery Foundation, Inc. and other contributors
  8. */
  9. (function (root, factory) {
  10. if (typeof define === 'function' && define.amd) {
  11. define('jQuery.extendext', ['jquery'], factory);
  12. }
  13. else if (typeof module === 'object' && module.exports) {
  14. module.exports = factory(require('jquery'));
  15. }
  16. else {
  17. factory(root.jQuery);
  18. }
  19. }(this, function ($) {
  20. "use strict";
  21. $.extendext = function () {
  22. var options, name, src, copy, copyIsArray, clone,
  23. target = arguments[0] || {},
  24. i = 1,
  25. length = arguments.length,
  26. deep = false,
  27. arrayMode = 'default';
  28. // Handle a deep copy situation
  29. if (typeof target === "boolean") {
  30. deep = target;
  31. // Skip the boolean and the target
  32. target = arguments[i++] || {};
  33. }
  34. // Handle array mode parameter
  35. if (typeof target === "string") {
  36. arrayMode = target.toLowerCase();
  37. if (arrayMode !== 'concat' && arrayMode !== 'replace' && arrayMode !== 'extend') {
  38. arrayMode = 'default';
  39. }
  40. // Skip the string param
  41. target = arguments[i++] || {};
  42. }
  43. // Handle case when target is a string or something (possible in deep copy)
  44. if (typeof target !== "object" && !$.isFunction(target)) {
  45. target = {};
  46. }
  47. // Extend jQuery itself if only one argument is passed
  48. if (i === length) {
  49. target = this;
  50. i--;
  51. }
  52. for (; i < length; i++) {
  53. // Only deal with non-null/undefined values
  54. if ((options = arguments[i]) !== null) {
  55. // Special operations for arrays
  56. if ($.isArray(options) && arrayMode !== 'default') {
  57. clone = target && $.isArray(target) ? target : [];
  58. switch (arrayMode) {
  59. case 'concat':
  60. target = clone.concat($.extend(deep, [], options));
  61. break;
  62. case 'replace':
  63. target = $.extend(deep, [], options);
  64. break;
  65. case 'extend':
  66. options.forEach(function (e, i) {
  67. if (typeof e === 'object') {
  68. var type = $.isArray(e) ? [] : {};
  69. clone[i] = $.extendext(deep, arrayMode, clone[i] || type, e);
  70. } else if (clone.indexOf(e) === -1) {
  71. clone.push(e);
  72. }
  73. });
  74. target = clone;
  75. break;
  76. }
  77. } else {
  78. // Extend the base object
  79. for (name in options) {
  80. src = target[name];
  81. copy = options[name];
  82. // Prevent never-ending loop
  83. if (target === copy) {
  84. continue;
  85. }
  86. // Recurse if we're merging plain objects or arrays
  87. if (deep && copy && ( $.isPlainObject(copy) ||
  88. (copyIsArray = $.isArray(copy)) )) {
  89. if (copyIsArray) {
  90. copyIsArray = false;
  91. clone = src && $.isArray(src) ? src : [];
  92. } else {
  93. clone = src && $.isPlainObject(src) ? src : {};
  94. }
  95. // Never move original objects, clone them
  96. target[name] = $.extendext(deep, arrayMode, clone, copy);
  97. // Don't bring in undefined values
  98. } else if (copy !== undefined) {
  99. target[name] = copy;
  100. }
  101. }
  102. }
  103. }
  104. }
  105. // Return the modified object
  106. return target;
  107. };
  108. }));
  109. // doT.js
  110. // 2011-2014, Laura Doktorova, https://github.com/olado/doT
  111. // Licensed under the MIT license.
  112. (function () {
  113. "use strict";
  114. var doT = {
  115. name: "doT",
  116. version: "1.1.1",
  117. templateSettings: {
  118. evaluate: /\{\{([\s\S]+?(\}?)+)\}\}/g,
  119. interpolate: /\{\{=([\s\S]+?)\}\}/g,
  120. encode: /\{\{!([\s\S]+?)\}\}/g,
  121. use: /\{\{#([\s\S]+?)\}\}/g,
  122. useParams: /(^|[^\w$])def(?:\.|\[[\'\"])([\w$\.]+)(?:[\'\"]\])?\s*\:\s*([\w$\.]+|\"[^\"]+\"|\'[^\']+\'|\{[^\}]+\})/g,
  123. define: /\{\{##\s*([\w\.$]+)\s*(\:|=)([\s\S]+?)#\}\}/g,
  124. defineParams:/^\s*([\w$]+):([\s\S]+)/,
  125. conditional: /\{\{\?(\?)?\s*([\s\S]*?)\s*\}\}/g,
  126. iterate: /\{\{~\s*(?:\}\}|([\s\S]+?)\s*\:\s*([\w$]+)\s*(?:\:\s*([\w$]+))?\s*\}\})/g,
  127. varname: "it",
  128. strip: true,
  129. append: true,
  130. selfcontained: false,
  131. doNotSkipEncoded: false
  132. },
  133. template: undefined, //fn, compile template
  134. compile: undefined, //fn, for express
  135. log: true
  136. }, _globals;
  137. doT.encodeHTMLSource = function(doNotSkipEncoded) {
  138. var encodeHTMLRules = { "&": "&#38;", "<": "&#60;", ">": "&#62;", '"': "&#34;", "'": "&#39;", "/": "&#47;" },
  139. matchHTML = doNotSkipEncoded ? /[&<>"'\/]/g : /&(?!#?\w+;)|<|>|"|'|\//g;
  140. return function(code) {
  141. return code ? code.toString().replace(matchHTML, function(m) {return encodeHTMLRules[m] || m;}) : "";
  142. };
  143. };
  144. _globals = (function(){ return this || (0,eval)("this"); }());
  145. /* istanbul ignore else */
  146. if (typeof module !== "undefined" && module.exports) {
  147. module.exports = doT;
  148. } else if (typeof define === "function" && define.amd) {
  149. define('doT', function(){return doT;});
  150. } else {
  151. _globals.doT = doT;
  152. }
  153. var startend = {
  154. append: { start: "'+(", end: ")+'", startencode: "'+encodeHTML(" },
  155. split: { start: "';out+=(", end: ");out+='", startencode: "';out+=encodeHTML(" }
  156. }, skip = /$^/;
  157. function resolveDefs(c, block, def) {
  158. return ((typeof block === "string") ? block : block.toString())
  159. .replace(c.define || skip, function(m, code, assign, value) {
  160. if (code.indexOf("def.") === 0) {
  161. code = code.substring(4);
  162. }
  163. if (!(code in def)) {
  164. if (assign === ":") {
  165. if (c.defineParams) value.replace(c.defineParams, function(m, param, v) {
  166. def[code] = {arg: param, text: v};
  167. });
  168. if (!(code in def)) def[code]= value;
  169. } else {
  170. new Function("def", "def['"+code+"']=" + value)(def);
  171. }
  172. }
  173. return "";
  174. })
  175. .replace(c.use || skip, function(m, code) {
  176. if (c.useParams) code = code.replace(c.useParams, function(m, s, d, param) {
  177. if (def[d] && def[d].arg && param) {
  178. var rw = (d+":"+param).replace(/'|\\/g, "_");
  179. def.__exp = def.__exp || {};
  180. def.__exp[rw] = def[d].text.replace(new RegExp("(^|[^\\w$])" + def[d].arg + "([^\\w$])", "g"), "$1" + param + "$2");
  181. return s + "def.__exp['"+rw+"']";
  182. }
  183. });
  184. var v = new Function("def", "return " + code)(def);
  185. return v ? resolveDefs(c, v, def) : v;
  186. });
  187. }
  188. function unescape(code) {
  189. return code.replace(/\\('|\\)/g, "$1").replace(/[\r\t\n]/g, " ");
  190. }
  191. doT.template = function(tmpl, c, def) {
  192. c = c || doT.templateSettings;
  193. var cse = c.append ? startend.append : startend.split, needhtmlencode, sid = 0, indv,
  194. str = (c.use || c.define) ? resolveDefs(c, tmpl, def || {}) : tmpl;
  195. str = ("var out='" + (c.strip ? str.replace(/(^|\r|\n)\t* +| +\t*(\r|\n|$)/g," ")
  196. .replace(/\r|\n|\t|\/\*[\s\S]*?\*\//g,""): str)
  197. .replace(/'|\\/g, "\\$&")
  198. .replace(c.interpolate || skip, function(m, code) {
  199. return cse.start + unescape(code) + cse.end;
  200. })
  201. .replace(c.encode || skip, function(m, code) {
  202. needhtmlencode = true;
  203. return cse.startencode + unescape(code) + cse.end;
  204. })
  205. .replace(c.conditional || skip, function(m, elsecase, code) {
  206. return elsecase ?
  207. (code ? "';}else if(" + unescape(code) + "){out+='" : "';}else{out+='") :
  208. (code ? "';if(" + unescape(code) + "){out+='" : "';}out+='");
  209. })
  210. .replace(c.iterate || skip, function(m, iterate, vname, iname) {
  211. if (!iterate) return "';} } out+='";
  212. sid+=1; indv=iname || "i"+sid; iterate=unescape(iterate);
  213. return "';var arr"+sid+"="+iterate+";if(arr"+sid+"){var "+vname+","+indv+"=-1,l"+sid+"=arr"+sid+".length-1;while("+indv+"<l"+sid+"){"
  214. +vname+"=arr"+sid+"["+indv+"+=1];out+='";
  215. })
  216. .replace(c.evaluate || skip, function(m, code) {
  217. return "';" + unescape(code) + "out+='";
  218. })
  219. + "';return out;")
  220. .replace(/\n/g, "\\n").replace(/\t/g, '\\t').replace(/\r/g, "\\r")
  221. .replace(/(\s|;|\}|^|\{)out\+='';/g, '$1').replace(/\+''/g, "");
  222. //.replace(/(\s|;|\}|^|\{)out\+=''\+/g,'$1out+=');
  223. if (needhtmlencode) {
  224. if (!c.selfcontained && _globals && !_globals._encodeHTML) _globals._encodeHTML = doT.encodeHTMLSource(c.doNotSkipEncoded);
  225. str = "var encodeHTML = typeof _encodeHTML !== 'undefined' ? _encodeHTML : ("
  226. + doT.encodeHTMLSource.toString() + "(" + (c.doNotSkipEncoded || '') + "));"
  227. + str;
  228. }
  229. try {
  230. return new Function(c.varname, str);
  231. } catch (e) {
  232. /* istanbul ignore else */
  233. if (typeof console !== "undefined") console.log("Could not create a template function: " + str);
  234. throw e;
  235. }
  236. };
  237. doT.compile = function(tmpl, def) {
  238. return doT.template(tmpl, null, def);
  239. };
  240. }());
  241. /*!
  242. * jQuery QueryBuilder 2.5.2
  243. * Copyright 2014-2018 Damien "Mistic" Sorel (http://www.strangeplanet.fr)
  244. * Licensed under MIT (https://opensource.org/licenses/MIT)
  245. */
  246. (function (root, factory) {
  247. if (typeof define == 'function' && define.amd) {
  248. define('query-builder', ['jquery', 'dot/doT', 'jquery-extendext'], factory);
  249. }
  250. else if (typeof module === 'object' && module.exports) {
  251. module.exports = factory(require('jquery'), require('dot/doT'), require('jquery-extendext'));
  252. }
  253. else {
  254. factory(root.jQuery, root.doT);
  255. }
  256. }(this, function($, doT) {
  257. "use strict";
  258. /**
  259. * @typedef {object} Filter
  260. * @memberof QueryBuilder
  261. * @description See {@link http://querybuilder.js.org/index.html#filters}
  262. */
  263. /**
  264. * @typedef {object} Operator
  265. * @memberof QueryBuilder
  266. * @description See {@link http://querybuilder.js.org/index.html#operators}
  267. */
  268. /**
  269. * @param {jQuery} $el
  270. * @param {object} options - see {@link http://querybuilder.js.org/#options}
  271. * @constructor
  272. */
  273. var QueryBuilder = function($el, options) {
  274. $el[0].queryBuilder = this;
  275. /**
  276. * Element container
  277. * @member {jQuery}
  278. * @readonly
  279. */
  280. this.$el = $el;
  281. /**
  282. * Configuration object
  283. * @member {object}
  284. * @readonly
  285. */
  286. this.settings = $.extendext(true, 'replace', {}, QueryBuilder.DEFAULTS, options);
  287. /**
  288. * Internal model
  289. * @member {Model}
  290. * @readonly
  291. */
  292. this.model = new Model();
  293. /**
  294. * Internal status
  295. * @member {object}
  296. * @property {string} id - id of the container
  297. * @property {boolean} generated_id - if the container id has been generated
  298. * @property {int} group_id - current group id
  299. * @property {int} rule_id - current rule id
  300. * @property {boolean} has_optgroup - if filters have optgroups
  301. * @property {boolean} has_operator_optgroup - if operators have optgroups
  302. * @readonly
  303. * @private
  304. */
  305. this.status = {
  306. id: null,
  307. generated_id: false,
  308. group_id: 0,
  309. rule_id: 0,
  310. has_optgroup: false,
  311. has_operator_optgroup: false
  312. };
  313. /**
  314. * List of filters
  315. * @member {QueryBuilder.Filter[]}
  316. * @readonly
  317. */
  318. this.filters = this.settings.filters;
  319. /**
  320. * List of icons
  321. * @member {object.<string, string>}
  322. * @readonly
  323. */
  324. this.icons = this.settings.icons;
  325. /**
  326. * List of operators
  327. * @member {QueryBuilder.Operator[]}
  328. * @readonly
  329. */
  330. this.operators = this.settings.operators;
  331. /**
  332. * List of templates
  333. * @member {object.<string, function>}
  334. * @readonly
  335. */
  336. this.templates = this.settings.templates;
  337. /**
  338. * Plugins configuration
  339. * @member {object.<string, object>}
  340. * @readonly
  341. */
  342. this.plugins = this.settings.plugins;
  343. /**
  344. * Translations object
  345. * @member {object}
  346. * @readonly
  347. */
  348. this.lang = null;
  349. // translations : english << 'lang_code' << custom
  350. if (QueryBuilder.regional['en'] === undefined) {
  351. Utils.error('Config', '"i18n/en.js" not loaded.');
  352. }
  353. this.lang = $.extendext(true, 'replace', {}, QueryBuilder.regional['en'], QueryBuilder.regional[this.settings.lang_code], this.settings.lang);
  354. // "allow_groups" can be boolean or int
  355. if (this.settings.allow_groups === false) {
  356. this.settings.allow_groups = 0;
  357. }
  358. else if (this.settings.allow_groups === true) {
  359. this.settings.allow_groups = -1;
  360. }
  361. // init templates
  362. Object.keys(this.templates).forEach(function(tpl) {
  363. if (!this.templates[tpl]) {
  364. this.templates[tpl] = QueryBuilder.templates[tpl];
  365. }
  366. if (typeof this.templates[tpl] == 'string') {
  367. this.templates[tpl] = doT.template(this.templates[tpl]);
  368. }
  369. }, this);
  370. // ensure we have a container id
  371. if (!this.$el.attr('id')) {
  372. this.$el.attr('id', 'qb_' + Math.floor(Math.random() * 99999));
  373. this.status.generated_id = true;
  374. }
  375. this.status.id = this.$el.attr('id');
  376. // INIT
  377. this.$el.addClass('query-builder form-inline');
  378. this.filters = this.checkFilters(this.filters);
  379. this.operators = this.checkOperators(this.operators);
  380. this.bindEvents();
  381. this.initPlugins();
  382. };
  383. $.extend(QueryBuilder.prototype, /** @lends QueryBuilder.prototype */ {
  384. /**
  385. * Triggers an event on the builder container
  386. * @param {string} type
  387. * @returns {$.Event}
  388. */
  389. trigger: function(type) {
  390. var event = new $.Event(this._tojQueryEvent(type), {
  391. builder: this
  392. });
  393. this.$el.triggerHandler(event, Array.prototype.slice.call(arguments, 1));
  394. return event;
  395. },
  396. /**
  397. * Triggers an event on the builder container and returns the modified value
  398. * @param {string} type
  399. * @param {*} value
  400. * @returns {*}
  401. */
  402. change: function(type, value) {
  403. var event = new $.Event(this._tojQueryEvent(type, true), {
  404. builder: this,
  405. value: value
  406. });
  407. this.$el.triggerHandler(event, Array.prototype.slice.call(arguments, 2));
  408. return event.value;
  409. },
  410. /**
  411. * Attaches an event listener on the builder container
  412. * @param {string} type
  413. * @param {function} cb
  414. * @returns {QueryBuilder}
  415. */
  416. on: function(type, cb) {
  417. this.$el.on(this._tojQueryEvent(type), cb);
  418. return this;
  419. },
  420. /**
  421. * Removes an event listener from the builder container
  422. * @param {string} type
  423. * @param {function} [cb]
  424. * @returns {QueryBuilder}
  425. */
  426. off: function(type, cb) {
  427. this.$el.off(this._tojQueryEvent(type), cb);
  428. return this;
  429. },
  430. /**
  431. * Attaches an event listener called once on the builder container
  432. * @param {string} type
  433. * @param {function} cb
  434. * @returns {QueryBuilder}
  435. */
  436. once: function(type, cb) {
  437. this.$el.one(this._tojQueryEvent(type), cb);
  438. return this;
  439. },
  440. /**
  441. * Appends `.queryBuilder` and optionally `.filter` to the events names
  442. * @param {string} name
  443. * @param {boolean} [filter=false]
  444. * @returns {string}
  445. * @private
  446. */
  447. _tojQueryEvent: function(name, filter) {
  448. return name.split(' ').map(function(type) {
  449. return type + '.queryBuilder' + (filter ? '.filter' : '');
  450. }).join(' ');
  451. }
  452. });
  453. /**
  454. * Allowed types and their internal representation
  455. * @type {object.<string, string>}
  456. * @readonly
  457. * @private
  458. */
  459. QueryBuilder.types = {
  460. 'string': 'string',
  461. 'integer': 'number',
  462. 'double': 'number',
  463. 'date': 'datetime',
  464. 'time': 'datetime',
  465. 'datetime': 'datetime',
  466. 'boolean': 'boolean'
  467. };
  468. /**
  469. * Allowed inputs
  470. * @type {string[]}
  471. * @readonly
  472. * @private
  473. */
  474. QueryBuilder.inputs = [
  475. 'text',
  476. 'number',
  477. 'textarea',
  478. 'radio',
  479. 'checkbox',
  480. 'select'
  481. ];
  482. /**
  483. * Runtime modifiable options with `setOptions` method
  484. * @type {string[]}
  485. * @readonly
  486. * @private
  487. */
  488. QueryBuilder.modifiable_options = [
  489. 'display_errors',
  490. 'allow_groups',
  491. 'allow_empty',
  492. 'default_condition',
  493. 'default_filter'
  494. ];
  495. /**
  496. * CSS selectors for common components
  497. * @type {object.<string, string>}
  498. * @readonly
  499. */
  500. QueryBuilder.selectors = {
  501. group_container: '.rules-group-container',
  502. rule_container: '.rule-container',
  503. filter_container: '.rule-filter-container',
  504. operator_container: '.rule-operator-container',
  505. value_container: '.rule-value-container',
  506. error_container: '.error-container',
  507. condition_container: '.rules-group-header .group-conditions',
  508. rule_header: '.rule-header',
  509. group_header: '.rules-group-header',
  510. group_actions: '.group-actions',
  511. rule_actions: '.rule-actions',
  512. rules_list: '.rules-group-body>.rules-list',
  513. group_condition: '.rules-group-header [name$=_cond]',
  514. rule_filter: '.rule-filter-container [name$=_filter]',
  515. rule_operator: '.rule-operator-container [name$=_operator]',
  516. rule_value: '.rule-value-container [name*=_value_]',
  517. add_rule: '[data-add=rule]',
  518. delete_rule: '[data-delete=rule]',
  519. add_group: '[data-add=group]',
  520. delete_group: '[data-delete=group]'
  521. };
  522. /**
  523. * Template strings (see template.js)
  524. * @type {object.<string, string>}
  525. * @readonly
  526. */
  527. QueryBuilder.templates = {};
  528. /**
  529. * Localized strings (see i18n/)
  530. * @type {object.<string, object>}
  531. * @readonly
  532. */
  533. QueryBuilder.regional = {};
  534. /**
  535. * Default operators
  536. * @type {object.<string, object>}
  537. * @readonly
  538. */
  539. QueryBuilder.OPERATORS = {
  540. equal: { type: 'equal', nb_inputs: 1, multiple: false, apply_to: ['string', 'number', 'datetime', 'boolean'] },
  541. not_equal: { type: 'not_equal', nb_inputs: 1, multiple: false, apply_to: ['string', 'number', 'datetime', 'boolean'] },
  542. in: { type: 'in', nb_inputs: 1, multiple: true, apply_to: ['string', 'number', 'datetime'] },
  543. not_in: { type: 'not_in', nb_inputs: 1, multiple: true, apply_to: ['string', 'number', 'datetime'] },
  544. less: { type: 'less', nb_inputs: 1, multiple: false, apply_to: ['number', 'datetime'] },
  545. less_or_equal: { type: 'less_or_equal', nb_inputs: 1, multiple: false, apply_to: ['number', 'datetime'] },
  546. greater: { type: 'greater', nb_inputs: 1, multiple: false, apply_to: ['number', 'datetime'] },
  547. greater_or_equal: { type: 'greater_or_equal', nb_inputs: 1, multiple: false, apply_to: ['number', 'datetime'] },
  548. between: { type: 'between', nb_inputs: 2, multiple: false, apply_to: ['number', 'datetime'] },
  549. not_between: { type: 'not_between', nb_inputs: 2, multiple: false, apply_to: ['number', 'datetime'] },
  550. begins_with: { type: 'begins_with', nb_inputs: 1, multiple: false, apply_to: ['string'] },
  551. not_begins_with: { type: 'not_begins_with', nb_inputs: 1, multiple: false, apply_to: ['string'] },
  552. contains: { type: 'contains', nb_inputs: 1, multiple: false, apply_to: ['string'] },
  553. not_contains: { type: 'not_contains', nb_inputs: 1, multiple: false, apply_to: ['string'] },
  554. ends_with: { type: 'ends_with', nb_inputs: 1, multiple: false, apply_to: ['string'] },
  555. not_ends_with: { type: 'not_ends_with', nb_inputs: 1, multiple: false, apply_to: ['string'] },
  556. is_empty: { type: 'is_empty', nb_inputs: 0, multiple: false, apply_to: ['string'] },
  557. is_not_empty: { type: 'is_not_empty', nb_inputs: 0, multiple: false, apply_to: ['string'] },
  558. is_null: { type: 'is_null', nb_inputs: 0, multiple: false, apply_to: ['string', 'number', 'datetime', 'boolean'] },
  559. is_not_null: { type: 'is_not_null', nb_inputs: 0, multiple: false, apply_to: ['string', 'number', 'datetime', 'boolean'] }
  560. };
  561. /**
  562. * Default configuration
  563. * @type {object}
  564. * @readonly
  565. */
  566. QueryBuilder.DEFAULTS = {
  567. filters: [],
  568. plugins: [],
  569. sort_filters: false,
  570. display_errors: true,
  571. allow_groups: -1,
  572. allow_empty: false,
  573. conditions: ['AND', 'OR'],
  574. default_condition: 'AND',
  575. inputs_separator: ' , ',
  576. select_placeholder: '------',
  577. display_empty_filter: true,
  578. default_filter: null,
  579. optgroups: {},
  580. default_rule_flags: {
  581. filter_readonly: false,
  582. operator_readonly: false,
  583. value_readonly: false,
  584. no_delete: false
  585. },
  586. default_group_flags: {
  587. condition_readonly: false,
  588. no_add_rule: false,
  589. no_add_group: false,
  590. no_delete: false
  591. },
  592. templates: {
  593. group: null,
  594. rule: null,
  595. filterSelect: null,
  596. operatorSelect: null,
  597. ruleValueSelect: null
  598. },
  599. lang_code: 'en',
  600. lang: {},
  601. operators: [
  602. 'equal',
  603. 'not_equal',
  604. 'in',
  605. 'not_in',
  606. 'less',
  607. 'less_or_equal',
  608. 'greater',
  609. 'greater_or_equal',
  610. 'between',
  611. 'not_between',
  612. 'begins_with',
  613. 'not_begins_with',
  614. 'contains',
  615. 'not_contains',
  616. 'ends_with',
  617. 'not_ends_with',
  618. 'is_empty',
  619. 'is_not_empty',
  620. 'is_null',
  621. 'is_not_null'
  622. ],
  623. icons: {
  624. add_group: 'glyphicon glyphicon-plus-sign',
  625. add_rule: 'glyphicon glyphicon-plus',
  626. remove_group: 'glyphicon glyphicon-remove',
  627. remove_rule: 'glyphicon glyphicon-remove',
  628. error: 'glyphicon glyphicon-warning-sign'
  629. }
  630. };
  631. /**
  632. * @module plugins
  633. */
  634. /**
  635. * Definition of available plugins
  636. * @type {object.<String, object>}
  637. */
  638. QueryBuilder.plugins = {};
  639. /**
  640. * Gets or extends the default configuration
  641. * @param {object} [options] - new configuration
  642. * @returns {undefined|object} nothing or configuration object (copy)
  643. */
  644. QueryBuilder.defaults = function(options) {
  645. if (typeof options == 'object') {
  646. $.extendext(true, 'replace', QueryBuilder.DEFAULTS, options);
  647. }
  648. else if (typeof options == 'string') {
  649. if (typeof QueryBuilder.DEFAULTS[options] == 'object') {
  650. return $.extend(true, {}, QueryBuilder.DEFAULTS[options]);
  651. }
  652. else {
  653. return QueryBuilder.DEFAULTS[options];
  654. }
  655. }
  656. else {
  657. return $.extend(true, {}, QueryBuilder.DEFAULTS);
  658. }
  659. };
  660. /**
  661. * Registers a new plugin
  662. * @param {string} name
  663. * @param {function} fct - init function
  664. * @param {object} [def] - default options
  665. */
  666. QueryBuilder.define = function(name, fct, def) {
  667. QueryBuilder.plugins[name] = {
  668. fct: fct,
  669. def: def || {}
  670. };
  671. };
  672. /**
  673. * Adds new methods to QueryBuilder prototype
  674. * @param {object.<string, function>} methods
  675. */
  676. QueryBuilder.extend = function(methods) {
  677. $.extend(QueryBuilder.prototype, methods);
  678. };
  679. /**
  680. * Initializes plugins for an instance
  681. * @throws ConfigError
  682. * @private
  683. */
  684. QueryBuilder.prototype.initPlugins = function() {
  685. if (!this.plugins) {
  686. return;
  687. }
  688. if ($.isArray(this.plugins)) {
  689. var tmp = {};
  690. this.plugins.forEach(function(plugin) {
  691. tmp[plugin] = null;
  692. });
  693. this.plugins = tmp;
  694. }
  695. Object.keys(this.plugins).forEach(function(plugin) {
  696. if (plugin in QueryBuilder.plugins) {
  697. this.plugins[plugin] = $.extend(true, {},
  698. QueryBuilder.plugins[plugin].def,
  699. this.plugins[plugin] || {}
  700. );
  701. QueryBuilder.plugins[plugin].fct.call(this, this.plugins[plugin]);
  702. }
  703. else {
  704. Utils.error('Config', 'Unable to find plugin "{0}"', plugin);
  705. }
  706. }, this);
  707. };
  708. /**
  709. * Returns the config of a plugin, if the plugin is not loaded, returns the default config.
  710. * @param {string} name
  711. * @param {string} [property]
  712. * @throws ConfigError
  713. * @returns {*}
  714. */
  715. QueryBuilder.prototype.getPluginOptions = function(name, property) {
  716. var plugin;
  717. if (this.plugins && this.plugins[name]) {
  718. plugin = this.plugins[name];
  719. }
  720. else if (QueryBuilder.plugins[name]) {
  721. plugin = QueryBuilder.plugins[name].def;
  722. }
  723. if (plugin) {
  724. if (property) {
  725. return plugin[property];
  726. }
  727. else {
  728. return plugin;
  729. }
  730. }
  731. else {
  732. Utils.error('Config', 'Unable to find plugin "{0}"', name);
  733. }
  734. };
  735. /**
  736. * Final initialisation of the builder
  737. * @param {object} [rules]
  738. * @fires QueryBuilder.afterInit
  739. * @private
  740. */
  741. QueryBuilder.prototype.init = function(rules) {
  742. /**
  743. * When the initilization is done, just before creating the root group
  744. * @event afterInit
  745. * @memberof QueryBuilder
  746. */
  747. this.trigger('afterInit');
  748. if (rules) {
  749. this.setRules(rules);
  750. delete this.settings.rules;
  751. }
  752. else {
  753. this.setRoot(true);
  754. }
  755. };
  756. /**
  757. * Checks the configuration of each filter
  758. * @param {QueryBuilder.Filter[]} filters
  759. * @returns {QueryBuilder.Filter[]}
  760. * @throws ConfigError
  761. */
  762. QueryBuilder.prototype.checkFilters = function(filters) {
  763. var definedFilters = [];
  764. if (!filters || filters.length === 0) {
  765. Utils.error('Config', 'Missing filters list');
  766. }
  767. filters.forEach(function(filter, i) {
  768. if (!filter.id) {
  769. Utils.error('Config', 'Missing filter {0} id', i);
  770. }
  771. if (definedFilters.indexOf(filter.id) != -1) {
  772. Utils.error('Config', 'Filter "{0}" already defined', filter.id);
  773. }
  774. definedFilters.push(filter.id);
  775. if (!filter.type) {
  776. filter.type = 'string';
  777. }
  778. else if (!QueryBuilder.types[filter.type]) {
  779. Utils.error('Config', 'Invalid type "{0}"', filter.type);
  780. }
  781. if (!filter.input) {
  782. filter.input = QueryBuilder.types[filter.type] === 'number' ? 'number' : 'text';
  783. }
  784. else if (typeof filter.input != 'function' && QueryBuilder.inputs.indexOf(filter.input) == -1) {
  785. Utils.error('Config', 'Invalid input "{0}"', filter.input);
  786. }
  787. if (filter.operators) {
  788. filter.operators.forEach(function(operator) {
  789. if (typeof operator != 'string') {
  790. Utils.error('Config', 'Filter operators must be global operators types (string)');
  791. }
  792. });
  793. }
  794. if (!filter.field) {
  795. filter.field = filter.id;
  796. }
  797. if (!filter.label) {
  798. filter.label = filter.field;
  799. }
  800. if (!filter.optgroup) {
  801. filter.optgroup = null;
  802. }
  803. else {
  804. this.status.has_optgroup = true;
  805. // register optgroup if needed
  806. if (!this.settings.optgroups[filter.optgroup]) {
  807. this.settings.optgroups[filter.optgroup] = filter.optgroup;
  808. }
  809. }
  810. switch (filter.input) {
  811. case 'radio':
  812. case 'checkbox':
  813. if (!filter.values || filter.values.length < 1) {
  814. Utils.error('Config', 'Missing filter "{0}" values', filter.id);
  815. }
  816. break;
  817. case 'select':
  818. var cleanValues = [];
  819. filter.has_optgroup = false;
  820. Utils.iterateOptions(filter.values, function(value, label, optgroup) {
  821. cleanValues.push({
  822. value: value,
  823. label: label,
  824. optgroup: optgroup || null
  825. });
  826. if (optgroup) {
  827. filter.has_optgroup = true;
  828. // register optgroup if needed
  829. if (!this.settings.optgroups[optgroup]) {
  830. this.settings.optgroups[optgroup] = optgroup;
  831. }
  832. }
  833. }.bind(this));
  834. if (filter.has_optgroup) {
  835. filter.values = Utils.groupSort(cleanValues, 'optgroup');
  836. }
  837. else {
  838. filter.values = cleanValues;
  839. }
  840. if (filter.placeholder) {
  841. if (filter.placeholder_value === undefined) {
  842. filter.placeholder_value = -1;
  843. }
  844. filter.values.forEach(function(entry) {
  845. if (entry.value == filter.placeholder_value) {
  846. Utils.error('Config', 'Placeholder of filter "{0}" overlaps with one of its values', filter.id);
  847. }
  848. });
  849. }
  850. break;
  851. }
  852. }, this);
  853. if (this.settings.sort_filters) {
  854. if (typeof this.settings.sort_filters == 'function') {
  855. filters.sort(this.settings.sort_filters);
  856. }
  857. else {
  858. var self = this;
  859. filters.sort(function(a, b) {
  860. return self.translate(a.label).localeCompare(self.translate(b.label));
  861. });
  862. }
  863. }
  864. if (this.status.has_optgroup) {
  865. filters = Utils.groupSort(filters, 'optgroup');
  866. }
  867. return filters;
  868. };
  869. /**
  870. * Checks the configuration of each operator
  871. * @param {QueryBuilder.Operator[]} operators
  872. * @returns {QueryBuilder.Operator[]}
  873. * @throws ConfigError
  874. */
  875. QueryBuilder.prototype.checkOperators = function(operators) {
  876. var definedOperators = [];
  877. operators.forEach(function(operator, i) {
  878. if (typeof operator == 'string') {
  879. if (!QueryBuilder.OPERATORS[operator]) {
  880. Utils.error('Config', 'Unknown operator "{0}"', operator);
  881. }
  882. operators[i] = operator = $.extendext(true, 'replace', {}, QueryBuilder.OPERATORS[operator]);
  883. }
  884. else {
  885. if (!operator.type) {
  886. Utils.error('Config', 'Missing "type" for operator {0}', i);
  887. }
  888. if (QueryBuilder.OPERATORS[operator.type]) {
  889. operators[i] = operator = $.extendext(true, 'replace', {}, QueryBuilder.OPERATORS[operator.type], operator);
  890. }
  891. if (operator.nb_inputs === undefined || operator.apply_to === undefined) {
  892. Utils.error('Config', 'Missing "nb_inputs" and/or "apply_to" for operator "{0}"', operator.type);
  893. }
  894. }
  895. if (definedOperators.indexOf(operator.type) != -1) {
  896. Utils.error('Config', 'Operator "{0}" already defined', operator.type);
  897. }
  898. definedOperators.push(operator.type);
  899. if (!operator.optgroup) {
  900. operator.optgroup = null;
  901. }
  902. else {
  903. this.status.has_operator_optgroup = true;
  904. // register optgroup if needed
  905. if (!this.settings.optgroups[operator.optgroup]) {
  906. this.settings.optgroups[operator.optgroup] = operator.optgroup;
  907. }
  908. }
  909. }, this);
  910. if (this.status.has_operator_optgroup) {
  911. operators = Utils.groupSort(operators, 'optgroup');
  912. }
  913. return operators;
  914. };
  915. /**
  916. * Adds all events listeners to the builder
  917. * @private
  918. */
  919. QueryBuilder.prototype.bindEvents = function() {
  920. var self = this;
  921. var Selectors = QueryBuilder.selectors;
  922. // group condition change
  923. this.$el.on('change.queryBuilder', Selectors.group_condition, function() {
  924. if ($(this).is(':checked')) {
  925. var $group = $(this).closest(Selectors.group_container);
  926. self.getModel($group).condition = $(this).val();
  927. }
  928. });
  929. // rule filter change
  930. this.$el.on('change.queryBuilder', Selectors.rule_filter, function() {
  931. var $rule = $(this).closest(Selectors.rule_container);
  932. self.getModel($rule).filter = self.getFilterById($(this).val());
  933. });
  934. // rule operator change
  935. this.$el.on('change.queryBuilder', Selectors.rule_operator, function() {
  936. var $rule = $(this).closest(Selectors.rule_container);
  937. self.getModel($rule).operator = self.getOperatorByType($(this).val());
  938. });
  939. // add rule button
  940. this.$el.on('click.queryBuilder', Selectors.add_rule, function() {
  941. var $group = $(this).closest(Selectors.group_container);
  942. self.addRule(self.getModel($group));
  943. });
  944. // delete rule button
  945. this.$el.on('click.queryBuilder', Selectors.delete_rule, function() {
  946. var $rule = $(this).closest(Selectors.rule_container);
  947. self.deleteRule(self.getModel($rule));
  948. });
  949. if (this.settings.allow_groups !== 0) {
  950. // add group button
  951. this.$el.on('click.queryBuilder', Selectors.add_group, function() {
  952. var $group = $(this).closest(Selectors.group_container);
  953. self.addGroup(self.getModel($group));
  954. });
  955. // delete group button
  956. this.$el.on('click.queryBuilder', Selectors.delete_group, function() {
  957. var $group = $(this).closest(Selectors.group_container);
  958. self.deleteGroup(self.getModel($group));
  959. });
  960. }
  961. // model events
  962. this.model.on({
  963. 'drop': function(e, node) {
  964. node.$el.remove();
  965. self.refreshGroupsConditions();
  966. },
  967. 'add': function(e, parent, node, index) {
  968. if (index === 0) {
  969. node.$el.prependTo(parent.$el.find('>' + QueryBuilder.selectors.rules_list));
  970. }
  971. else {
  972. node.$el.insertAfter(parent.rules[index - 1].$el);
  973. }
  974. self.refreshGroupsConditions();
  975. },
  976. 'move': function(e, node, group, index) {
  977. node.$el.detach();
  978. if (index === 0) {
  979. node.$el.prependTo(group.$el.find('>' + QueryBuilder.selectors.rules_list));
  980. }
  981. else {
  982. node.$el.insertAfter(group.rules[index - 1].$el);
  983. }
  984. self.refreshGroupsConditions();
  985. },
  986. 'update': function(e, node, field, value, oldValue) {
  987. if (node instanceof Rule) {
  988. switch (field) {
  989. case 'error':
  990. self.updateError(node);
  991. break;
  992. case 'flags':
  993. self.applyRuleFlags(node);
  994. break;
  995. case 'filter':
  996. self.updateRuleFilter(node, oldValue);
  997. break;
  998. case 'operator':
  999. self.updateRuleOperator(node, oldValue);
  1000. break;
  1001. case 'value':
  1002. self.updateRuleValue(node, oldValue);
  1003. break;
  1004. }
  1005. }
  1006. else {
  1007. switch (field) {
  1008. case 'error':
  1009. self.updateError(node);
  1010. break;
  1011. case 'flags':
  1012. self.applyGroupFlags(node);
  1013. break;
  1014. case 'condition':
  1015. self.updateGroupCondition(node, oldValue);
  1016. break;
  1017. }
  1018. }
  1019. }
  1020. });
  1021. };
  1022. /**
  1023. * Creates the root group
  1024. * @param {boolean} [addRule=true] - adds a default empty rule
  1025. * @param {object} [data] - group custom data
  1026. * @param {object} [flags] - flags to apply to the group
  1027. * @returns {Group} root group
  1028. * @fires QueryBuilder.afterAddGroup
  1029. */
  1030. QueryBuilder.prototype.setRoot = function(addRule, data, flags) {
  1031. addRule = (addRule === undefined || addRule === true);
  1032. var group_id = this.nextGroupId();
  1033. var $group = $(this.getGroupTemplate(group_id, 1));
  1034. this.$el.append($group);
  1035. this.model.root = new Group(null, $group);
  1036. this.model.root.model = this.model;
  1037. this.model.root.data = data;
  1038. this.model.root.flags = $.extend({}, this.settings.default_group_flags, flags);
  1039. this.model.root.condition = this.settings.default_condition;
  1040. this.trigger('afterAddGroup', this.model.root);
  1041. if (addRule) {
  1042. this.addRule(this.model.root);
  1043. }
  1044. return this.model.root;
  1045. };
  1046. /**
  1047. * Adds a new group
  1048. * @param {Group} parent
  1049. * @param {boolean} [addRule=true] - adds a default empty rule
  1050. * @param {object} [data] - group custom data
  1051. * @param {object} [flags] - flags to apply to the group
  1052. * @returns {Group}
  1053. * @fires QueryBuilder.beforeAddGroup
  1054. * @fires QueryBuilder.afterAddGroup
  1055. */
  1056. QueryBuilder.prototype.addGroup = function(parent, addRule, data, flags) {
  1057. addRule = (addRule === undefined || addRule === true);
  1058. var level = parent.level + 1;
  1059. /**
  1060. * Just before adding a group, can be prevented.
  1061. * @event beforeAddGroup
  1062. * @memberof QueryBuilder
  1063. * @param {Group} parent
  1064. * @param {boolean} addRule - if an empty rule will be added in the group
  1065. * @param {int} level - nesting level of the group, 1 is the root group
  1066. */
  1067. var e = this.trigger('beforeAddGroup', parent, addRule, level);
  1068. if (e.isDefaultPrevented()) {
  1069. return null;
  1070. }
  1071. var group_id = this.nextGroupId();
  1072. var $group = $(this.getGroupTemplate(group_id, level));
  1073. var model = parent.addGroup($group);
  1074. model.data = data;
  1075. model.flags = $.extend({}, this.settings.default_group_flags, flags);
  1076. model.condition = this.settings.default_condition;
  1077. /**
  1078. * Just after adding a group
  1079. * @event afterAddGroup
  1080. * @memberof QueryBuilder
  1081. * @param {Group} group
  1082. */
  1083. this.trigger('afterAddGroup', model);
  1084. /**
  1085. * After any change in the rules
  1086. * @event rulesChanged
  1087. * @memberof QueryBuilder
  1088. */
  1089. this.trigger('rulesChanged');
  1090. if (addRule) {
  1091. this.addRule(model);
  1092. }
  1093. return model;
  1094. };
  1095. /**
  1096. * Tries to delete a group. The group is not deleted if at least one rule is flagged `no_delete`.
  1097. * @param {Group} group
  1098. * @returns {boolean} if the group has been deleted
  1099. * @fires QueryBuilder.beforeDeleteGroup
  1100. * @fires QueryBuilder.afterDeleteGroup
  1101. */
  1102. QueryBuilder.prototype.deleteGroup = function(group) {
  1103. if (group.isRoot()) {
  1104. return false;
  1105. }
  1106. /**
  1107. * Just before deleting a group, can be prevented
  1108. * @event beforeDeleteGroup
  1109. * @memberof QueryBuilder
  1110. * @param {Group} parent
  1111. */
  1112. var e = this.trigger('beforeDeleteGroup', group);
  1113. if (e.isDefaultPrevented()) {
  1114. return false;
  1115. }
  1116. var del = true;
  1117. group.each('reverse', function(rule) {
  1118. del &= this.deleteRule(rule);
  1119. }, function(group) {
  1120. del &= this.deleteGroup(group);
  1121. }, this);
  1122. if (del) {
  1123. group.drop();
  1124. /**
  1125. * Just after deleting a group
  1126. * @event afterDeleteGroup
  1127. * @memberof QueryBuilder
  1128. */
  1129. this.trigger('afterDeleteGroup');
  1130. this.trigger('rulesChanged');
  1131. }
  1132. return del;
  1133. };
  1134. /**
  1135. * Performs actions when a group's condition changes
  1136. * @param {Group} group
  1137. * @param {object} previousCondition
  1138. * @fires QueryBuilder.afterUpdateGroupCondition
  1139. * @private
  1140. */
  1141. QueryBuilder.prototype.updateGroupCondition = function(group, previousCondition) {
  1142. group.$el.find('>' + QueryBuilder.selectors.group_condition).each(function() {
  1143. var $this = $(this);
  1144. $this.prop('checked', $this.val() === group.condition);
  1145. $this.parent().toggleClass('active', $this.val() === group.condition);
  1146. });
  1147. /**
  1148. * After the group condition has been modified
  1149. * @event afterUpdateGroupCondition
  1150. * @memberof QueryBuilder
  1151. * @param {Group} group
  1152. * @param {object} previousCondition
  1153. */
  1154. this.trigger('afterUpdateGroupCondition', group, previousCondition);
  1155. this.trigger('rulesChanged');
  1156. };
  1157. /**
  1158. * Updates the visibility of conditions based on number of rules inside each group
  1159. * @private
  1160. */
  1161. QueryBuilder.prototype.refreshGroupsConditions = function() {
  1162. (function walk(group) {
  1163. if (!group.flags || (group.flags && !group.flags.condition_readonly)) {
  1164. group.$el.find('>' + QueryBuilder.selectors.group_condition).prop('disabled', group.rules.length <= 1)
  1165. .parent().toggleClass('disabled', group.rules.length <= 1);
  1166. }
  1167. group.each(null, function(group) {
  1168. walk(group);
  1169. }, this);
  1170. }(this.model.root));
  1171. };
  1172. /**
  1173. * Adds a new rule
  1174. * @param {Group} parent
  1175. * @param {object} [data] - rule custom data
  1176. * @param {object} [flags] - flags to apply to the rule
  1177. * @returns {Rule}
  1178. * @fires QueryBuilder.beforeAddRule
  1179. * @fires QueryBuilder.afterAddRule
  1180. * @fires QueryBuilder.changer:getDefaultFilter
  1181. */
  1182. QueryBuilder.prototype.addRule = function(parent, data, flags) {
  1183. /**
  1184. * Just before adding a rule, can be prevented
  1185. * @event beforeAddRule
  1186. * @memberof QueryBuilder
  1187. * @param {Group} parent
  1188. */
  1189. var e = this.trigger('beforeAddRule', parent);
  1190. if (e.isDefaultPrevented()) {
  1191. return null;
  1192. }
  1193. var rule_id = this.nextRuleId();
  1194. var $rule = $(this.getRuleTemplate(rule_id));
  1195. var model = parent.addRule($rule);
  1196. model.data = data;
  1197. model.flags = $.extend({}, this.settings.default_rule_flags, flags);
  1198. /**
  1199. * Just after adding a rule
  1200. * @event afterAddRule
  1201. * @memberof QueryBuilder
  1202. * @param {Rule} rule
  1203. */
  1204. this.trigger('afterAddRule', model);
  1205. this.trigger('rulesChanged');
  1206. this.createRuleFilters(model);
  1207. if (this.settings.default_filter || !this.settings.display_empty_filter) {
  1208. /**
  1209. * Modifies the default filter for a rule
  1210. * @event changer:getDefaultFilter
  1211. * @memberof QueryBuilder
  1212. * @param {QueryBuilder.Filter} filter
  1213. * @param {Rule} rule
  1214. * @returns {QueryBuilder.Filter}
  1215. */
  1216. model.filter = this.change('getDefaultFilter',
  1217. this.getFilterById(this.settings.default_filter || this.filters[0].id),
  1218. model
  1219. );
  1220. }
  1221. return model;
  1222. };
  1223. /**
  1224. * Tries to delete a rule
  1225. * @param {Rule} rule
  1226. * @returns {boolean} if the rule has been deleted
  1227. * @fires QueryBuilder.beforeDeleteRule
  1228. * @fires QueryBuilder.afterDeleteRule
  1229. */
  1230. QueryBuilder.prototype.deleteRule = function(rule) {
  1231. if (rule.flags.no_delete) {
  1232. return false;
  1233. }
  1234. /**
  1235. * Just before deleting a rule, can be prevented
  1236. * @event beforeDeleteRule
  1237. * @memberof QueryBuilder
  1238. * @param {Rule} rule
  1239. */
  1240. var e = this.trigger('beforeDeleteRule', rule);
  1241. if (e.isDefaultPrevented()) {
  1242. return false;
  1243. }
  1244. rule.drop();
  1245. /**
  1246. * Just after deleting a rule
  1247. * @event afterDeleteRule
  1248. * @memberof QueryBuilder
  1249. */
  1250. this.trigger('afterDeleteRule');
  1251. this.trigger('rulesChanged');
  1252. return true;
  1253. };
  1254. /**
  1255. * Creates the filters for a rule
  1256. * @param {Rule} rule
  1257. * @fires QueryBuilder.changer:getRuleFilters
  1258. * @fires QueryBuilder.afterCreateRuleFilters
  1259. * @private
  1260. */
  1261. QueryBuilder.prototype.createRuleFilters = function(rule) {
  1262. /**
  1263. * Modifies the list a filters available for a rule
  1264. * @event changer:getRuleFilters
  1265. * @memberof QueryBuilder
  1266. * @param {QueryBuilder.Filter[]} filters
  1267. * @param {Rule} rule
  1268. * @returns {QueryBuilder.Filter[]}
  1269. */
  1270. var filters = this.change('getRuleFilters', this.filters, rule);
  1271. var $filterSelect = $(this.getRuleFilterSelect(rule, filters));
  1272. rule.$el.find(QueryBuilder.selectors.filter_container).html($filterSelect);
  1273. /**
  1274. * After creating the dropdown for filters
  1275. * @event afterCreateRuleFilters
  1276. * @memberof QueryBuilder
  1277. * @param {Rule} rule
  1278. */
  1279. this.trigger('afterCreateRuleFilters', rule);
  1280. this.applyRuleFlags(rule);
  1281. };
  1282. /**
  1283. * Creates the operators for a rule and init the rule operator
  1284. * @param {Rule} rule
  1285. * @fires QueryBuilder.afterCreateRuleOperators
  1286. * @private
  1287. */
  1288. QueryBuilder.prototype.createRuleOperators = function(rule) {
  1289. var $operatorContainer = rule.$el.find(QueryBuilder.selectors.operator_container).empty();
  1290. if (!rule.filter) {
  1291. return;
  1292. }
  1293. var operators = this.getOperators(rule.filter);
  1294. var $operatorSelect = $(this.getRuleOperatorSelect(rule, operators));
  1295. $operatorContainer.html($operatorSelect);
  1296. // set the operator without triggering update event
  1297. if (rule.filter.default_operator) {
  1298. rule.__.operator = this.getOperatorByType(rule.filter.default_operator);
  1299. }
  1300. else {
  1301. rule.__.operator = operators[0];
  1302. }
  1303. rule.$el.find(QueryBuilder.selectors.rule_operator).val(rule.operator.type);
  1304. /**
  1305. * After creating the dropdown for operators
  1306. * @event afterCreateRuleOperators
  1307. * @memberof QueryBuilder
  1308. * @param {Rule} rule
  1309. * @param {QueryBuilder.Operator[]} operators - allowed operators for this rule
  1310. */
  1311. this.trigger('afterCreateRuleOperators', rule, operators);
  1312. this.applyRuleFlags(rule);
  1313. };
  1314. /**
  1315. * Creates the main input for a rule
  1316. * @param {Rule} rule
  1317. * @fires QueryBuilder.afterCreateRuleInput
  1318. * @private
  1319. */
  1320. QueryBuilder.prototype.createRuleInput = function(rule) {
  1321. var $valueContainer = rule.$el.find(QueryBuilder.selectors.value_container).empty();
  1322. rule.__.value = undefined;
  1323. if (!rule.filter || !rule.operator || rule.operator.nb_inputs === 0) {
  1324. return;
  1325. }
  1326. var self = this;
  1327. var $inputs = $();
  1328. var filter = rule.filter;
  1329. for (var i = 0; i < rule.operator.nb_inputs; i++) {
  1330. var $ruleInput = $(this.getRuleInput(rule, i));
  1331. if (i > 0) $valueContainer.append(this.settings.inputs_separator);
  1332. $valueContainer.append($ruleInput);
  1333. $inputs = $inputs.add($ruleInput);
  1334. }
  1335. $valueContainer.css('display', '');
  1336. $inputs.on('change ' + (filter.input_event || ''), function() {
  1337. if (!rule._updating_input) {
  1338. rule._updating_value = true;
  1339. rule.value = self.getRuleInputValue(rule);
  1340. rule._updating_value = false;
  1341. }
  1342. });
  1343. if (filter.plugin) {
  1344. $inputs[filter.plugin](filter.plugin_config || {});
  1345. }
  1346. /**
  1347. * After creating the input for a rule and initializing optional plugin
  1348. * @event afterCreateRuleInput
  1349. * @memberof QueryBuilder
  1350. * @param {Rule} rule
  1351. */
  1352. this.trigger('afterCreateRuleInput', rule);
  1353. if (filter.default_value !== undefined) {
  1354. rule.value = filter.default_value;
  1355. }
  1356. else {
  1357. rule._updating_value = true;
  1358. rule.value = self.getRuleInputValue(rule);
  1359. rule._updating_value = false;
  1360. }
  1361. this.applyRuleFlags(rule);
  1362. };
  1363. /**
  1364. * Performs action when a rule's filter changes
  1365. * @param {Rule} rule
  1366. * @param {object} previousFilter
  1367. * @fires QueryBuilder.afterUpdateRuleFilter
  1368. * @private
  1369. */
  1370. QueryBuilder.prototype.updateRuleFilter = function(rule, previousFilter) {
  1371. this.createRuleOperators(rule);
  1372. this.createRuleInput(rule);
  1373. rule.$el.find(QueryBuilder.selectors.rule_filter).val(rule.filter ? rule.filter.id : '-1');
  1374. // clear rule data if the filter changed
  1375. if (previousFilter && rule.filter && previousFilter.id !== rule.filter.id) {
  1376. rule.data = undefined;
  1377. }
  1378. /**
  1379. * After the filter has been updated and the operators and input re-created
  1380. * @event afterUpdateRuleFilter
  1381. * @memberof QueryBuilder
  1382. * @param {Rule} rule
  1383. * @param {object} previousFilter
  1384. */
  1385. this.trigger('afterUpdateRuleFilter', rule, previousFilter);
  1386. this.trigger('rulesChanged');
  1387. };
  1388. /**
  1389. * Performs actions when a rule's operator changes
  1390. * @param {Rule} rule
  1391. * @param {object} previousOperator
  1392. * @fires QueryBuilder.afterUpdateRuleOperator
  1393. * @private
  1394. */
  1395. QueryBuilder.prototype.updateRuleOperator = function(rule, previousOperator) {
  1396. var $valueContainer = rule.$el.find(QueryBuilder.selectors.value_container);
  1397. if (!rule.operator || rule.operator.nb_inputs === 0) {
  1398. $valueContainer.hide();
  1399. rule.__.value = undefined;
  1400. }
  1401. else {
  1402. $valueContainer.css('display', '');
  1403. if ($valueContainer.is(':empty') || !previousOperator ||
  1404. rule.operator.nb_inputs !== previousOperator.nb_inputs ||
  1405. rule.operator.optgroup !== previousOperator.optgroup
  1406. ) {
  1407. this.createRuleInput(rule);
  1408. }
  1409. }
  1410. if (rule.operator) {
  1411. rule.$el.find(QueryBuilder.selectors.rule_operator).val(rule.operator.type);
  1412. // refresh value if the format changed for this operator
  1413. rule.__.value = this.getRuleInputValue(rule);
  1414. }
  1415. /**
  1416. * After the operator has been updated and the input optionally re-created
  1417. * @event afterUpdateRuleOperator
  1418. * @memberof QueryBuilder
  1419. * @param {Rule} rule
  1420. * @param {object} previousOperator
  1421. */
  1422. this.trigger('afterUpdateRuleOperator', rule, previousOperator);
  1423. this.trigger('rulesChanged');
  1424. };
  1425. /**
  1426. * Performs actions when rule's value changes
  1427. * @param {Rule} rule
  1428. * @param {object} previousValue
  1429. * @fires QueryBuilder.afterUpdateRuleValue
  1430. * @private
  1431. */
  1432. QueryBuilder.prototype.updateRuleValue = function(rule, previousValue) {
  1433. if (!rule._updating_value) {
  1434. this.setRuleInputValue(rule, rule.value);
  1435. }
  1436. /**
  1437. * After the rule value has been modified
  1438. * @event afterUpdateRuleValue
  1439. * @memberof QueryBuilder
  1440. * @param {Rule} rule
  1441. * @param {*} previousValue
  1442. */
  1443. this.trigger('afterUpdateRuleValue', rule, previousValue);
  1444. this.trigger('rulesChanged');
  1445. };
  1446. /**
  1447. * Changes a rule's properties depending on its flags
  1448. * @param {Rule} rule
  1449. * @fires QueryBuilder.afterApplyRuleFlags
  1450. * @private
  1451. */
  1452. QueryBuilder.prototype.applyRuleFlags = function(rule) {
  1453. var flags = rule.flags;
  1454. var Selectors = QueryBuilder.selectors;
  1455. rule.$el.find(Selectors.rule_filter).prop('disabled', flags.filter_readonly);
  1456. rule.$el.find(Selectors.rule_operator).prop('disabled', flags.operator_readonly);
  1457. rule.$el.find(Selectors.rule_value).prop('disabled', flags.value_readonly);
  1458. if (flags.no_delete) {
  1459. rule.$el.find(Selectors.delete_rule).remove();
  1460. }
  1461. /**
  1462. * After rule's flags has been applied
  1463. * @event afterApplyRuleFlags
  1464. * @memberof QueryBuilder
  1465. * @param {Rule} rule
  1466. */
  1467. this.trigger('afterApplyRuleFlags', rule);
  1468. };
  1469. /**
  1470. * Changes group's properties depending on its flags
  1471. * @param {Group} group
  1472. * @fires QueryBuilder.afterApplyGroupFlags
  1473. * @private
  1474. */
  1475. QueryBuilder.prototype.applyGroupFlags = function(group) {
  1476. var flags = group.flags;
  1477. var Selectors = QueryBuilder.selectors;
  1478. group.$el.find('>' + Selectors.group_condition).prop('disabled', flags.condition_readonly)
  1479. .parent().toggleClass('readonly', flags.condition_readonly);
  1480. if (flags.no_add_rule) {
  1481. group.$el.find(Selectors.add_rule).remove();
  1482. }
  1483. if (flags.no_add_group) {
  1484. group.$el.find(Selectors.add_group).remove();
  1485. }
  1486. if (flags.no_delete) {
  1487. group.$el.find(Selectors.delete_group).remove();
  1488. }
  1489. /**
  1490. * After group's flags has been applied
  1491. * @event afterApplyGroupFlags
  1492. * @memberof QueryBuilder
  1493. * @param {Group} group
  1494. */
  1495. this.trigger('afterApplyGroupFlags', group);
  1496. };
  1497. /**
  1498. * Clears all errors markers
  1499. * @param {Node} [node] default is root Group
  1500. */
  1501. QueryBuilder.prototype.clearErrors = function(node) {
  1502. node = node || this.model.root;
  1503. if (!node) {
  1504. return;
  1505. }
  1506. node.error = null;
  1507. if (node instanceof Group) {
  1508. node.each(function(rule) {
  1509. rule.error = null;
  1510. }, function(group) {
  1511. this.clearErrors(group);
  1512. }, this);
  1513. }
  1514. };
  1515. /**
  1516. * Adds/Removes error on a Rule or Group
  1517. * @param {Node} node
  1518. * @fires QueryBuilder.changer:displayError
  1519. * @private
  1520. */
  1521. QueryBuilder.prototype.updateError = function(node) {
  1522. if (this.settings.display_errors) {
  1523. if (node.error === null) {
  1524. node.$el.removeClass('has-error');
  1525. }
  1526. else {
  1527. var errorMessage = this.translate('errors', node.error[0]);
  1528. errorMessage = Utils.fmt(errorMessage, node.error.slice(1));
  1529. /**
  1530. * Modifies an error message before display
  1531. * @event changer:displayError
  1532. * @memberof QueryBuilder
  1533. * @param {string} errorMessage - the error message (translated and formatted)
  1534. * @param {array} error - the raw error array (error code and optional arguments)
  1535. * @param {Node} node
  1536. * @returns {string}
  1537. */
  1538. errorMessage = this.change('displayError', errorMessage, node.error, node);
  1539. node.$el.addClass('has-error')
  1540. .find(QueryBuilder.selectors.error_container).eq(0)
  1541. .attr('title', errorMessage);
  1542. }
  1543. }
  1544. };
  1545. /**
  1546. * Triggers a validation error event
  1547. * @param {Node} node
  1548. * @param {string|array} error
  1549. * @param {*} value
  1550. * @fires QueryBuilder.validationError
  1551. * @private
  1552. */
  1553. QueryBuilder.prototype.triggerValidationError = function(node, error, value) {
  1554. if (!$.isArray(error)) {
  1555. error = [error];
  1556. }
  1557. /**
  1558. * Fired when a validation error occurred, can be prevented
  1559. * @event validationError
  1560. * @memberof QueryBuilder
  1561. * @param {Node} node
  1562. * @param {string} error
  1563. * @param {*} value
  1564. */
  1565. var e = this.trigger('validationError', node, error, value);
  1566. if (!e.isDefaultPrevented()) {
  1567. node.error = error;
  1568. }
  1569. };
  1570. /**
  1571. * Destroys the builder
  1572. * @fires QueryBuilder.beforeDestroy
  1573. */
  1574. QueryBuilder.prototype.destroy = function() {
  1575. /**
  1576. * Before the {@link QueryBuilder#destroy} method
  1577. * @event beforeDestroy
  1578. * @memberof QueryBuilder
  1579. */
  1580. this.trigger('beforeDestroy');
  1581. if (this.status.generated_id) {
  1582. this.$el.removeAttr('id');
  1583. }
  1584. this.clear();
  1585. this.model = null;
  1586. this.$el
  1587. .off('.queryBuilder')
  1588. .removeClass('query-builder')
  1589. .removeData('queryBuilder');
  1590. delete this.$el[0].queryBuilder;
  1591. };
  1592. /**
  1593. * Clear all rules and resets the root group
  1594. * @fires QueryBuilder.beforeReset
  1595. * @fires QueryBuilder.afterReset
  1596. */
  1597. QueryBuilder.prototype.reset = function() {
  1598. /**
  1599. * Before the {@link QueryBuilder#reset} method, can be prevented
  1600. * @event beforeReset
  1601. * @memberof QueryBuilder
  1602. */
  1603. var e = this.trigger('beforeReset');
  1604. if (e.isDefaultPrevented()) {
  1605. return;
  1606. }
  1607. this.status.group_id = 1;
  1608. this.status.rule_id = 0;
  1609. this.model.root.empty();
  1610. this.model.root.data = undefined;
  1611. this.model.root.flags = $.extend({}, this.settings.default_group_flags);
  1612. this.model.root.condition = this.settings.default_condition;
  1613. this.addRule(this.model.root);
  1614. /**
  1615. * After the {@link QueryBuilder#reset} method
  1616. * @event afterReset
  1617. * @memberof QueryBuilder
  1618. */
  1619. this.trigger('afterReset');
  1620. this.trigger('rulesChanged');
  1621. };
  1622. /**
  1623. * Clears all rules and removes the root group
  1624. * @fires QueryBuilder.beforeClear
  1625. * @fires QueryBuilder.afterClear
  1626. */
  1627. QueryBuilder.prototype.clear = function() {
  1628. /**
  1629. * Before the {@link QueryBuilder#clear} method, can be prevented
  1630. * @event beforeClear
  1631. * @memberof QueryBuilder
  1632. */
  1633. var e = this.trigger('beforeClear');
  1634. if (e.isDefaultPrevented()) {
  1635. return;
  1636. }
  1637. this.status.group_id = 0;
  1638. this.status.rule_id = 0;
  1639. if (this.model.root) {
  1640. this.model.root.drop();
  1641. this.model.root = null;
  1642. }
  1643. /**
  1644. * After the {@link QueryBuilder#clear} method
  1645. * @event afterClear
  1646. * @memberof QueryBuilder
  1647. */
  1648. this.trigger('afterClear');
  1649. this.trigger('rulesChanged');
  1650. };
  1651. /**
  1652. * Modifies the builder configuration.<br>
  1653. * Only options defined in QueryBuilder.modifiable_options are modifiable
  1654. * @param {object} options
  1655. */
  1656. QueryBuilder.prototype.setOptions = function(options) {
  1657. $.each(options, function(opt, value) {
  1658. if (QueryBuilder.modifiable_options.indexOf(opt) !== -1) {
  1659. this.settings[opt] = value;
  1660. }
  1661. }.bind(this));
  1662. };
  1663. /**
  1664. * Returns the model associated to a DOM object, or the root model
  1665. * @param {jQuery} [target]
  1666. * @returns {Node}
  1667. */
  1668. QueryBuilder.prototype.getModel = function(target) {
  1669. if (!target) {
  1670. return this.model.root;
  1671. }
  1672. else if (target instanceof Node) {
  1673. return target;
  1674. }
  1675. else {
  1676. return $(target).data('queryBuilderModel');
  1677. }
  1678. };
  1679. /**
  1680. * Validates the whole builder
  1681. * @param {object} [options]
  1682. * @param {boolean} [options.skip_empty=false] - skips validating rules that have no filter selected
  1683. * @returns {boolean}
  1684. * @fires QueryBuilder.changer:validate
  1685. */
  1686. QueryBuilder.prototype.validate = function(options) {
  1687. options = $.extend({
  1688. skip_empty: false
  1689. }, options);
  1690. this.clearErrors();
  1691. var self = this;
  1692. var valid = (function parse(group) {
  1693. var done = 0;
  1694. var errors = 0;
  1695. group.each(function(rule) {
  1696. if (!rule.filter && options.skip_empty) {
  1697. return;
  1698. }
  1699. if (!rule.filter) {
  1700. self.triggerValidationError(rule, 'no_filter', null);
  1701. errors++;
  1702. return;
  1703. }
  1704. if (!rule.operator) {
  1705. self.triggerValidationError(rule, 'no_operator', null);
  1706. errors++;
  1707. return;
  1708. }
  1709. if (rule.operator.nb_inputs !== 0) {
  1710. var valid = self.validateValue(rule, rule.value);
  1711. if (valid !== true) {
  1712. self.triggerValidationError(rule, valid, rule.value);
  1713. errors++;
  1714. return;
  1715. }
  1716. }
  1717. done++;
  1718. }, function(group) {
  1719. var res = parse(group);
  1720. if (res === true) {
  1721. done++;
  1722. }
  1723. else if (res === false) {
  1724. errors++;
  1725. }
  1726. });
  1727. if (errors > 0) {
  1728. return false;
  1729. }
  1730. else if (done === 0 && !group.isRoot() && options.skip_empty) {
  1731. return null;
  1732. }
  1733. else if (done === 0 && (!self.settings.allow_empty || !group.isRoot())) {
  1734. self.triggerValidationError(group, 'empty_group', null);
  1735. return false;
  1736. }
  1737. return true;
  1738. }(this.model.root));
  1739. /**
  1740. * Modifies the result of the {@link QueryBuilder#validate} method
  1741. * @event changer:validate
  1742. * @memberof QueryBuilder
  1743. * @param {boolean} valid
  1744. * @returns {boolean}
  1745. */
  1746. return this.change('validate', valid);
  1747. };
  1748. /**
  1749. * Gets an object representing current rules
  1750. * @param {object} [options]
  1751. * @param {boolean|string} [options.get_flags=false] - export flags, true: only changes from default flags or 'all'
  1752. * @param {boolean} [options.allow_invalid=false] - returns rules even if they are invalid
  1753. * @param {boolean} [options.skip_empty=false] - remove rules that have no filter selected
  1754. * @returns {object}
  1755. * @fires QueryBuilder.changer:ruleToJson
  1756. * @fires QueryBuilder.changer:groupToJson
  1757. * @fires QueryBuilder.changer:getRules
  1758. */
  1759. QueryBuilder.prototype.getRules = function(options) {
  1760. options = $.extend({
  1761. get_flags: false,
  1762. allow_invalid: false,
  1763. skip_empty: false
  1764. }, options);
  1765. var valid = this.validate(options);
  1766. if (!valid && !options.allow_invalid) {
  1767. return null;
  1768. }
  1769. var self = this;
  1770. var out = (function parse(group) {
  1771. var groupData = {
  1772. condition: group.condition,
  1773. rules: []
  1774. };
  1775. if (group.data) {
  1776. groupData.data = $.extendext(true, 'replace', {}, group.data);
  1777. }
  1778. if (options.get_flags) {
  1779. var flags = self.getGroupFlags(group.flags, options.get_flags === 'all');
  1780. if (!$.isEmptyObject(flags)) {
  1781. groupData.flags = flags;
  1782. }
  1783. }
  1784. group.each(function(rule) {
  1785. if (!rule.filter && options.skip_empty) {
  1786. return;
  1787. }
  1788. var value = null;
  1789. if (!rule.operator || rule.operator.nb_inputs !== 0) {
  1790. value = rule.value;
  1791. }
  1792. var ruleData = {
  1793. id: rule.filter ? rule.filter.id : null,
  1794. field: rule.filter ? rule.filter.field : null,
  1795. type: rule.filter ? rule.filter.type : null,
  1796. input: rule.filter ? rule.filter.input : null,
  1797. operator: rule.operator ? rule.operator.type : null,
  1798. value: value
  1799. };
  1800. if (rule.filter && rule.filter.data || rule.data) {
  1801. ruleData.data = $.extendext(true, 'replace', {}, rule.filter.data, rule.data);
  1802. }
  1803. if (options.get_flags) {
  1804. var flags = self.getRuleFlags(rule.flags, options.get_flags === 'all');
  1805. if (!$.isEmptyObject(flags)) {
  1806. ruleData.flags = flags;
  1807. }
  1808. }
  1809. /**
  1810. * Modifies the JSON generated from a Rule object
  1811. * @event changer:ruleToJson
  1812. * @memberof QueryBuilder
  1813. * @param {object} json
  1814. * @param {Rule} rule
  1815. * @returns {object}
  1816. */
  1817. groupData.rules.push(self.change('ruleToJson', ruleData, rule));
  1818. }, function(model) {
  1819. var data = parse(model);
  1820. if (data.rules.length !== 0 || !options.skip_empty) {
  1821. groupData.rules.push(data);
  1822. }
  1823. }, this);
  1824. /**
  1825. * Modifies the JSON generated from a Group object
  1826. * @event changer:groupToJson
  1827. * @memberof QueryBuilder
  1828. * @param {object} json
  1829. * @param {Group} group
  1830. * @returns {object}
  1831. */
  1832. return self.change('groupToJson', groupData, group);
  1833. }(this.model.root));
  1834. out.valid = valid;
  1835. /**
  1836. * Modifies the result of the {@link QueryBuilder#getRules} method
  1837. * @event changer:getRules
  1838. * @memberof QueryBuilder
  1839. * @param {object} json
  1840. * @returns {object}
  1841. */
  1842. return this.change('getRules', out);
  1843. };
  1844. /**
  1845. * Sets rules from object
  1846. * @param {object} data
  1847. * @param {object} [options]
  1848. * @param {boolean} [options.allow_invalid=false] - silent-fail if the data are invalid
  1849. * @throws RulesError, UndefinedConditionError
  1850. * @fires QueryBuilder.changer:setRules
  1851. * @fires QueryBuilder.changer:jsonToRule
  1852. * @fires QueryBuilder.changer:jsonToGroup
  1853. * @fires QueryBuilder.afterSetRules
  1854. */
  1855. QueryBuilder.prototype.setRules = function(data, options) {
  1856. options = $.extend({
  1857. allow_invalid: false
  1858. }, options);
  1859. if ($.isArray(data)) {
  1860. data = {
  1861. condition: this.settings.default_condition,
  1862. rules: data
  1863. };
  1864. }
  1865. if (!data || !data.rules || (data.rules.length === 0 && !this.settings.allow_empty)) {
  1866. Utils.error('RulesParse', 'Incorrect data object passed');
  1867. }
  1868. this.clear();
  1869. this.setRoot(false, data.data, this.parseGroupFlags(data));
  1870. /**
  1871. * Modifies data before the {@link QueryBuilder#setRules} method
  1872. * @event changer:setRules
  1873. * @memberof QueryBuilder
  1874. * @param {object} json
  1875. * @param {object} options
  1876. * @returns {object}
  1877. */
  1878. data = this.change('setRules', data, options);
  1879. var self = this;
  1880. (function add(data, group) {
  1881. if (group === null) {
  1882. return;
  1883. }
  1884. if (data.condition === undefined) {
  1885. data.condition = self.settings.default_condition;
  1886. }
  1887. else if (self.settings.conditions.indexOf(data.condition) == -1) {
  1888. Utils.error(!options.allow_invalid, 'UndefinedCondition', 'Invalid condition "{0}"', data.condition);
  1889. data.condition = self.settings.default_condition;
  1890. }
  1891. group.condition = data.condition;
  1892. data.rules.forEach(function(item) {
  1893. var model;
  1894. if (item.rules !== undefined) {
  1895. if (self.settings.allow_groups !== -1 && self.settings.allow_groups < group.level) {
  1896. Utils.error(!options.allow_invalid, 'RulesParse', 'No more than {0} groups are allowed', self.settings.allow_groups);
  1897. self.reset();
  1898. }
  1899. else {
  1900. model = self.addGroup(group, false, item.data, self.parseGroupFlags(item));
  1901. if (model === null) {
  1902. return;
  1903. }
  1904. add(item, model);
  1905. }
  1906. }
  1907. else {
  1908. if (!item.empty) {
  1909. if (item.id === undefined) {
  1910. Utils.error(!options.allow_invalid, 'RulesParse', 'Missing rule field id');
  1911. item.empty = true;
  1912. }
  1913. if (item.operator === undefined) {
  1914. item.operator = 'equal';
  1915. }
  1916. }
  1917. model = self.addRule(group, item.data, self.parseRuleFlags(item));
  1918. if (model === null) {
  1919. return;
  1920. }
  1921. if (!item.empty) {
  1922. model.filter = self.getFilterById(item.id, !options.allow_invalid);
  1923. }
  1924. if (model.filter) {
  1925. model.operator = self.getOperatorByType(item.operator, !options.allow_invalid);
  1926. if (!model.operator) {
  1927. model.operator = self.getOperators(model.filter)[0];
  1928. }
  1929. }
  1930. if (model.operator && model.operator.nb_inputs !== 0) {
  1931. if (item.value !== undefined) {
  1932. model.value = item.value;
  1933. }
  1934. else if (model.filter.default_value !== undefined) {
  1935. model.value = model.filter.default_value;
  1936. }
  1937. }
  1938. /**
  1939. * Modifies the Rule object generated from the JSON
  1940. * @event changer:jsonToRule
  1941. * @memberof QueryBuilder
  1942. * @param {Rule} rule
  1943. * @param {object} json
  1944. * @returns {Rule} the same rule
  1945. */
  1946. if (self.change('jsonToRule', model, item) != model) {
  1947. Utils.error('RulesParse', 'Plugin tried to change rule reference');
  1948. }
  1949. }
  1950. });
  1951. /**
  1952. * Modifies the Group object generated from the JSON
  1953. * @event changer:jsonToGroup
  1954. * @memberof QueryBuilder
  1955. * @param {Group} group
  1956. * @param {object} json
  1957. * @returns {Group} the same group
  1958. */
  1959. if (self.change('jsonToGroup', group, data) != group) {
  1960. Utils.error('RulesParse', 'Plugin tried to change group reference');
  1961. }
  1962. }(data, this.model.root));
  1963. /**
  1964. * After the {@link QueryBuilder#setRules} method
  1965. * @event afterSetRules
  1966. * @memberof QueryBuilder
  1967. */
  1968. this.trigger('afterSetRules');
  1969. };
  1970. /**
  1971. * Performs value validation
  1972. * @param {Rule} rule
  1973. * @param {string|string[]} value
  1974. * @returns {array|boolean} true or error array
  1975. * @fires QueryBuilder.changer:validateValue
  1976. */
  1977. QueryBuilder.prototype.validateValue = function(rule, value) {
  1978. var validation = rule.filter.validation || {};
  1979. var result = true;
  1980. if (validation.callback) {
  1981. result = validation.callback.call(this, value, rule);
  1982. }
  1983. else {
  1984. result = this._validateValue(rule, value);
  1985. }
  1986. /**
  1987. * Modifies the result of the rule validation method
  1988. * @event changer:validateValue
  1989. * @memberof QueryBuilder
  1990. * @param {array|boolean} result - true or an error array
  1991. * @param {*} value
  1992. * @param {Rule} rule
  1993. * @returns {array|boolean}
  1994. */
  1995. return this.change('validateValue', result, value, rule);
  1996. };
  1997. /**
  1998. * Default validation function
  1999. * @param {Rule} rule
  2000. * @param {string|string[]} value
  2001. * @returns {array|boolean} true or error array
  2002. * @throws ConfigError
  2003. * @private
  2004. */
  2005. QueryBuilder.prototype._validateValue = function(rule, value) {
  2006. var filter = rule.filter;
  2007. var operator = rule.operator;
  2008. var validation = filter.validation || {};
  2009. var result = true;
  2010. var tmp, tempValue;
  2011. if (rule.operator.nb_inputs === 1) {
  2012. value = [value];
  2013. }
  2014. for (var i = 0; i < operator.nb_inputs; i++) {
  2015. if (!operator.multiple && $.isArray(value[i]) && value[i].length > 1) {
  2016. result = ['operator_not_multiple', operator.type, this.translate('operators', operator.type)];
  2017. break;
  2018. }
  2019. switch (filter.input) {
  2020. case 'radio':
  2021. if (value[i] === undefined || value[i].length === 0) {
  2022. if (!validation.allow_empty_value) {
  2023. result = ['radio_empty'];
  2024. }
  2025. break;
  2026. }
  2027. break;
  2028. case 'checkbox':
  2029. if (value[i] === undefined || value[i].length === 0) {
  2030. if (!validation.allow_empty_value) {
  2031. result = ['checkbox_empty'];
  2032. }
  2033. break;
  2034. }
  2035. break;
  2036. case 'select':
  2037. if (value[i] === undefined || value[i].length === 0 || (filter.placeholder && value[i] == filter.placeholder_value)) {
  2038. if (!validation.allow_empty_value) {
  2039. result = ['select_empty'];
  2040. }
  2041. break;
  2042. }
  2043. break;
  2044. default:
  2045. tempValue = $.isArray(value[i]) ? value[i] : [value[i]];
  2046. for (var j = 0; j < tempValue.length; j++) {
  2047. switch (QueryBuilder.types[filter.type]) {
  2048. case 'string':
  2049. if (tempValue[j] === undefined || tempValue[j].length === 0) {
  2050. if (!validation.allow_empty_value) {
  2051. result = ['string_empty'];
  2052. }
  2053. break;
  2054. }
  2055. if (validation.min !== undefined) {
  2056. if (tempValue[j].length < parseInt(validation.min)) {
  2057. result = [this.getValidationMessage(validation, 'min', 'string_exceed_min_length'), validation.min];
  2058. break;
  2059. }
  2060. }
  2061. if (validation.max !== undefined) {
  2062. if (tempValue[j].length > parseInt(validation.max)) {
  2063. result = [this.getValidationMessage(validation, 'max', 'string_exceed_max_length'), validation.max];
  2064. break;
  2065. }
  2066. }
  2067. if (validation.format) {
  2068. if (typeof validation.format == 'string') {
  2069. validation.format = new RegExp(validation.format);
  2070. }
  2071. if (!validation.format.test(tempValue[j])) {
  2072. result = [this.getValidationMessage(validation, 'format', 'string_invalid_format'), validation.format];
  2073. break;
  2074. }
  2075. }
  2076. break;
  2077. case 'number':
  2078. if (tempValue[j] === undefined || tempValue[j].length === 0) {
  2079. if (!validation.allow_empty_value) {
  2080. result = ['number_nan'];
  2081. }
  2082. break;
  2083. }
  2084. if (isNaN(tempValue[j])) {
  2085. result = ['number_nan'];
  2086. break;
  2087. }
  2088. if (filter.type == 'integer') {
  2089. if (parseInt(tempValue[j]) != tempValue[j]) {
  2090. result = ['number_not_integer'];
  2091. break;
  2092. }
  2093. }
  2094. else {
  2095. if (parseFloat(tempValue[j]) != tempValue[j]) {
  2096. result = ['number_not_double'];
  2097. break;
  2098. }
  2099. }
  2100. if (validation.min !== undefined) {
  2101. if (tempValue[j] < parseFloat(validation.min)) {
  2102. result = [this.getValidationMessage(validation, 'min', 'number_exceed_min'), validation.min];
  2103. break;
  2104. }
  2105. }
  2106. if (validation.max !== undefined) {
  2107. if (tempValue[j] > parseFloat(validation.max)) {
  2108. result = [this.getValidationMessage(validation, 'max', 'number_exceed_max'), validation.max];
  2109. break;
  2110. }
  2111. }
  2112. if (validation.step !== undefined && validation.step !== 'any') {
  2113. var v = (tempValue[j] / validation.step).toPrecision(14);
  2114. if (parseInt(v) != v) {
  2115. result = [this.getValidationMessage(validation, 'step', 'number_wrong_step'), validation.step];
  2116. break;
  2117. }
  2118. }
  2119. break;
  2120. case 'datetime':
  2121. if (tempValue[j] === undefined || tempValue[j].length === 0) {
  2122. if (!validation.allow_empty_value) {
  2123. result = ['datetime_empty'];
  2124. }
  2125. break;
  2126. }
  2127. // we need MomentJS
  2128. if (validation.format) {
  2129. if (!('moment' in window)) {
  2130. Utils.error('MissingLibrary', 'MomentJS is required for Date/Time validation. Get it here http://momentjs.com');
  2131. }
  2132. var datetime = moment(tempValue[j], validation.format);
  2133. if (!datetime.isValid()) {
  2134. result = [this.getValidationMessage(validation, 'format', 'datetime_invalid'), validation.format];
  2135. break;
  2136. }
  2137. else {
  2138. if (validation.min) {
  2139. if (datetime < moment(validation.min, validation.format)) {
  2140. result = [this.getValidationMessage(validation, 'min', 'datetime_exceed_min'), validation.min];
  2141. break;
  2142. }
  2143. }
  2144. if (validation.max) {
  2145. if (datetime > moment(validation.max, validation.format)) {
  2146. result = [this.getValidationMessage(validation, 'max', 'datetime_exceed_max'), validation.max];
  2147. break;
  2148. }
  2149. }
  2150. }
  2151. }
  2152. break;
  2153. case 'boolean':
  2154. if (tempValue[j] === undefined || tempValue[j].length === 0) {
  2155. if (!validation.allow_empty_value) {
  2156. result = ['boolean_not_valid'];
  2157. }
  2158. break;
  2159. }
  2160. tmp = ('' + tempValue[j]).trim().toLowerCase();
  2161. if (tmp !== 'true' && tmp !== 'false' && tmp !== '1' && tmp !== '0' && tempValue[j] !== 1 && tempValue[j] !== 0) {
  2162. result = ['boolean_not_valid'];
  2163. break;
  2164. }
  2165. }
  2166. if (result !== true) {
  2167. break;
  2168. }
  2169. }
  2170. }
  2171. if (result !== true) {
  2172. break;
  2173. }
  2174. }
  2175. if ((rule.operator.type === 'between' || rule.operator.type === 'not_between') && value.length === 2) {
  2176. switch (QueryBuilder.types[filter.type]) {
  2177. case 'number':
  2178. if (value[0] > value[1]) {
  2179. result = ['number_between_invalid', value[0], value[1]];
  2180. }
  2181. break;
  2182. case 'datetime':
  2183. // we need MomentJS
  2184. if (validation.format) {
  2185. if (!('moment' in window)) {
  2186. Utils.error('MissingLibrary', 'MomentJS is required for Date/Time validation. Get it here http://momentjs.com');
  2187. }
  2188. if (moment(value[0], validation.format).isAfter(moment(value[1], validation.format))) {
  2189. result = ['datetime_between_invalid', value[0], value[1]];
  2190. }
  2191. }
  2192. break;
  2193. }
  2194. }
  2195. return result;
  2196. };
  2197. /**
  2198. * Returns an incremented group ID
  2199. * @returns {string}
  2200. * @private
  2201. */
  2202. QueryBuilder.prototype.nextGroupId = function() {
  2203. return this.status.id + '_group_' + (this.status.group_id++);
  2204. };
  2205. /**
  2206. * Returns an incremented rule ID
  2207. * @returns {string}
  2208. * @private
  2209. */
  2210. QueryBuilder.prototype.nextRuleId = function() {
  2211. return this.status.id + '_rule_' + (this.status.rule_id++);
  2212. };
  2213. /**
  2214. * Returns the operators for a filter
  2215. * @param {string|object} filter - filter id or filter object
  2216. * @returns {object[]}
  2217. * @fires QueryBuilder.changer:getOperators
  2218. * @private
  2219. */
  2220. QueryBuilder.prototype.getOperators = function(filter) {
  2221. if (typeof filter == 'string') {
  2222. filter = this.getFilterById(filter);
  2223. }
  2224. var result = [];
  2225. for (var i = 0, l = this.operators.length; i < l; i++) {
  2226. // filter operators check
  2227. if (filter.operators) {
  2228. if (filter.operators.indexOf(this.operators[i].type) == -1) {
  2229. continue;
  2230. }
  2231. }
  2232. // type check
  2233. else if (this.operators[i].apply_to.indexOf(QueryBuilder.types[filter.type]) == -1) {
  2234. continue;
  2235. }
  2236. result.push(this.operators[i]);
  2237. }
  2238. // keep sort order defined for the filter
  2239. if (filter.operators) {
  2240. result.sort(function(a, b) {
  2241. return filter.operators.indexOf(a.type) - filter.operators.indexOf(b.type);
  2242. });
  2243. }
  2244. /**
  2245. * Modifies the operators available for a filter
  2246. * @event changer:getOperators
  2247. * @memberof QueryBuilder
  2248. * @param {QueryBuilder.Operator[]} operators
  2249. * @param {QueryBuilder.Filter} filter
  2250. * @returns {QueryBuilder.Operator[]}
  2251. */
  2252. return this.change('getOperators', result, filter);
  2253. };
  2254. /**
  2255. * Returns a particular filter by its id
  2256. * @param {string} id
  2257. * @param {boolean} [doThrow=true]
  2258. * @returns {object|null}
  2259. * @throws UndefinedFilterError
  2260. * @private
  2261. */
  2262. QueryBuilder.prototype.getFilterById = function(id, doThrow) {
  2263. if (id == '-1') {
  2264. return null;
  2265. }
  2266. for (var i = 0, l = this.filters.length; i < l; i++) {
  2267. if (this.filters[i].id == id) {
  2268. return this.filters[i];
  2269. }
  2270. }
  2271. Utils.error(doThrow !== false, 'UndefinedFilter', 'Undefined filter "{0}"', id);
  2272. return null;
  2273. };
  2274. /**
  2275. * Returns a particular operator by its type
  2276. * @param {string} type
  2277. * @param {boolean} [doThrow=true]
  2278. * @returns {object|null}
  2279. * @throws UndefinedOperatorError
  2280. * @private
  2281. */
  2282. QueryBuilder.prototype.getOperatorByType = function(type, doThrow) {
  2283. if (type == '-1') {
  2284. return null;
  2285. }
  2286. for (var i = 0, l = this.operators.length; i < l; i++) {
  2287. if (this.operators[i].type == type) {
  2288. return this.operators[i];
  2289. }
  2290. }
  2291. Utils.error(doThrow !== false, 'UndefinedOperator', 'Undefined operator "{0}"', type);
  2292. return null;
  2293. };
  2294. /**
  2295. * Returns rule's current input value
  2296. * @param {Rule} rule
  2297. * @returns {*}
  2298. * @fires QueryBuilder.changer:getRuleValue
  2299. * @private
  2300. */
  2301. QueryBuilder.prototype.getRuleInputValue = function(rule) {
  2302. var filter = rule.filter;
  2303. var operator = rule.operator;
  2304. var value = [];
  2305. if (filter.valueGetter) {
  2306. value = filter.valueGetter.call(this, rule);
  2307. }
  2308. else {
  2309. var $value = rule.$el.find(QueryBuilder.selectors.value_container);
  2310. for (var i = 0; i < operator.nb_inputs; i++) {
  2311. var name = Utils.escapeElementId(rule.id + '_value_' + i);
  2312. var tmp;
  2313. switch (filter.input) {
  2314. case 'radio':
  2315. value.push($value.find('[name=' + name + ']:checked').val());
  2316. break;
  2317. case 'checkbox':
  2318. tmp = [];
  2319. // jshint loopfunc:true
  2320. $value.find('[name=' + name + ']:checked').each(function() {
  2321. tmp.push($(this).val());
  2322. });
  2323. // jshint loopfunc:false
  2324. value.push(tmp);
  2325. break;
  2326. case 'select':
  2327. if (filter.multiple) {
  2328. tmp = [];
  2329. // jshint loopfunc:true
  2330. $value.find('[name=' + name + '] option:selected').each(function() {
  2331. tmp.push($(this).val());
  2332. });
  2333. // jshint loopfunc:false
  2334. value.push(tmp);
  2335. }
  2336. else {
  2337. value.push($value.find('[name=' + name + '] option:selected').val());
  2338. }
  2339. break;
  2340. default:
  2341. value.push($value.find('[name=' + name + ']').val());
  2342. }
  2343. }
  2344. value = value.map(function(val) {
  2345. if (operator.multiple && filter.value_separator && typeof val == 'string') {
  2346. val = val.split(filter.value_separator);
  2347. }
  2348. if ($.isArray(val)) {
  2349. return val.map(function(subval) {
  2350. return Utils.changeType(subval, filter.type);
  2351. });
  2352. }
  2353. else {
  2354. return Utils.changeType(val, filter.type);
  2355. }
  2356. });
  2357. if (operator.nb_inputs === 1) {
  2358. value = value[0];
  2359. }
  2360. // @deprecated
  2361. if (filter.valueParser) {
  2362. value = filter.valueParser.call(this, rule, value);
  2363. }
  2364. }
  2365. /**
  2366. * Modifies the rule's value grabbed from the DOM
  2367. * @event changer:getRuleValue
  2368. * @memberof QueryBuilder
  2369. * @param {*} value
  2370. * @param {Rule} rule
  2371. * @returns {*}
  2372. */
  2373. return this.change('getRuleValue', value, rule);
  2374. };
  2375. /**
  2376. * Sets the value of a rule's input
  2377. * @param {Rule} rule
  2378. * @param {*} value
  2379. * @private
  2380. */
  2381. QueryBuilder.prototype.setRuleInputValue = function(rule, value) {
  2382. var filter = rule.filter;
  2383. var operator = rule.operator;
  2384. if (!filter || !operator) {
  2385. return;
  2386. }
  2387. rule._updating_input = true;
  2388. if (filter.valueSetter) {
  2389. filter.valueSetter.call(this, rule, value);
  2390. }
  2391. else {
  2392. var $value = rule.$el.find(QueryBuilder.selectors.value_container);
  2393. if (operator.nb_inputs == 1) {
  2394. value = [value];
  2395. }
  2396. for (var i = 0; i < operator.nb_inputs; i++) {
  2397. var name = Utils.escapeElementId(rule.id + '_value_' + i);
  2398. switch (filter.input) {
  2399. case 'radio':
  2400. $value.find('[name=' + name + '][value="' + value[i] + '"]').prop('checked', true).trigger('change');
  2401. break;
  2402. case 'checkbox':
  2403. if (!$.isArray(value[i])) {
  2404. value[i] = [value[i]];
  2405. }
  2406. // jshint loopfunc:true
  2407. value[i].forEach(function(value) {
  2408. $value.find('[name=' + name + '][value="' + value + '"]').prop('checked', true).trigger('change');
  2409. });
  2410. // jshint loopfunc:false
  2411. break;
  2412. default:
  2413. if (operator.multiple && filter.value_separator && $.isArray(value[i])) {
  2414. value[i] = value[i].join(filter.value_separator);
  2415. }
  2416. $value.find('[name=' + name + ']').val(value[i]).trigger('change');
  2417. break;
  2418. }
  2419. }
  2420. }
  2421. rule._updating_input = false;
  2422. };
  2423. /**
  2424. * Parses rule flags
  2425. * @param {object} rule
  2426. * @returns {object}
  2427. * @fires QueryBuilder.changer:parseRuleFlags
  2428. * @private
  2429. */
  2430. QueryBuilder.prototype.parseRuleFlags = function(rule) {
  2431. var flags = $.extend({}, this.settings.default_rule_flags);
  2432. if (rule.readonly) {
  2433. $.extend(flags, {
  2434. filter_readonly: true,
  2435. operator_readonly: true,
  2436. value_readonly: true,
  2437. no_delete: true
  2438. });
  2439. }
  2440. if (rule.flags) {
  2441. $.extend(flags, rule.flags);
  2442. }
  2443. /**
  2444. * Modifies the consolidated rule's flags
  2445. * @event changer:parseRuleFlags
  2446. * @memberof QueryBuilder
  2447. * @param {object} flags
  2448. * @param {object} rule - <b>not</b> a Rule object
  2449. * @returns {object}
  2450. */
  2451. return this.change('parseRuleFlags', flags, rule);
  2452. };
  2453. /**
  2454. * Gets a copy of flags of a rule
  2455. * @param {object} flags
  2456. * @param {boolean} [all=false] - return all flags or only changes from default flags
  2457. * @returns {object}
  2458. * @private
  2459. */
  2460. QueryBuilder.prototype.getRuleFlags = function(flags, all) {
  2461. if (all) {
  2462. return $.extend({}, flags);
  2463. }
  2464. else {
  2465. var ret = {};
  2466. $.each(this.settings.default_rule_flags, function(key, value) {
  2467. if (flags[key] !== value) {
  2468. ret[key] = flags[key];
  2469. }
  2470. });
  2471. return ret;
  2472. }
  2473. };
  2474. /**
  2475. * Parses group flags
  2476. * @param {object} group
  2477. * @returns {object}
  2478. * @fires QueryBuilder.changer:parseGroupFlags
  2479. * @private
  2480. */
  2481. QueryBuilder.prototype.parseGroupFlags = function(group) {
  2482. var flags = $.extend({}, this.settings.default_group_flags);
  2483. if (group.readonly) {
  2484. $.extend(flags, {
  2485. condition_readonly: true,
  2486. no_add_rule: true,
  2487. no_add_group: true,
  2488. no_delete: true
  2489. });
  2490. }
  2491. if (group.flags) {
  2492. $.extend(flags, group.flags);
  2493. }
  2494. /**
  2495. * Modifies the consolidated group's flags
  2496. * @event changer:parseGroupFlags
  2497. * @memberof QueryBuilder
  2498. * @param {object} flags
  2499. * @param {object} group - <b>not</b> a Group object
  2500. * @returns {object}
  2501. */
  2502. return this.change('parseGroupFlags', flags, group);
  2503. };
  2504. /**
  2505. * Gets a copy of flags of a group
  2506. * @param {object} flags
  2507. * @param {boolean} [all=false] - return all flags or only changes from default flags
  2508. * @returns {object}
  2509. * @private
  2510. */
  2511. QueryBuilder.prototype.getGroupFlags = function(flags, all) {
  2512. if (all) {
  2513. return $.extend({}, flags);
  2514. }
  2515. else {
  2516. var ret = {};
  2517. $.each(this.settings.default_group_flags, function(key, value) {
  2518. if (flags[key] !== value) {
  2519. ret[key] = flags[key];
  2520. }
  2521. });
  2522. return ret;
  2523. }
  2524. };
  2525. /**
  2526. * Translate a label either by looking in the `lang` object or in itself if it's an object where keys are language codes
  2527. * @param {string} [category]
  2528. * @param {string|object} key
  2529. * @returns {string}
  2530. * @fires QueryBuilder.changer:translate
  2531. */
  2532. QueryBuilder.prototype.translate = function(category, key) {
  2533. if (!key) {
  2534. key = category;
  2535. category = undefined;
  2536. }
  2537. var translation;
  2538. if (typeof key === 'object') {
  2539. translation = key[this.settings.lang_code] || key['en'];
  2540. }
  2541. else {
  2542. translation = (category ? this.lang[category] : this.lang)[key] || key;
  2543. }
  2544. /**
  2545. * Modifies the translated label
  2546. * @event changer:translate
  2547. * @memberof QueryBuilder
  2548. * @param {string} translation
  2549. * @param {string|object} key
  2550. * @param {string} [category]
  2551. * @returns {string}
  2552. */
  2553. return this.change('translate', translation, key, category);
  2554. };
  2555. /**
  2556. * Returns a validation message
  2557. * @param {object} validation
  2558. * @param {string} type
  2559. * @param {string} def
  2560. * @returns {string}
  2561. * @private
  2562. */
  2563. QueryBuilder.prototype.getValidationMessage = function(validation, type, def) {
  2564. return validation.messages && validation.messages[type] || def;
  2565. };
  2566. QueryBuilder.templates.group = '\
  2567. <div id="{{= it.group_id }}" class="rules-group-container"> \
  2568. <div class="rules-group-header"> \
  2569. <div class="btn-group pull-right group-actions"> \
  2570. <button type="button" class="btn btn-xs btn-success" data-add="rule"> \
  2571. <i class="{{= it.icons.add_rule }}"></i> {{= it.translate("add_rule") }} \
  2572. </button> \
  2573. {{? it.settings.allow_groups===-1 || it.settings.allow_groups>=it.level }} \
  2574. <button type="button" class="btn btn-xs btn-success" data-add="group"> \
  2575. <i class="{{= it.icons.add_group }}"></i> {{= it.translate("add_group") }} \
  2576. </button> \
  2577. {{?}} \
  2578. {{? it.level>1 }} \
  2579. <button type="button" class="btn btn-xs btn-danger" data-delete="group"> \
  2580. <i class="{{= it.icons.remove_group }}"></i> {{= it.translate("delete_group") }} \
  2581. </button> \
  2582. {{?}} \
  2583. </div> \
  2584. <div class="btn-group group-conditions"> \
  2585. {{~ it.conditions: condition }} \
  2586. <label class="btn btn-xs btn-primary"> \
  2587. <input type="radio" name="{{= it.group_id }}_cond" value="{{= condition }}"> {{= it.translate("conditions", condition) }} \
  2588. </label> \
  2589. {{~}} \
  2590. </div> \
  2591. {{? it.settings.display_errors }} \
  2592. <div class="error-container"><i class="{{= it.icons.error }}"></i></div> \
  2593. {{?}} \
  2594. </div> \
  2595. <div class=rules-group-body> \
  2596. <div class=rules-list></div> \
  2597. </div> \
  2598. </div>';
  2599. QueryBuilder.templates.rule = '\
  2600. <div id="{{= it.rule_id }}" class="rule-container"> \
  2601. <div class="rule-header"> \
  2602. <div class="btn-group pull-right rule-actions"> \
  2603. <button type="button" class="btn btn-xs btn-danger" data-delete="rule"> \
  2604. <i class="{{= it.icons.remove_rule }}"></i> {{= it.translate("delete_rule") }} \
  2605. </button> \
  2606. </div> \
  2607. </div> \
  2608. {{? it.settings.display_errors }} \
  2609. <div class="error-container"><i class="{{= it.icons.error }}"></i></div> \
  2610. {{?}} \
  2611. <div class="rule-filter-container"></div> \
  2612. <div class="rule-operator-container"></div> \
  2613. <div class="rule-value-container"></div> \
  2614. </div>';
  2615. QueryBuilder.templates.filterSelect = '\
  2616. {{ var optgroup = null; }} \
  2617. <select class="form-control" name="{{= it.rule.id }}_filter"> \
  2618. {{? it.settings.display_empty_filter }} \
  2619. <option value="-1">{{= it.settings.select_placeholder }}</option> \
  2620. {{?}} \
  2621. {{~ it.filters: filter }} \
  2622. {{? optgroup !== filter.optgroup }} \
  2623. {{? optgroup !== null }}</optgroup>{{?}} \
  2624. {{? (optgroup = filter.optgroup) !== null }} \
  2625. <optgroup label="{{= it.translate(it.settings.optgroups[optgroup]) }}"> \
  2626. {{?}} \
  2627. {{?}} \
  2628. <option value="{{= filter.id }}" {{? filter.icon}}data-icon="{{= filter.icon}}"{{?}}>{{= it.translate(filter.label) }}</option> \
  2629. {{~}} \
  2630. {{? optgroup !== null }}</optgroup>{{?}} \
  2631. </select>';
  2632. QueryBuilder.templates.operatorSelect = '\
  2633. {{? it.operators.length === 1 }} \
  2634. <span> \
  2635. {{= it.translate("operators", it.operators[0].type) }} \
  2636. </span> \
  2637. {{?}} \
  2638. {{ var optgroup = null; }} \
  2639. <select class="form-control {{? it.operators.length === 1 }}hide{{?}}" name="{{= it.rule.id }}_operator"> \
  2640. {{~ it.operators: operator }} \
  2641. {{? optgroup !== operator.optgroup }} \
  2642. {{? optgroup !== null }}</optgroup>{{?}} \
  2643. {{? (optgroup = operator.optgroup) !== null }} \
  2644. <optgroup label="{{= it.translate(it.settings.optgroups[optgroup]) }}"> \
  2645. {{?}} \
  2646. {{?}} \
  2647. <option value="{{= operator.type }}" {{? operator.icon}}data-icon="{{= operator.icon}}"{{?}}>{{= it.translate("operators", operator.type) }}</option> \
  2648. {{~}} \
  2649. {{? optgroup !== null }}</optgroup>{{?}} \
  2650. </select>';
  2651. QueryBuilder.templates.ruleValueSelect = '\
  2652. {{ var optgroup = null; }} \
  2653. <select class="form-control" name="{{= it.name }}" {{? it.rule.filter.multiple }}multiple{{?}}> \
  2654. {{? it.rule.filter.placeholder }} \
  2655. <option value="{{= it.rule.filter.placeholder_value }}" disabled selected>{{= it.rule.filter.placeholder }}</option> \
  2656. {{?}} \
  2657. {{~ it.rule.filter.values: entry }} \
  2658. {{? optgroup !== entry.optgroup }} \
  2659. {{? optgroup !== null }}</optgroup>{{?}} \
  2660. {{? (optgroup = entry.optgroup) !== null }} \
  2661. <optgroup label="{{= it.translate(it.settings.optgroups[optgroup]) }}"> \
  2662. {{?}} \
  2663. {{?}} \
  2664. <option value="{{= entry.value }}">{{= entry.label }}</option> \
  2665. {{~}} \
  2666. {{? optgroup !== null }}</optgroup>{{?}} \
  2667. </select>';
  2668. /**
  2669. * Returns group's HTML
  2670. * @param {string} group_id
  2671. * @param {int} level
  2672. * @returns {string}
  2673. * @fires QueryBuilder.changer:getGroupTemplate
  2674. * @private
  2675. */
  2676. QueryBuilder.prototype.getGroupTemplate = function(group_id, level) {
  2677. var h = this.templates.group({
  2678. builder: this,
  2679. group_id: group_id,
  2680. level: level,
  2681. conditions: this.settings.conditions,
  2682. icons: this.icons,
  2683. settings: this.settings,
  2684. translate: this.translate.bind(this)
  2685. });
  2686. /**
  2687. * Modifies the raw HTML of a group
  2688. * @event changer:getGroupTemplate
  2689. * @memberof QueryBuilder
  2690. * @param {string} html
  2691. * @param {int} level
  2692. * @returns {string}
  2693. */
  2694. return this.change('getGroupTemplate', h, level);
  2695. };
  2696. /**
  2697. * Returns rule's HTML
  2698. * @param {string} rule_id
  2699. * @returns {string}
  2700. * @fires QueryBuilder.changer:getRuleTemplate
  2701. * @private
  2702. */
  2703. QueryBuilder.prototype.getRuleTemplate = function(rule_id) {
  2704. var h = this.templates.rule({
  2705. builder: this,
  2706. rule_id: rule_id,
  2707. icons: this.icons,
  2708. settings: this.settings,
  2709. translate: this.translate.bind(this)
  2710. });
  2711. /**
  2712. * Modifies the raw HTML of a rule
  2713. * @event changer:getRuleTemplate
  2714. * @memberof QueryBuilder
  2715. * @param {string} html
  2716. * @returns {string}
  2717. */
  2718. return this.change('getRuleTemplate', h);
  2719. };
  2720. /**
  2721. * Returns rule's filter HTML
  2722. * @param {Rule} rule
  2723. * @param {object[]} filters
  2724. * @returns {string}
  2725. * @fires QueryBuilder.changer:getRuleFilterTemplate
  2726. * @private
  2727. */
  2728. QueryBuilder.prototype.getRuleFilterSelect = function(rule, filters) {
  2729. var h = this.templates.filterSelect({
  2730. builder: this,
  2731. rule: rule,
  2732. filters: filters,
  2733. icons: this.icons,
  2734. settings: this.settings,
  2735. translate: this.translate.bind(this)
  2736. });
  2737. /**
  2738. * Modifies the raw HTML of the rule's filter dropdown
  2739. * @event changer:getRuleFilterSelect
  2740. * @memberof QueryBuilder
  2741. * @param {string} html
  2742. * @param {Rule} rule
  2743. * @param {QueryBuilder.Filter[]} filters
  2744. * @returns {string}
  2745. */
  2746. return this.change('getRuleFilterSelect', h, rule, filters);
  2747. };
  2748. /**
  2749. * Returns rule's operator HTML
  2750. * @param {Rule} rule
  2751. * @param {object[]} operators
  2752. * @returns {string}
  2753. * @fires QueryBuilder.changer:getRuleOperatorTemplate
  2754. * @private
  2755. */
  2756. QueryBuilder.prototype.getRuleOperatorSelect = function(rule, operators) {
  2757. var h = this.templates.operatorSelect({
  2758. builder: this,
  2759. rule: rule,
  2760. operators: operators,
  2761. icons: this.icons,
  2762. settings: this.settings,
  2763. translate: this.translate.bind(this)
  2764. });
  2765. /**
  2766. * Modifies the raw HTML of the rule's operator dropdown
  2767. * @event changer:getRuleOperatorSelect
  2768. * @memberof QueryBuilder
  2769. * @param {string} html
  2770. * @param {Rule} rule
  2771. * @param {QueryBuilder.Operator[]} operators
  2772. * @returns {string}
  2773. */
  2774. return this.change('getRuleOperatorSelect', h, rule, operators);
  2775. };
  2776. /**
  2777. * Returns the rule's value select HTML
  2778. * @param {string} name
  2779. * @param {Rule} rule
  2780. * @returns {string}
  2781. * @fires QueryBuilder.changer:getRuleValueSelect
  2782. * @private
  2783. */
  2784. QueryBuilder.prototype.getRuleValueSelect = function(name, rule) {
  2785. var h = this.templates.ruleValueSelect({
  2786. builder: this,
  2787. name: name,
  2788. rule: rule,
  2789. icons: this.icons,
  2790. settings: this.settings,
  2791. translate: this.translate.bind(this)
  2792. });
  2793. /**
  2794. * Modifies the raw HTML of the rule's value dropdown (in case of a "select filter)
  2795. * @event changer:getRuleValueSelect
  2796. * @memberof QueryBuilder
  2797. * @param {string} html
  2798. * @param [string} name
  2799. * @param {Rule} rule
  2800. * @returns {string}
  2801. */
  2802. return this.change('getRuleValueSelect', h, name, rule);
  2803. };
  2804. /**
  2805. * Returns the rule's value HTML
  2806. * @param {Rule} rule
  2807. * @param {int} value_id
  2808. * @returns {string}
  2809. * @fires QueryBuilder.changer:getRuleInput
  2810. * @private
  2811. */
  2812. QueryBuilder.prototype.getRuleInput = function(rule, value_id) {
  2813. var filter = rule.filter;
  2814. var validation = rule.filter.validation || {};
  2815. var name = rule.id + '_value_' + value_id;
  2816. var c = filter.vertical ? ' class=block' : '';
  2817. var h = '';
  2818. if (typeof filter.input == 'function') {
  2819. h = filter.input.call(this, rule, name);
  2820. }
  2821. else {
  2822. switch (filter.input) {
  2823. case 'radio':
  2824. case 'checkbox':
  2825. Utils.iterateOptions(filter.values, function(key, val) {
  2826. h += '<label' + c + '><input type="' + filter.input + '" name="' + name + '" value="' + key + '"> ' + val + '</label> ';
  2827. });
  2828. break;
  2829. case 'select':
  2830. h = this.getRuleValueSelect(name, rule);
  2831. break;
  2832. case 'textarea':
  2833. h += '<textarea class="form-control" name="' + name + '"';
  2834. if (filter.size) h += ' cols="' + filter.size + '"';
  2835. if (filter.rows) h += ' rows="' + filter.rows + '"';
  2836. if (validation.min !== undefined) h += ' minlength="' + validation.min + '"';
  2837. if (validation.max !== undefined) h += ' maxlength="' + validation.max + '"';
  2838. if (filter.placeholder) h += ' placeholder="' + filter.placeholder + '"';
  2839. h += '></textarea>';
  2840. break;
  2841. case 'number':
  2842. h += '<input class="form-control" type="number" name="' + name + '"';
  2843. if (validation.step !== undefined) h += ' step="' + validation.step + '"';
  2844. if (validation.min !== undefined) h += ' min="' + validation.min + '"';
  2845. if (validation.max !== undefined) h += ' max="' + validation.max + '"';
  2846. if (filter.placeholder) h += ' placeholder="' + filter.placeholder + '"';
  2847. if (filter.size) h += ' size="' + filter.size + '"';
  2848. h += '>';
  2849. break;
  2850. default:
  2851. h += '<input class="form-control" type="text" name="' + name + '"';
  2852. if (filter.placeholder) h += ' placeholder="' + filter.placeholder + '"';
  2853. if (filter.type === 'string' && validation.min !== undefined) h += ' minlength="' + validation.min + '"';
  2854. if (filter.type === 'string' && validation.max !== undefined) h += ' maxlength="' + validation.max + '"';
  2855. if (filter.size) h += ' size="' + filter.size + '"';
  2856. h += '>';
  2857. }
  2858. }
  2859. /**
  2860. * Modifies the raw HTML of the rule's input
  2861. * @event changer:getRuleInput
  2862. * @memberof QueryBuilder
  2863. * @param {string} html
  2864. * @param {Rule} rule
  2865. * @param {string} name - the name that the input must have
  2866. * @returns {string}
  2867. */
  2868. return this.change('getRuleInput', h, rule, name);
  2869. };
  2870. /**
  2871. * @namespace
  2872. */
  2873. var Utils = {};
  2874. /**
  2875. * @member {object}
  2876. * @memberof QueryBuilder
  2877. * @see Utils
  2878. */
  2879. QueryBuilder.utils = Utils;
  2880. /**
  2881. * @callback Utils#OptionsIteratee
  2882. * @param {string} key
  2883. * @param {string} value
  2884. * @param {string} [optgroup]
  2885. */
  2886. /**
  2887. * Iterates over radio/checkbox/selection options, it accept four formats
  2888. *
  2889. * @example
  2890. * // array of values
  2891. * options = ['one', 'two', 'three']
  2892. * @example
  2893. * // simple key-value map
  2894. * options = {1: 'one', 2: 'two', 3: 'three'}
  2895. * @example
  2896. * // array of 1-element maps
  2897. * options = [{1: 'one'}, {2: 'two'}, {3: 'three'}]
  2898. * @example
  2899. * // array of elements
  2900. * options = [{value: 1, label: 'one', optgroup: 'group'}, {value: 2, label: 'two'}]
  2901. *
  2902. * @param {object|array} options
  2903. * @param {Utils#OptionsIteratee} tpl
  2904. */
  2905. Utils.iterateOptions = function(options, tpl) {
  2906. if (options) {
  2907. if ($.isArray(options)) {
  2908. options.forEach(function(entry) {
  2909. if ($.isPlainObject(entry)) {
  2910. // array of elements
  2911. if ('value' in entry) {
  2912. tpl(entry.value, entry.label || entry.value, entry.optgroup);
  2913. }
  2914. // array of one-element maps
  2915. else {
  2916. $.each(entry, function(key, val) {
  2917. tpl(key, val);
  2918. return false; // break after first entry
  2919. });
  2920. }
  2921. }
  2922. // array of values
  2923. else {
  2924. tpl(entry, entry);
  2925. }
  2926. });
  2927. }
  2928. // unordered map
  2929. else {
  2930. $.each(options, function(key, val) {
  2931. tpl(key, val);
  2932. });
  2933. }
  2934. }
  2935. };
  2936. /**
  2937. * Replaces {0}, {1}, ... in a string
  2938. * @param {string} str
  2939. * @param {...*} args
  2940. * @returns {string}
  2941. */
  2942. Utils.fmt = function(str, args) {
  2943. if (!Array.isArray(args)) {
  2944. args = Array.prototype.slice.call(arguments, 1);
  2945. }
  2946. return str.replace(/{([0-9]+)}/g, function(m, i) {
  2947. return args[parseInt(i)];
  2948. });
  2949. };
  2950. /**
  2951. * Throws an Error object with custom name or logs an error
  2952. * @param {boolean} [doThrow=true]
  2953. * @param {string} type
  2954. * @param {string} message
  2955. * @param {...*} args
  2956. */
  2957. Utils.error = function() {
  2958. var i = 0;
  2959. var doThrow = typeof arguments[i] === 'boolean' ? arguments[i++] : true;
  2960. var type = arguments[i++];
  2961. var message = arguments[i++];
  2962. var args = Array.isArray(arguments[i]) ? arguments[i] : Array.prototype.slice.call(arguments, i);
  2963. if (doThrow) {
  2964. var err = new Error(Utils.fmt(message, args));
  2965. err.name = type + 'Error';
  2966. err.args = args;
  2967. throw err;
  2968. }
  2969. else {
  2970. console.error(type + 'Error: ' + Utils.fmt(message, args));
  2971. }
  2972. };
  2973. /**
  2974. * Changes the type of a value to int, float or bool
  2975. * @param {*} value
  2976. * @param {string} type - 'integer', 'double', 'boolean' or anything else (passthrough)
  2977. * @returns {*}
  2978. */
  2979. Utils.changeType = function(value, type) {
  2980. if (value === '' || value === undefined) {
  2981. return undefined;
  2982. }
  2983. switch (type) {
  2984. // @formatter:off
  2985. case 'integer':
  2986. if (typeof value === 'string' && !/^-?\d+$/.test(value)) {
  2987. return value;
  2988. }
  2989. return parseInt(value);
  2990. case 'double':
  2991. if (typeof value === 'string' && !/^-?\d+\.?\d*$/.test(value)) {
  2992. return value;
  2993. }
  2994. return parseFloat(value);
  2995. case 'boolean':
  2996. if (typeof value === 'string' && !/^(0|1|true|false){1}$/i.test(value)) {
  2997. return value;
  2998. }
  2999. return value === true || value === 1 || value.toLowerCase() === 'true' || value === '1';
  3000. default: return value;
  3001. // @formatter:on
  3002. }
  3003. };
  3004. /**
  3005. * Escapes a string like PHP's mysql_real_escape_string does
  3006. * @param {string} value
  3007. * @returns {string}
  3008. */
  3009. Utils.escapeString = function(value) {
  3010. if (typeof value != 'string') {
  3011. return value;
  3012. }
  3013. return value
  3014. .replace(/[\0\n\r\b\\\'\"]/g, function(s) {
  3015. switch (s) {
  3016. // @formatter:off
  3017. case '\0': return '\\0';
  3018. case '\n': return '\\n';
  3019. case '\r': return '\\r';
  3020. case '\b': return '\\b';
  3021. default: return '\\' + s;
  3022. // @formatter:off
  3023. }
  3024. })
  3025. // uglify compliant
  3026. .replace(/\t/g, '\\t')
  3027. .replace(/\x1a/g, '\\Z');
  3028. };
  3029. /**
  3030. * Escapes a string for use in regex
  3031. * @param {string} str
  3032. * @returns {string}
  3033. */
  3034. Utils.escapeRegExp = function(str) {
  3035. return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
  3036. };
  3037. /**
  3038. * Escapes a string for use in HTML element id
  3039. * @param {string} str
  3040. * @returns {string}
  3041. */
  3042. Utils.escapeElementId = function(str) {
  3043. // Regex based on that suggested by:
  3044. // https://learn.jquery.com/using-jquery-core/faq/how-do-i-select-an-element-by-an-id-that-has-characters-used-in-css-notation/
  3045. // - escapes : . [ ] ,
  3046. // - avoids escaping already escaped values
  3047. return (str) ? str.replace(/(\\)?([:.\[\],])/g,
  3048. function( $0, $1, $2 ) { return $1 ? $0 : '\\' + $2; }) : str;
  3049. };
  3050. /**
  3051. * Sorts objects by grouping them by `key`, preserving initial order when possible
  3052. * @param {object[]} items
  3053. * @param {string} key
  3054. * @returns {object[]}
  3055. */
  3056. Utils.groupSort = function(items, key) {
  3057. var optgroups = [];
  3058. var newItems = [];
  3059. items.forEach(function(item) {
  3060. var idx;
  3061. if (item[key]) {
  3062. idx = optgroups.lastIndexOf(item[key]);
  3063. if (idx == -1) {
  3064. idx = optgroups.length;
  3065. }
  3066. else {
  3067. idx++;
  3068. }
  3069. }
  3070. else {
  3071. idx = optgroups.length;
  3072. }
  3073. optgroups.splice(idx, 0, item[key]);
  3074. newItems.splice(idx, 0, item);
  3075. });
  3076. return newItems;
  3077. };
  3078. /**
  3079. * Defines properties on an Node prototype with getter and setter.<br>
  3080. * Update events are emitted in the setter through root Model (if any).<br>
  3081. * The object must have a `__` object, non enumerable property to store values.
  3082. * @param {function} obj
  3083. * @param {string[]} fields
  3084. */
  3085. Utils.defineModelProperties = function(obj, fields) {
  3086. fields.forEach(function(field) {
  3087. Object.defineProperty(obj.prototype, field, {
  3088. enumerable: true,
  3089. get: function() {
  3090. return this.__[field];
  3091. },
  3092. set: function(value) {
  3093. var previousValue = (this.__[field] !== null && typeof this.__[field] == 'object') ?
  3094. $.extend({}, this.__[field]) :
  3095. this.__[field];
  3096. this.__[field] = value;
  3097. if (this.model !== null) {
  3098. /**
  3099. * After a value of the model changed
  3100. * @event model:update
  3101. * @memberof Model
  3102. * @param {Node} node
  3103. * @param {string} field
  3104. * @param {*} value
  3105. * @param {*} previousValue
  3106. */
  3107. this.model.trigger('update', this, field, value, previousValue);
  3108. }
  3109. }
  3110. });
  3111. });
  3112. };
  3113. /**
  3114. * Main object storing data model and emitting model events
  3115. * @constructor
  3116. */
  3117. function Model() {
  3118. /**
  3119. * @member {Group}
  3120. * @readonly
  3121. */
  3122. this.root = null;
  3123. /**
  3124. * Base for event emitting
  3125. * @member {jQuery}
  3126. * @readonly
  3127. * @private
  3128. */
  3129. this.$ = $(this);
  3130. }
  3131. $.extend(Model.prototype, /** @lends Model.prototype */ {
  3132. /**
  3133. * Triggers an event on the model
  3134. * @param {string} type
  3135. * @returns {$.Event}
  3136. */
  3137. trigger: function(type) {
  3138. var event = new $.Event(type);
  3139. this.$.triggerHandler(event, Array.prototype.slice.call(arguments, 1));
  3140. return event;
  3141. },
  3142. /**
  3143. * Attaches an event listener on the model
  3144. * @param {string} type
  3145. * @param {function} cb
  3146. * @returns {Model}
  3147. */
  3148. on: function() {
  3149. this.$.on.apply(this.$, Array.prototype.slice.call(arguments));
  3150. return this;
  3151. },
  3152. /**
  3153. * Removes an event listener from the model
  3154. * @param {string} type
  3155. * @param {function} [cb]
  3156. * @returns {Model}
  3157. */
  3158. off: function() {
  3159. this.$.off.apply(this.$, Array.prototype.slice.call(arguments));
  3160. return this;
  3161. },
  3162. /**
  3163. * Attaches an event listener called once on the model
  3164. * @param {string} type
  3165. * @param {function} cb
  3166. * @returns {Model}
  3167. */
  3168. once: function() {
  3169. this.$.one.apply(this.$, Array.prototype.slice.call(arguments));
  3170. return this;
  3171. }
  3172. });
  3173. /**
  3174. * Root abstract object
  3175. * @constructor
  3176. * @param {Node} [parent]
  3177. * @param {jQuery} $el
  3178. */
  3179. var Node = function(parent, $el) {
  3180. if (!(this instanceof Node)) {
  3181. return new Node(parent, $el);
  3182. }
  3183. Object.defineProperty(this, '__', { value: {} });
  3184. $el.data('queryBuilderModel', this);
  3185. /**
  3186. * @name level
  3187. * @member {int}
  3188. * @memberof Node
  3189. * @instance
  3190. * @readonly
  3191. */
  3192. this.__.level = 1;
  3193. /**
  3194. * @name error
  3195. * @member {string}
  3196. * @memberof Node
  3197. * @instance
  3198. */
  3199. this.__.error = null;
  3200. /**
  3201. * @name flags
  3202. * @member {object}
  3203. * @memberof Node
  3204. * @instance
  3205. * @readonly
  3206. */
  3207. this.__.flags = {};
  3208. /**
  3209. * @name data
  3210. * @member {object}
  3211. * @memberof Node
  3212. * @instance
  3213. */
  3214. this.__.data = undefined;
  3215. /**
  3216. * @member {jQuery}
  3217. * @readonly
  3218. */
  3219. this.$el = $el;
  3220. /**
  3221. * @member {string}
  3222. * @readonly
  3223. */
  3224. this.id = $el[0].id;
  3225. /**
  3226. * @member {Model}
  3227. * @readonly
  3228. */
  3229. this.model = null;
  3230. /**
  3231. * @member {Group}
  3232. * @readonly
  3233. */
  3234. this.parent = parent;
  3235. };
  3236. Utils.defineModelProperties(Node, ['level', 'error', 'data', 'flags']);
  3237. Object.defineProperty(Node.prototype, 'parent', {
  3238. enumerable: true,
  3239. get: function() {
  3240. return this.__.parent;
  3241. },
  3242. set: function(value) {
  3243. this.__.parent = value;
  3244. this.level = value === null ? 1 : value.level + 1;
  3245. this.model = value === null ? null : value.model;
  3246. }
  3247. });
  3248. /**
  3249. * Checks if this Node is the root
  3250. * @returns {boolean}
  3251. */
  3252. Node.prototype.isRoot = function() {
  3253. return (this.level === 1);
  3254. };
  3255. /**
  3256. * Returns the node position inside its parent
  3257. * @returns {int}
  3258. */
  3259. Node.prototype.getPos = function() {
  3260. if (this.isRoot()) {
  3261. return -1;
  3262. }
  3263. else {
  3264. return this.parent.getNodePos(this);
  3265. }
  3266. };
  3267. /**
  3268. * Deletes self
  3269. * @fires Model.model:drop
  3270. */
  3271. Node.prototype.drop = function() {
  3272. var model = this.model;
  3273. if (!!this.parent) {
  3274. this.parent.removeNode(this);
  3275. }
  3276. this.$el.removeData('queryBuilderModel');
  3277. if (model !== null) {
  3278. /**
  3279. * After a node of the model has been removed
  3280. * @event model:drop
  3281. * @memberof Model
  3282. * @param {Node} node
  3283. */
  3284. model.trigger('drop', this);
  3285. }
  3286. };
  3287. /**
  3288. * Moves itself after another Node
  3289. * @param {Node} target
  3290. * @fires Model.model:move
  3291. */
  3292. Node.prototype.moveAfter = function(target) {
  3293. if (!this.isRoot()) {
  3294. this.move(target.parent, target.getPos() + 1);
  3295. }
  3296. };
  3297. /**
  3298. * Moves itself at the beginning of parent or another Group
  3299. * @param {Group} [target]
  3300. * @fires Model.model:move
  3301. */
  3302. Node.prototype.moveAtBegin = function(target) {
  3303. if (!this.isRoot()) {
  3304. if (target === undefined) {
  3305. target = this.parent;
  3306. }
  3307. this.move(target, 0);
  3308. }
  3309. };
  3310. /**
  3311. * Moves itself at the end of parent or another Group
  3312. * @param {Group} [target]
  3313. * @fires Model.model:move
  3314. */
  3315. Node.prototype.moveAtEnd = function(target) {
  3316. if (!this.isRoot()) {
  3317. if (target === undefined) {
  3318. target = this.parent;
  3319. }
  3320. this.move(target, target.length() === 0 ? 0 : target.length() - 1);
  3321. }
  3322. };
  3323. /**
  3324. * Moves itself at specific position of Group
  3325. * @param {Group} target
  3326. * @param {int} index
  3327. * @fires Model.model:move
  3328. */
  3329. Node.prototype.move = function(target, index) {
  3330. if (!this.isRoot()) {
  3331. if (typeof target === 'number') {
  3332. index = target;
  3333. target = this.parent;
  3334. }
  3335. this.parent.removeNode(this);
  3336. target.insertNode(this, index, false);
  3337. if (this.model !== null) {
  3338. /**
  3339. * After a node of the model has been moved
  3340. * @event model:move
  3341. * @memberof Model
  3342. * @param {Node} node
  3343. * @param {Node} target
  3344. * @param {int} index
  3345. */
  3346. this.model.trigger('move', this, target, index);
  3347. }
  3348. }
  3349. };
  3350. /**
  3351. * Group object
  3352. * @constructor
  3353. * @extends Node
  3354. * @param {Group} [parent]
  3355. * @param {jQuery} $el
  3356. */
  3357. var Group = function(parent, $el) {
  3358. if (!(this instanceof Group)) {
  3359. return new Group(parent, $el);
  3360. }
  3361. Node.call(this, parent, $el);
  3362. /**
  3363. * @member {object[]}
  3364. * @readonly
  3365. */
  3366. this.rules = [];
  3367. /**
  3368. * @name condition
  3369. * @member {string}
  3370. * @memberof Group
  3371. * @instance
  3372. */
  3373. this.__.condition = null;
  3374. };
  3375. Group.prototype = Object.create(Node.prototype);
  3376. Group.prototype.constructor = Group;
  3377. Utils.defineModelProperties(Group, ['condition']);
  3378. /**
  3379. * Removes group's content
  3380. */
  3381. Group.prototype.empty = function() {
  3382. this.each('reverse', function(rule) {
  3383. rule.drop();
  3384. }, function(group) {
  3385. group.drop();
  3386. });
  3387. };
  3388. /**
  3389. * Deletes self
  3390. */
  3391. Group.prototype.drop = function() {
  3392. this.empty();
  3393. Node.prototype.drop.call(this);
  3394. };
  3395. /**
  3396. * Returns the number of children
  3397. * @returns {int}
  3398. */
  3399. Group.prototype.length = function() {
  3400. return this.rules.length;
  3401. };
  3402. /**
  3403. * Adds a Node at specified index
  3404. * @param {Node} node
  3405. * @param {int} [index=end]
  3406. * @param {boolean} [trigger=false] - fire 'add' event
  3407. * @returns {Node} the inserted node
  3408. * @fires Model.model:add
  3409. */
  3410. Group.prototype.insertNode = function(node, index, trigger) {
  3411. if (index === undefined) {
  3412. index = this.length();
  3413. }
  3414. this.rules.splice(index, 0, node);
  3415. node.parent = this;
  3416. if (trigger && this.model !== null) {
  3417. /**
  3418. * After a node of the model has been added
  3419. * @event model:add
  3420. * @memberof Model
  3421. * @param {Node} parent
  3422. * @param {Node} node
  3423. * @param {int} index
  3424. */
  3425. this.model.trigger('add', this, node, index);
  3426. }
  3427. return node;
  3428. };
  3429. /**
  3430. * Adds a new Group at specified index
  3431. * @param {jQuery} $el
  3432. * @param {int} [index=end]
  3433. * @returns {Group}
  3434. * @fires Model.model:add
  3435. */
  3436. Group.prototype.addGroup = function($el, index) {
  3437. return this.insertNode(new Group(this, $el), index, true);
  3438. };
  3439. /**
  3440. * Adds a new Rule at specified index
  3441. * @param {jQuery} $el
  3442. * @param {int} [index=end]
  3443. * @returns {Rule}
  3444. * @fires Model.model:add
  3445. */
  3446. Group.prototype.addRule = function($el, index) {
  3447. return this.insertNode(new Rule(this, $el), index, true);
  3448. };
  3449. /**
  3450. * Deletes a specific Node
  3451. * @param {Node} node
  3452. */
  3453. Group.prototype.removeNode = function(node) {
  3454. var index = this.getNodePos(node);
  3455. if (index !== -1) {
  3456. node.parent = null;
  3457. this.rules.splice(index, 1);
  3458. }
  3459. };
  3460. /**
  3461. * Returns the position of a child Node
  3462. * @param {Node} node
  3463. * @returns {int}
  3464. */
  3465. Group.prototype.getNodePos = function(node) {
  3466. return this.rules.indexOf(node);
  3467. };
  3468. /**
  3469. * @callback Model#GroupIteratee
  3470. * @param {Node} node
  3471. * @returns {boolean} stop the iteration
  3472. */
  3473. /**
  3474. * Iterate over all Nodes
  3475. * @param {boolean} [reverse=false] - iterate in reverse order, required if you delete nodes
  3476. * @param {Model#GroupIteratee} cbRule - callback for Rules (can be `null` but not omitted)
  3477. * @param {Model#GroupIteratee} [cbGroup] - callback for Groups
  3478. * @param {object} [context] - context for callbacks
  3479. * @returns {boolean} if the iteration has been stopped by a callback
  3480. */
  3481. Group.prototype.each = function(reverse, cbRule, cbGroup, context) {
  3482. if (typeof reverse !== 'boolean' && typeof reverse !== 'string') {
  3483. context = cbGroup;
  3484. cbGroup = cbRule;
  3485. cbRule = reverse;
  3486. reverse = false;
  3487. }
  3488. context = context === undefined ? null : context;
  3489. var i = reverse ? this.rules.length - 1 : 0;
  3490. var l = reverse ? 0 : this.rules.length - 1;
  3491. var c = reverse ? -1 : 1;
  3492. var next = function() {
  3493. return reverse ? i >= l : i <= l;
  3494. };
  3495. var stop = false;
  3496. for (; next(); i += c) {
  3497. if (this.rules[i] instanceof Group) {
  3498. if (!!cbGroup) {
  3499. stop = cbGroup.call(context, this.rules[i]) === false;
  3500. }
  3501. }
  3502. else if (!!cbRule) {
  3503. stop = cbRule.call(context, this.rules[i]) === false;
  3504. }
  3505. if (stop) {
  3506. break;
  3507. }
  3508. }
  3509. return !stop;
  3510. };
  3511. /**
  3512. * Checks if the group contains a particular Node
  3513. * @param {Node} node
  3514. * @param {boolean} [recursive=false]
  3515. * @returns {boolean}
  3516. */
  3517. Group.prototype.contains = function(node, recursive) {
  3518. if (this.getNodePos(node) !== -1) {
  3519. return true;
  3520. }
  3521. else if (!recursive) {
  3522. return false;
  3523. }
  3524. else {
  3525. // the loop will return with false as soon as the Node is found
  3526. return !this.each(function() {
  3527. return true;
  3528. }, function(group) {
  3529. return !group.contains(node, true);
  3530. });
  3531. }
  3532. };
  3533. /**
  3534. * Rule object
  3535. * @constructor
  3536. * @extends Node
  3537. * @param {Group} parent
  3538. * @param {jQuery} $el
  3539. */
  3540. var Rule = function(parent, $el) {
  3541. if (!(this instanceof Rule)) {
  3542. return new Rule(parent, $el);
  3543. }
  3544. Node.call(this, parent, $el);
  3545. this._updating_value = false;
  3546. this._updating_input = false;
  3547. /**
  3548. * @name filter
  3549. * @member {QueryBuilder.Filter}
  3550. * @memberof Rule
  3551. * @instance
  3552. */
  3553. this.__.filter = null;
  3554. /**
  3555. * @name operator
  3556. * @member {QueryBuilder.Operator}
  3557. * @memberof Rule
  3558. * @instance
  3559. */
  3560. this.__.operator = null;
  3561. /**
  3562. * @name value
  3563. * @member {*}
  3564. * @memberof Rule
  3565. * @instance
  3566. */
  3567. this.__.value = undefined;
  3568. };
  3569. Rule.prototype = Object.create(Node.prototype);
  3570. Rule.prototype.constructor = Rule;
  3571. Utils.defineModelProperties(Rule, ['filter', 'operator', 'value']);
  3572. /**
  3573. * Checks if this Node is the root
  3574. * @returns {boolean} always false
  3575. */
  3576. Rule.prototype.isRoot = function() {
  3577. return false;
  3578. };
  3579. /**
  3580. * @member {function}
  3581. * @memberof QueryBuilder
  3582. * @see Group
  3583. */
  3584. QueryBuilder.Group = Group;
  3585. /**
  3586. * @member {function}
  3587. * @memberof QueryBuilder
  3588. * @see Rule
  3589. */
  3590. QueryBuilder.Rule = Rule;
  3591. /**
  3592. * The {@link http://learn.jquery.com/plugins/|jQuery Plugins} namespace
  3593. * @external "jQuery.fn"
  3594. */
  3595. /**
  3596. * Instanciates or accesses the {@link QueryBuilder} on an element
  3597. * @function
  3598. * @memberof external:"jQuery.fn"
  3599. * @param {*} option - initial configuration or method name
  3600. * @param {...*} args - method arguments
  3601. *
  3602. * @example
  3603. * $('#builder').queryBuilder({ /** configuration object *\/ });
  3604. * @example
  3605. * $('#builder').queryBuilder('methodName', methodParam1, methodParam2);
  3606. */
  3607. $.fn.queryBuilder = function(option) {
  3608. if (this.length === 0) {
  3609. Utils.error('Config', 'No target defined');
  3610. }
  3611. if (this.length > 1) {
  3612. Utils.error('Config', 'Unable to initialize on multiple target');
  3613. }
  3614. var data = this.data('queryBuilder');
  3615. var options = (typeof option == 'object' && option) || {};
  3616. if (!data && option == 'destroy') {
  3617. return this;
  3618. }
  3619. if (!data) {
  3620. var builder = new QueryBuilder(this, options);
  3621. this.data('queryBuilder', builder);
  3622. builder.init(options.rules);
  3623. }
  3624. if (typeof option == 'string') {
  3625. return data[option].apply(data, Array.prototype.slice.call(arguments, 1));
  3626. }
  3627. return this;
  3628. };
  3629. /**
  3630. * @function
  3631. * @memberof external:"jQuery.fn"
  3632. * @see QueryBuilder
  3633. */
  3634. $.fn.queryBuilder.constructor = QueryBuilder;
  3635. /**
  3636. * @function
  3637. * @memberof external:"jQuery.fn"
  3638. * @see QueryBuilder.defaults
  3639. */
  3640. $.fn.queryBuilder.defaults = QueryBuilder.defaults;
  3641. /**
  3642. * @function
  3643. * @memberof external:"jQuery.fn"
  3644. * @see QueryBuilder.defaults
  3645. */
  3646. $.fn.queryBuilder.extend = QueryBuilder.extend;
  3647. /**
  3648. * @function
  3649. * @memberof external:"jQuery.fn"
  3650. * @see QueryBuilder.define
  3651. */
  3652. $.fn.queryBuilder.define = QueryBuilder.define;
  3653. /**
  3654. * @function
  3655. * @memberof external:"jQuery.fn"
  3656. * @see QueryBuilder.regional
  3657. */
  3658. $.fn.queryBuilder.regional = QueryBuilder.regional;
  3659. /**
  3660. * @class BtCheckbox
  3661. * @memberof module:plugins
  3662. * @description Applies Awesome Bootstrap Checkbox for checkbox and radio inputs.
  3663. * @param {object} [options]
  3664. * @param {string} [options.font='glyphicons']
  3665. * @param {string} [options.color='default']
  3666. */
  3667. QueryBuilder.define('bt-checkbox', function(options) {
  3668. if (options.font == 'glyphicons') {
  3669. this.$el.addClass('bt-checkbox-glyphicons');
  3670. }
  3671. this.on('getRuleInput.filter', function(h, rule, name) {
  3672. var filter = rule.filter;
  3673. if ((filter.input === 'radio' || filter.input === 'checkbox') && !filter.plugin) {
  3674. h.value = '';
  3675. if (!filter.colors) {
  3676. filter.colors = {};
  3677. }
  3678. if (filter.color) {
  3679. filter.colors._def_ = filter.color;
  3680. }
  3681. var style = filter.vertical ? ' style="display:block"' : '';
  3682. var i = 0;
  3683. Utils.iterateOptions(filter.values, function(key, val) {
  3684. var color = filter.colors[key] || filter.colors._def_ || options.color;
  3685. var id = name + '_' + (i++);
  3686. h.value+= '\
  3687. <div' + style + ' class="' + filter.input + ' ' + filter.input + '-' + color + '"> \
  3688. <input type="' + filter.input + '" name="' + name + '" id="' + id + '" value="' + key + '"> \
  3689. <label for="' + id + '">' + val + '</label> \
  3690. </div>';
  3691. });
  3692. }
  3693. });
  3694. }, {
  3695. font: 'glyphicons',
  3696. color: 'default'
  3697. });
  3698. /**
  3699. * @class BtSelectpicker
  3700. * @memberof module:plugins
  3701. * @descriptioon Applies Bootstrap Select on filters and operators combo-boxes.
  3702. * @param {object} [options]
  3703. * @param {string} [options.container='body']
  3704. * @param {string} [options.style='btn-inverse btn-xs']
  3705. * @param {int|string} [options.width='auto']
  3706. * @param {boolean} [options.showIcon=false]
  3707. * @throws MissingLibraryError
  3708. */
  3709. QueryBuilder.define('bt-selectpicker', function(options) {
  3710. if (!$.fn.selectpicker || !$.fn.selectpicker.Constructor) {
  3711. Utils.error('MissingLibrary', 'Bootstrap Select is required to use "bt-selectpicker" plugin. Get it here: http://silviomoreto.github.io/bootstrap-select');
  3712. }
  3713. var Selectors = QueryBuilder.selectors;
  3714. // init selectpicker
  3715. this.on('afterCreateRuleFilters', function(e, rule) {
  3716. rule.$el.find(Selectors.rule_filter).removeClass('form-control').selectpicker(options);
  3717. });
  3718. this.on('afterCreateRuleOperators', function(e, rule) {
  3719. rule.$el.find(Selectors.rule_operator).removeClass('form-control').selectpicker(options);
  3720. });
  3721. // update selectpicker on change
  3722. this.on('afterUpdateRuleFilter', function(e, rule) {
  3723. rule.$el.find(Selectors.rule_filter).selectpicker('render');
  3724. });
  3725. this.on('afterUpdateRuleOperator', function(e, rule) {
  3726. rule.$el.find(Selectors.rule_operator).selectpicker('render');
  3727. });
  3728. this.on('beforeDeleteRule', function(e, rule) {
  3729. rule.$el.find(Selectors.rule_filter).selectpicker('destroy');
  3730. rule.$el.find(Selectors.rule_operator).selectpicker('destroy');
  3731. });
  3732. }, {
  3733. container: 'body',
  3734. style: 'btn-inverse btn-xs',
  3735. width: 'auto',
  3736. showIcon: false
  3737. });
  3738. /**
  3739. * @class BtTooltipErrors
  3740. * @memberof module:plugins
  3741. * @description Applies Bootstrap Tooltips on validation error messages.
  3742. * @param {object} [options]
  3743. * @param {string} [options.placement='right']
  3744. * @throws MissingLibraryError
  3745. */
  3746. QueryBuilder.define('bt-tooltip-errors', function(options) {
  3747. if (!$.fn.tooltip || !$.fn.tooltip.Constructor || !$.fn.tooltip.Constructor.prototype.fixTitle) {
  3748. Utils.error('MissingLibrary', 'Bootstrap Tooltip is required to use "bt-tooltip-errors" plugin. Get it here: http://getbootstrap.com');
  3749. }
  3750. var self = this;
  3751. // add BT Tooltip data
  3752. this.on('getRuleTemplate.filter getGroupTemplate.filter', function(h) {
  3753. var $h = $(h.value);
  3754. $h.find(QueryBuilder.selectors.error_container).attr('data-toggle', 'tooltip');
  3755. h.value = $h.prop('outerHTML');
  3756. });
  3757. // init/refresh tooltip when title changes
  3758. this.model.on('update', function(e, node, field) {
  3759. if (field == 'error' && self.settings.display_errors) {
  3760. node.$el.find(QueryBuilder.selectors.error_container).eq(0)
  3761. .tooltip(options)
  3762. .tooltip('hide')
  3763. .tooltip('fixTitle');
  3764. }
  3765. });
  3766. }, {
  3767. placement: 'right'
  3768. });
  3769. /**
  3770. * @class ChangeFilters
  3771. * @memberof module:plugins
  3772. * @description Allows to change available filters after plugin initialization.
  3773. */
  3774. QueryBuilder.extend(/** @lends module:plugins.ChangeFilters.prototype */ {
  3775. /**
  3776. * Change the filters of the builder
  3777. * @param {boolean} [deleteOrphans=false] - delete rules using old filters
  3778. * @param {QueryBuilder[]} filters
  3779. * @fires module:plugins.ChangeFilters.changer:setFilters
  3780. * @fires module:plugins.ChangeFilters.afterSetFilters
  3781. * @throws ChangeFilterError
  3782. */
  3783. setFilters: function(deleteOrphans, filters) {
  3784. var self = this;
  3785. if (filters === undefined) {
  3786. filters = deleteOrphans;
  3787. deleteOrphans = false;
  3788. }
  3789. filters = this.checkFilters(filters);
  3790. /**
  3791. * Modifies the filters before {@link module:plugins.ChangeFilters.setFilters} method
  3792. * @event changer:setFilters
  3793. * @memberof module:plugins.ChangeFilters
  3794. * @param {QueryBuilder.Filter[]} filters
  3795. * @returns {QueryBuilder.Filter[]}
  3796. */
  3797. filters = this.change('setFilters', filters);
  3798. var filtersIds = filters.map(function(filter) {
  3799. return filter.id;
  3800. });
  3801. // check for orphans
  3802. if (!deleteOrphans) {
  3803. (function checkOrphans(node) {
  3804. node.each(
  3805. function(rule) {
  3806. if (rule.filter && filtersIds.indexOf(rule.filter.id) === -1) {
  3807. Utils.error('ChangeFilter', 'A rule is using filter "{0}"', rule.filter.id);
  3808. }
  3809. },
  3810. checkOrphans
  3811. );
  3812. }(this.model.root));
  3813. }
  3814. // replace filters
  3815. this.filters = filters;
  3816. // apply on existing DOM
  3817. (function updateBuilder(node) {
  3818. node.each(true,
  3819. function(rule) {
  3820. if (rule.filter && filtersIds.indexOf(rule.filter.id) === -1) {
  3821. rule.drop();
  3822. self.trigger('rulesChanged');
  3823. }
  3824. else {
  3825. self.createRuleFilters(rule);
  3826. rule.$el.find(QueryBuilder.selectors.rule_filter).val(rule.filter ? rule.filter.id : '-1');
  3827. self.trigger('afterUpdateRuleFilter', rule);
  3828. }
  3829. },
  3830. updateBuilder
  3831. );
  3832. }(this.model.root));
  3833. // update plugins
  3834. if (this.settings.plugins) {
  3835. if (this.settings.plugins['unique-filter']) {
  3836. this.updateDisabledFilters();
  3837. }
  3838. if (this.settings.plugins['bt-selectpicker']) {
  3839. this.$el.find(QueryBuilder.selectors.rule_filter).selectpicker('render');
  3840. }
  3841. }
  3842. // reset the default_filter if does not exist anymore
  3843. if (this.settings.default_filter) {
  3844. try {
  3845. this.getFilterById(this.settings.default_filter);
  3846. }
  3847. catch (e) {
  3848. this.settings.default_filter = null;
  3849. }
  3850. }
  3851. /**
  3852. * After {@link module:plugins.ChangeFilters.setFilters} method
  3853. * @event afterSetFilters
  3854. * @memberof module:plugins.ChangeFilters
  3855. * @param {QueryBuilder.Filter[]} filters
  3856. */
  3857. this.trigger('afterSetFilters', filters);
  3858. },
  3859. /**
  3860. * Adds a new filter to the builder
  3861. * @param {QueryBuilder.Filter|Filter[]} newFilters
  3862. * @param {int|string} [position=#end] - index or '#start' or '#end'
  3863. * @fires module:plugins.ChangeFilters.changer:setFilters
  3864. * @fires module:plugins.ChangeFilters.afterSetFilters
  3865. * @throws ChangeFilterError
  3866. */
  3867. addFilter: function(newFilters, position) {
  3868. if (position === undefined || position == '#end') {
  3869. position = this.filters.length;
  3870. }
  3871. else if (position == '#start') {
  3872. position = 0;
  3873. }
  3874. if (!$.isArray(newFilters)) {
  3875. newFilters = [newFilters];
  3876. }
  3877. var filters = $.extend(true, [], this.filters);
  3878. // numeric position
  3879. if (parseInt(position) == position) {
  3880. Array.prototype.splice.apply(filters, [position, 0].concat(newFilters));
  3881. }
  3882. else {
  3883. // after filter by its id
  3884. if (this.filters.some(function(filter, index) {
  3885. if (filter.id == position) {
  3886. position = index + 1;
  3887. return true;
  3888. }
  3889. })
  3890. ) {
  3891. Array.prototype.splice.apply(filters, [position, 0].concat(newFilters));
  3892. }
  3893. // defaults to end of list
  3894. else {
  3895. Array.prototype.push.apply(filters, newFilters);
  3896. }
  3897. }
  3898. this.setFilters(filters);
  3899. },
  3900. /**
  3901. * Removes a filter from the builder
  3902. * @param {string|string[]} filterIds
  3903. * @param {boolean} [deleteOrphans=false] delete rules using old filters
  3904. * @fires module:plugins.ChangeFilters.changer:setFilters
  3905. * @fires module:plugins.ChangeFilters.afterSetFilters
  3906. * @throws ChangeFilterError
  3907. */
  3908. removeFilter: function(filterIds, deleteOrphans) {
  3909. var filters = $.extend(true, [], this.filters);
  3910. if (typeof filterIds === 'string') {
  3911. filterIds = [filterIds];
  3912. }
  3913. filters = filters.filter(function(filter) {
  3914. return filterIds.indexOf(filter.id) === -1;
  3915. });
  3916. this.setFilters(deleteOrphans, filters);
  3917. }
  3918. });
  3919. /**
  3920. * @class ChosenSelectpicker
  3921. * @memberof module:plugins
  3922. * @descriptioon Applies chosen-js Select on filters and operators combo-boxes.
  3923. * @param {object} [options] Supports all the options for chosen
  3924. * @throws MissingLibraryError
  3925. */
  3926. QueryBuilder.define('chosen-selectpicker', function(options) {
  3927. if (!$.fn.chosen) {
  3928. Utils.error('MissingLibrary', 'chosen is required to use "chosen-selectpicker" plugin. Get it here: https://github.com/harvesthq/chosen');
  3929. }
  3930. if (this.settings.plugins['bt-selectpicker']) {
  3931. Utils.error('Conflict', 'bt-selectpicker is already selected as the dropdown plugin. Please remove chosen-selectpicker from the plugin list');
  3932. }
  3933. var Selectors = QueryBuilder.selectors;
  3934. // init selectpicker
  3935. this.on('afterCreateRuleFilters', function(e, rule) {
  3936. rule.$el.find(Selectors.rule_filter).removeClass('form-control').chosen(options);
  3937. });
  3938. this.on('afterCreateRuleOperators', function(e, rule) {
  3939. rule.$el.find(Selectors.rule_operator).removeClass('form-control').chosen(options);
  3940. });
  3941. // update selectpicker on change
  3942. this.on('afterUpdateRuleFilter', function(e, rule) {
  3943. rule.$el.find(Selectors.rule_filter).trigger('chosen:updated');
  3944. });
  3945. this.on('afterUpdateRuleOperator', function(e, rule) {
  3946. rule.$el.find(Selectors.rule_operator).trigger('chosen:updated');
  3947. });
  3948. this.on('beforeDeleteRule', function(e, rule) {
  3949. rule.$el.find(Selectors.rule_filter).chosen('destroy');
  3950. rule.$el.find(Selectors.rule_operator).chosen('destroy');
  3951. });
  3952. });
  3953. /**
  3954. * @class FilterDescription
  3955. * @memberof module:plugins
  3956. * @description Provides three ways to display a description about a filter: inline, Bootsrap Popover or Bootbox.
  3957. * @param {object} [options]
  3958. * @param {string} [options.icon='glyphicon glyphicon-info-sign']
  3959. * @param {string} [options.mode='popover'] - inline, popover or bootbox
  3960. * @throws ConfigError
  3961. */
  3962. QueryBuilder.define('filter-description', function(options) {
  3963. // INLINE
  3964. if (options.mode === 'inline') {
  3965. this.on('afterUpdateRuleFilter afterUpdateRuleOperator', function(e, rule) {
  3966. var $p = rule.$el.find('p.filter-description');
  3967. var description = e.builder.getFilterDescription(rule.filter, rule);
  3968. if (!description) {
  3969. $p.hide();
  3970. }
  3971. else {
  3972. if ($p.length === 0) {
  3973. $p = $('<p class="filter-description"></p>');
  3974. $p.appendTo(rule.$el);
  3975. }
  3976. else {
  3977. $p.css('display', '');
  3978. }
  3979. $p.html('<i class="' + options.icon + '"></i> ' + description);
  3980. }
  3981. });
  3982. }
  3983. // POPOVER
  3984. else if (options.mode === 'popover') {
  3985. if (!$.fn.popover || !$.fn.popover.Constructor || !$.fn.popover.Constructor.prototype.fixTitle) {
  3986. Utils.error('MissingLibrary', 'Bootstrap Popover is required to use "filter-description" plugin. Get it here: http://getbootstrap.com');
  3987. }
  3988. this.on('afterUpdateRuleFilter afterUpdateRuleOperator', function(e, rule) {
  3989. var $b = rule.$el.find('button.filter-description');
  3990. var description = e.builder.getFilterDescription(rule.filter, rule);
  3991. if (!description) {
  3992. $b.hide();
  3993. if ($b.data('bs.popover')) {
  3994. $b.popover('hide');
  3995. }
  3996. }
  3997. else {
  3998. if ($b.length === 0) {
  3999. $b = $('<button type="button" class="btn btn-xs btn-info filter-description" data-toggle="popover"><i class="' + options.icon + '"></i></button>');
  4000. $b.prependTo(rule.$el.find(QueryBuilder.selectors.rule_actions));
  4001. $b.popover({
  4002. placement: 'left',
  4003. container: 'body',
  4004. html: true
  4005. });
  4006. $b.on('mouseout', function() {
  4007. $b.popover('hide');
  4008. });
  4009. }
  4010. else {
  4011. $b.css('display', '');
  4012. }
  4013. $b.data('bs.popover').options.content = description;
  4014. if ($b.attr('aria-describedby')) {
  4015. $b.popover('show');
  4016. }
  4017. }
  4018. });
  4019. }
  4020. // BOOTBOX
  4021. else if (options.mode === 'bootbox') {
  4022. if (!('bootbox' in window)) {
  4023. Utils.error('MissingLibrary', 'Bootbox is required to use "filter-description" plugin. Get it here: http://bootboxjs.com');
  4024. }
  4025. this.on('afterUpdateRuleFilter afterUpdateRuleOperator', function(e, rule) {
  4026. var $b = rule.$el.find('button.filter-description');
  4027. var description = e.builder.getFilterDescription(rule.filter, rule);
  4028. if (!description) {
  4029. $b.hide();
  4030. }
  4031. else {
  4032. if ($b.length === 0) {
  4033. $b = $('<button type="button" class="btn btn-xs btn-info filter-description" data-toggle="bootbox"><i class="' + options.icon + '"></i></button>');
  4034. $b.prependTo(rule.$el.find(QueryBuilder.selectors.rule_actions));
  4035. $b.on('click', function() {
  4036. bootbox.alert($b.data('description'));
  4037. });
  4038. }
  4039. else {
  4040. $b.css('display', '');
  4041. }
  4042. $b.data('description', description);
  4043. }
  4044. });
  4045. }
  4046. }, {
  4047. icon: 'glyphicon glyphicon-info-sign',
  4048. mode: 'popover'
  4049. });
  4050. QueryBuilder.extend(/** @lends module:plugins.FilterDescription.prototype */ {
  4051. /**
  4052. * Returns the description of a filter for a particular rule (if present)
  4053. * @param {object} filter
  4054. * @param {Rule} [rule]
  4055. * @returns {string}
  4056. * @private
  4057. */
  4058. getFilterDescription: function(filter, rule) {
  4059. if (!filter) {
  4060. return undefined;
  4061. }
  4062. else if (typeof filter.description == 'function') {
  4063. return filter.description.call(this, rule);
  4064. }
  4065. else {
  4066. return filter.description;
  4067. }
  4068. }
  4069. });
  4070. /**
  4071. * @class Invert
  4072. * @memberof module:plugins
  4073. * @description Allows to invert a rule operator, a group condition or the entire builder.
  4074. * @param {object} [options]
  4075. * @param {string} [options.icon='glyphicon glyphicon-random']
  4076. * @param {boolean} [options.recursive=true]
  4077. * @param {boolean} [options.invert_rules=true]
  4078. * @param {boolean} [options.display_rules_button=false]
  4079. * @param {boolean} [options.silent_fail=false]
  4080. */
  4081. QueryBuilder.define('invert', function(options) {
  4082. var self = this;
  4083. var Selectors = QueryBuilder.selectors;
  4084. // Bind events
  4085. this.on('afterInit', function() {
  4086. self.$el.on('click.queryBuilder', '[data-invert=group]', function() {
  4087. var $group = $(this).closest(Selectors.group_container);
  4088. self.invert(self.getModel($group), options);
  4089. });
  4090. if (options.display_rules_button && options.invert_rules) {
  4091. self.$el.on('click.queryBuilder', '[data-invert=rule]', function() {
  4092. var $rule = $(this).closest(Selectors.rule_container);
  4093. self.invert(self.getModel($rule), options);
  4094. });
  4095. }
  4096. });
  4097. // Modify templates
  4098. if (!options.disable_template) {
  4099. this.on('getGroupTemplate.filter', function(h) {
  4100. var $h = $(h.value);
  4101. $h.find(Selectors.condition_container).after(
  4102. '<button type="button" class="btn btn-xs btn-default" data-invert="group">' +
  4103. '<i class="' + options.icon + '"></i> ' + self.translate('invert') +
  4104. '</button>'
  4105. );
  4106. h.value = $h.prop('outerHTML');
  4107. });
  4108. if (options.display_rules_button && options.invert_rules) {
  4109. this.on('getRuleTemplate.filter', function(h) {
  4110. var $h = $(h.value);
  4111. $h.find(Selectors.rule_actions).prepend(
  4112. '<button type="button" class="btn btn-xs btn-default" data-invert="rule">' +
  4113. '<i class="' + options.icon + '"></i> ' + self.translate('invert') +
  4114. '</button>'
  4115. );
  4116. h.value = $h.prop('outerHTML');
  4117. });
  4118. }
  4119. }
  4120. }, {
  4121. icon: 'glyphicon glyphicon-random',
  4122. recursive: true,
  4123. invert_rules: true,
  4124. display_rules_button: false,
  4125. silent_fail: false,
  4126. disable_template: false
  4127. });
  4128. QueryBuilder.defaults({
  4129. operatorOpposites: {
  4130. 'equal': 'not_equal',
  4131. 'not_equal': 'equal',
  4132. 'in': 'not_in',
  4133. 'not_in': 'in',
  4134. 'less': 'greater_or_equal',
  4135. 'less_or_equal': 'greater',
  4136. 'greater': 'less_or_equal',
  4137. 'greater_or_equal': 'less',
  4138. 'between': 'not_between',
  4139. 'not_between': 'between',
  4140. 'begins_with': 'not_begins_with',
  4141. 'not_begins_with': 'begins_with',
  4142. 'contains': 'not_contains',
  4143. 'not_contains': 'contains',
  4144. 'ends_with': 'not_ends_with',
  4145. 'not_ends_with': 'ends_with',
  4146. 'is_empty': 'is_not_empty',
  4147. 'is_not_empty': 'is_empty',
  4148. 'is_null': 'is_not_null',
  4149. 'is_not_null': 'is_null'
  4150. },
  4151. conditionOpposites: {
  4152. 'AND': 'OR',
  4153. 'OR': 'AND'
  4154. }
  4155. });
  4156. QueryBuilder.extend(/** @lends module:plugins.Invert.prototype */ {
  4157. /**
  4158. * Invert a Group, a Rule or the whole builder
  4159. * @param {Node} [node]
  4160. * @param {object} [options] {@link module:plugins.Invert}
  4161. * @fires module:plugins.Invert.afterInvert
  4162. * @throws InvertConditionError, InvertOperatorError
  4163. */
  4164. invert: function(node, options) {
  4165. if (!(node instanceof Node)) {
  4166. if (!this.model.root) return;
  4167. options = node;
  4168. node = this.model.root;
  4169. }
  4170. if (typeof options != 'object') options = {};
  4171. if (options.recursive === undefined) options.recursive = true;
  4172. if (options.invert_rules === undefined) options.invert_rules = true;
  4173. if (options.silent_fail === undefined) options.silent_fail = false;
  4174. if (options.trigger === undefined) options.trigger = true;
  4175. if (node instanceof Group) {
  4176. // invert group condition
  4177. if (this.settings.conditionOpposites[node.condition]) {
  4178. node.condition = this.settings.conditionOpposites[node.condition];
  4179. }
  4180. else if (!options.silent_fail) {
  4181. Utils.error('InvertCondition', 'Unknown inverse of condition "{0}"', node.condition);
  4182. }
  4183. // recursive call
  4184. if (options.recursive) {
  4185. var tempOpts = $.extend({}, options, { trigger: false });
  4186. node.each(function(rule) {
  4187. if (options.invert_rules) {
  4188. this.invert(rule, tempOpts);
  4189. }
  4190. }, function(group) {
  4191. this.invert(group, tempOpts);
  4192. }, this);
  4193. }
  4194. }
  4195. else if (node instanceof Rule) {
  4196. if (node.operator && !node.filter.no_invert) {
  4197. // invert rule operator
  4198. if (this.settings.operatorOpposites[node.operator.type]) {
  4199. var invert = this.settings.operatorOpposites[node.operator.type];
  4200. // check if the invert is "authorized"
  4201. if (!node.filter.operators || node.filter.operators.indexOf(invert) != -1) {
  4202. node.operator = this.getOperatorByType(invert);
  4203. }
  4204. }
  4205. else if (!options.silent_fail) {
  4206. Utils.error('InvertOperator', 'Unknown inverse of operator "{0}"', node.operator.type);
  4207. }
  4208. }
  4209. }
  4210. if (options.trigger) {
  4211. /**
  4212. * After {@link module:plugins.Invert.invert} method
  4213. * @event afterInvert
  4214. * @memberof module:plugins.Invert
  4215. * @param {Node} node - the main group or rule that has been modified
  4216. * @param {object} options
  4217. */
  4218. this.trigger('afterInvert', node, options);
  4219. this.trigger('rulesChanged');
  4220. }
  4221. }
  4222. });
  4223. /**
  4224. * @class MongoDbSupport
  4225. * @memberof module:plugins
  4226. * @description Allows to export rules as a MongoDB find object as well as populating the builder from a MongoDB object.
  4227. */
  4228. QueryBuilder.defaults({
  4229. mongoOperators: {
  4230. // @formatter:off
  4231. equal: function(v) { return v[0]; },
  4232. not_equal: function(v) { return { '$ne': v[0] }; },
  4233. in: function(v) { return { '$in': v }; },
  4234. not_in: function(v) { return { '$nin': v }; },
  4235. less: function(v) { return { '$lt': v[0] }; },
  4236. less_or_equal: function(v) { return { '$lte': v[0] }; },
  4237. greater: function(v) { return { '$gt': v[0] }; },
  4238. greater_or_equal: function(v) { return { '$gte': v[0] }; },
  4239. between: function(v) { return { '$gte': v[0], '$lte': v[1] }; },
  4240. not_between: function(v) { return { '$lt': v[0], '$gt': v[1] }; },
  4241. begins_with: function(v) { return { '$regex': '^' + Utils.escapeRegExp(v[0]) }; },
  4242. not_begins_with: function(v) { return { '$regex': '^(?!' + Utils.escapeRegExp(v[0]) + ')' }; },
  4243. contains: function(v) { return { '$regex': Utils.escapeRegExp(v[0]) }; },
  4244. not_contains: function(v) { return { '$regex': '^((?!' + Utils.escapeRegExp(v[0]) + ').)*$', '$options': 's' }; },
  4245. ends_with: function(v) { return { '$regex': Utils.escapeRegExp(v[0]) + '$' }; },
  4246. not_ends_with: function(v) { return { '$regex': '(?<!' + Utils.escapeRegExp(v[0]) + ')$' }; },
  4247. is_empty: function(v) { return ''; },
  4248. is_not_empty: function(v) { return { '$ne': '' }; },
  4249. is_null: function(v) { return null; },
  4250. is_not_null: function(v) { return { '$ne': null }; }
  4251. // @formatter:on
  4252. },
  4253. mongoRuleOperators: {
  4254. $eq: function(v) {
  4255. return {
  4256. 'val': v,
  4257. 'op': v === null ? 'is_null' : (v === '' ? 'is_empty' : 'equal')
  4258. };
  4259. },
  4260. $ne: function(v) {
  4261. v = v.$ne;
  4262. return {
  4263. 'val': v,
  4264. 'op': v === null ? 'is_not_null' : (v === '' ? 'is_not_empty' : 'not_equal')
  4265. };
  4266. },
  4267. $regex: function(v) {
  4268. v = v.$regex;
  4269. if (v.slice(0, 4) == '^(?!' && v.slice(-1) == ')') {
  4270. return { 'val': v.slice(4, -1), 'op': 'not_begins_with' };
  4271. }
  4272. else if (v.slice(0, 5) == '^((?!' && v.slice(-5) == ').)*$') {
  4273. return { 'val': v.slice(5, -5), 'op': 'not_contains' };
  4274. }
  4275. else if (v.slice(0, 4) == '(?<!' && v.slice(-2) == ')$') {
  4276. return { 'val': v.slice(4, -2), 'op': 'not_ends_with' };
  4277. }
  4278. else if (v.slice(-1) == '$') {
  4279. return { 'val': v.slice(0, -1), 'op': 'ends_with' };
  4280. }
  4281. else if (v.slice(0, 1) == '^') {
  4282. return { 'val': v.slice(1), 'op': 'begins_with' };
  4283. }
  4284. else {
  4285. return { 'val': v, 'op': 'contains' };
  4286. }
  4287. },
  4288. between: function(v) {
  4289. return { 'val': [v.$gte, v.$lte], 'op': 'between' };
  4290. },
  4291. not_between: function(v) {
  4292. return { 'val': [v.$lt, v.$gt], 'op': 'not_between' };
  4293. },
  4294. $in: function(v) {
  4295. return { 'val': v.$in, 'op': 'in' };
  4296. },
  4297. $nin: function(v) {
  4298. return { 'val': v.$nin, 'op': 'not_in' };
  4299. },
  4300. $lt: function(v) {
  4301. return { 'val': v.$lt, 'op': 'less' };
  4302. },
  4303. $lte: function(v) {
  4304. return { 'val': v.$lte, 'op': 'less_or_equal' };
  4305. },
  4306. $gt: function(v) {
  4307. return { 'val': v.$gt, 'op': 'greater' };
  4308. },
  4309. $gte: function(v) {
  4310. return { 'val': v.$gte, 'op': 'greater_or_equal' };
  4311. }
  4312. }
  4313. });
  4314. QueryBuilder.extend(/** @lends module:plugins.MongoDbSupport.prototype */ {
  4315. /**
  4316. * Returns rules as a MongoDB query
  4317. * @param {object} [data] - current rules by default
  4318. * @returns {object}
  4319. * @fires module:plugins.MongoDbSupport.changer:getMongoDBField
  4320. * @fires module:plugins.MongoDbSupport.changer:ruleToMongo
  4321. * @fires module:plugins.MongoDbSupport.changer:groupToMongo
  4322. * @throws UndefinedMongoConditionError, UndefinedMongoOperatorError
  4323. */
  4324. getMongo: function(data) {
  4325. data = (data === undefined) ? this.getRules() : data;
  4326. if (!data) {
  4327. return null;
  4328. }
  4329. var self = this;
  4330. return (function parse(group) {
  4331. if (!group.condition) {
  4332. group.condition = self.settings.default_condition;
  4333. }
  4334. if (['AND', 'OR'].indexOf(group.condition.toUpperCase()) === -1) {
  4335. Utils.error('UndefinedMongoCondition', 'Unable to build MongoDB query with condition "{0}"', group.condition);
  4336. }
  4337. if (!group.rules) {
  4338. return {};
  4339. }
  4340. var parts = [];
  4341. group.rules.forEach(function(rule) {
  4342. if (rule.rules && rule.rules.length > 0) {
  4343. parts.push(parse(rule));
  4344. }
  4345. else {
  4346. var mdb = self.settings.mongoOperators[rule.operator];
  4347. var ope = self.getOperatorByType(rule.operator);
  4348. if (mdb === undefined) {
  4349. Utils.error('UndefinedMongoOperator', 'Unknown MongoDB operation for operator "{0}"', rule.operator);
  4350. }
  4351. if (ope.nb_inputs !== 0) {
  4352. if (!(rule.value instanceof Array)) {
  4353. rule.value = [rule.value];
  4354. }
  4355. }
  4356. /**
  4357. * Modifies the MongoDB field used by a rule
  4358. * @event changer:getMongoDBField
  4359. * @memberof module:plugins.MongoDbSupport
  4360. * @param {string} field
  4361. * @param {Rule} rule
  4362. * @returns {string}
  4363. */
  4364. var field = self.change('getMongoDBField', rule.field, rule);
  4365. var ruleExpression = {};
  4366. ruleExpression[field] = mdb.call(self, rule.value);
  4367. /**
  4368. * Modifies the MongoDB expression generated for a rul
  4369. * @event changer:ruleToMongo
  4370. * @memberof module:plugins.MongoDbSupport
  4371. * @param {object} expression
  4372. * @param {Rule} rule
  4373. * @param {*} value
  4374. * @param {function} valueWrapper - function that takes the value and adds the operator
  4375. * @returns {object}
  4376. */
  4377. parts.push(self.change('ruleToMongo', ruleExpression, rule, rule.value, mdb));
  4378. }
  4379. });
  4380. var groupExpression = {};
  4381. groupExpression['$' + group.condition.toLowerCase()] = parts;
  4382. /**
  4383. * Modifies the MongoDB expression generated for a group
  4384. * @event changer:groupToMongo
  4385. * @memberof module:plugins.MongoDbSupport
  4386. * @param {object} expression
  4387. * @param {Group} group
  4388. * @returns {object}
  4389. */
  4390. return self.change('groupToMongo', groupExpression, group);
  4391. }(data));
  4392. },
  4393. /**
  4394. * Converts a MongoDB query to rules
  4395. * @param {object} query
  4396. * @returns {object}
  4397. * @fires module:plugins.MongoDbSupport.changer:parseMongoNode
  4398. * @fires module:plugins.MongoDbSupport.changer:getMongoDBFieldID
  4399. * @fires module:plugins.MongoDbSupport.changer:mongoToRule
  4400. * @fires module:plugins.MongoDbSupport.changer:mongoToGroup
  4401. * @throws MongoParseError, UndefinedMongoConditionError, UndefinedMongoOperatorError
  4402. */
  4403. getRulesFromMongo: function(query) {
  4404. if (query === undefined || query === null) {
  4405. return null;
  4406. }
  4407. var self = this;
  4408. /**
  4409. * Custom parsing of a MongoDB expression, you can return a sub-part of the expression, or a well formed group or rule JSON
  4410. * @event changer:parseMongoNode
  4411. * @memberof module:plugins.MongoDbSupport
  4412. * @param {object} expression
  4413. * @returns {object} expression, rule or group
  4414. */
  4415. query = self.change('parseMongoNode', query);
  4416. // a plugin returned a group
  4417. if ('rules' in query && 'condition' in query) {
  4418. return query;
  4419. }
  4420. // a plugin returned a rule
  4421. if ('id' in query && 'operator' in query && 'value' in query) {
  4422. return {
  4423. condition: this.settings.default_condition,
  4424. rules: [query]
  4425. };
  4426. }
  4427. var key = self.getMongoCondition(query);
  4428. if (!key) {
  4429. Utils.error('MongoParse', 'Invalid MongoDB query format');
  4430. }
  4431. return (function parse(data, topKey) {
  4432. var rules = data[topKey];
  4433. var parts = [];
  4434. rules.forEach(function(data) {
  4435. // allow plugins to manually parse or handle special cases
  4436. data = self.change('parseMongoNode', data);
  4437. // a plugin returned a group
  4438. if ('rules' in data && 'condition' in data) {
  4439. parts.push(data);
  4440. return;
  4441. }
  4442. // a plugin returned a rule
  4443. if ('id' in data && 'operator' in data && 'value' in data) {
  4444. parts.push(data);
  4445. return;
  4446. }
  4447. var key = self.getMongoCondition(data);
  4448. if (key) {
  4449. parts.push(parse(data, key));
  4450. }
  4451. else {
  4452. var field = Object.keys(data)[0];
  4453. var value = data[field];
  4454. var operator = self.getMongoOperator(value);
  4455. if (operator === undefined) {
  4456. Utils.error('MongoParse', 'Invalid MongoDB query format');
  4457. }
  4458. var mdbrl = self.settings.mongoRuleOperators[operator];
  4459. if (mdbrl === undefined) {
  4460. Utils.error('UndefinedMongoOperator', 'JSON Rule operation unknown for operator "{0}"', operator);
  4461. }
  4462. var opVal = mdbrl.call(self, value);
  4463. var id = self.getMongoDBFieldID(field, value);
  4464. /**
  4465. * Modifies the rule generated from the MongoDB expression
  4466. * @event changer:mongoToRule
  4467. * @memberof module:plugins.MongoDbSupport
  4468. * @param {object} rule
  4469. * @param {object} expression
  4470. * @returns {object}
  4471. */
  4472. var rule = self.change('mongoToRule', {
  4473. id: id,
  4474. field: field,
  4475. operator: opVal.op,
  4476. value: opVal.val
  4477. }, data);
  4478. parts.push(rule);
  4479. }
  4480. });
  4481. /**
  4482. * Modifies the group generated from the MongoDB expression
  4483. * @event changer:mongoToGroup
  4484. * @memberof module:plugins.MongoDbSupport
  4485. * @param {object} group
  4486. * @param {object} expression
  4487. * @returns {object}
  4488. */
  4489. return self.change('mongoToGroup', {
  4490. condition: topKey.replace('$', '').toUpperCase(),
  4491. rules: parts
  4492. }, data);
  4493. }(query, key));
  4494. },
  4495. /**
  4496. * Sets rules a from MongoDB query
  4497. * @see module:plugins.MongoDbSupport.getRulesFromMongo
  4498. */
  4499. setRulesFromMongo: function(query) {
  4500. this.setRules(this.getRulesFromMongo(query));
  4501. },
  4502. /**
  4503. * Returns a filter identifier from the MongoDB field.
  4504. * Automatically use the only one filter with a matching field, fires a changer otherwise.
  4505. * @param {string} field
  4506. * @param {*} value
  4507. * @fires module:plugins.MongoDbSupport:changer:getMongoDBFieldID
  4508. * @returns {string}
  4509. * @private
  4510. */
  4511. getMongoDBFieldID: function(field, value) {
  4512. var matchingFilters = this.filters.filter(function(filter) {
  4513. return filter.field === field;
  4514. });
  4515. var id;
  4516. if (matchingFilters.length === 1) {
  4517. id = matchingFilters[0].id;
  4518. }
  4519. else {
  4520. /**
  4521. * Returns a filter identifier from the MongoDB field
  4522. * @event changer:getMongoDBFieldID
  4523. * @memberof module:plugins.MongoDbSupport
  4524. * @param {string} field
  4525. * @param {*} value
  4526. * @returns {string}
  4527. */
  4528. id = this.change('getMongoDBFieldID', field, value);
  4529. }
  4530. return id;
  4531. },
  4532. /**
  4533. * Finds which operator is used in a MongoDB sub-object
  4534. * @param {*} data
  4535. * @returns {string|undefined}
  4536. * @private
  4537. */
  4538. getMongoOperator: function(data) {
  4539. if (data !== null && typeof data === 'object') {
  4540. if (data.$gte !== undefined && data.$lte !== undefined) {
  4541. return 'between';
  4542. }
  4543. if (data.$lt !== undefined && data.$gt !== undefined) {
  4544. return 'not_between';
  4545. }
  4546. var knownKeys = Object.keys(data).filter(function(key) {
  4547. return !!this.settings.mongoRuleOperators[key];
  4548. }.bind(this));
  4549. if (knownKeys.length === 1) {
  4550. return knownKeys[0];
  4551. }
  4552. }
  4553. else {
  4554. return '$eq';
  4555. }
  4556. },
  4557. /**
  4558. * Returns the key corresponding to "$or" or "$and"
  4559. * @param {object} data
  4560. * @returns {string|undefined}
  4561. * @private
  4562. */
  4563. getMongoCondition: function(data) {
  4564. var keys = Object.keys(data);
  4565. for (var i = 0, l = keys.length; i < l; i++) {
  4566. if (keys[i].toLowerCase() === '$or' || keys[i].toLowerCase() === '$and') {
  4567. return keys[i];
  4568. }
  4569. }
  4570. }
  4571. });
  4572. /**
  4573. * @class NotGroup
  4574. * @memberof module:plugins
  4575. * @description Adds a "Not" checkbox in front of group conditions.
  4576. * @param {object} [options]
  4577. * @param {string} [options.icon_checked='glyphicon glyphicon-checked']
  4578. * @param {string} [options.icon_unchecked='glyphicon glyphicon-unchecked']
  4579. */
  4580. QueryBuilder.define('not-group', function(options) {
  4581. var self = this;
  4582. // Bind events
  4583. this.on('afterInit', function() {
  4584. self.$el.on('click.queryBuilder', '[data-not=group]', function() {
  4585. var $group = $(this).closest(QueryBuilder.selectors.group_container);
  4586. var group = self.getModel($group);
  4587. group.not = !group.not;
  4588. });
  4589. self.model.on('update', function(e, node, field) {
  4590. if (node instanceof Group && field === 'not') {
  4591. self.updateGroupNot(node);
  4592. }
  4593. });
  4594. });
  4595. // Init "not" property
  4596. this.on('afterAddGroup', function(e, group) {
  4597. group.__.not = false;
  4598. });
  4599. // Modify templates
  4600. if (!options.disable_template) {
  4601. this.on('getGroupTemplate.filter', function(h) {
  4602. var $h = $(h.value);
  4603. $h.find(QueryBuilder.selectors.condition_container).prepend(
  4604. '<button type="button" class="btn btn-xs btn-default" data-not="group">' +
  4605. '<i class="' + options.icon_unchecked + '"></i> ' + self.translate('NOT') +
  4606. '</button>'
  4607. );
  4608. h.value = $h.prop('outerHTML');
  4609. });
  4610. }
  4611. // Export "not" to JSON
  4612. this.on('groupToJson.filter', function(e, group) {
  4613. e.value.not = group.not;
  4614. });
  4615. // Read "not" from JSON
  4616. this.on('jsonToGroup.filter', function(e, json) {
  4617. e.value.not = !!json.not;
  4618. });
  4619. // Export "not" to SQL
  4620. this.on('groupToSQL.filter', function(e, group) {
  4621. if (group.not) {
  4622. e.value = 'NOT ( ' + e.value + ' )';
  4623. }
  4624. });
  4625. // Parse "NOT" function from sqlparser
  4626. this.on('parseSQLNode.filter', function(e) {
  4627. if (e.value.name && e.value.name.toUpperCase() == 'NOT') {
  4628. e.value = e.value.arguments.value[0];
  4629. // if the there is no sub-group, create one
  4630. if (['AND', 'OR'].indexOf(e.value.operation.toUpperCase()) === -1) {
  4631. e.value = new SQLParser.nodes.Op(
  4632. self.settings.default_condition,
  4633. e.value,
  4634. null
  4635. );
  4636. }
  4637. e.value.not = true;
  4638. }
  4639. });
  4640. // Request to create sub-group if the "not" flag is set
  4641. this.on('sqlGroupsDistinct.filter', function(e, group, data, i) {
  4642. if (data.not && i > 0) {
  4643. e.value = true;
  4644. }
  4645. });
  4646. // Read "not" from parsed SQL
  4647. this.on('sqlToGroup.filter', function(e, data) {
  4648. e.value.not = !!data.not;
  4649. });
  4650. // Export "not" to Mongo
  4651. this.on('groupToMongo.filter', function(e, group) {
  4652. var key = '$' + group.condition.toLowerCase();
  4653. if (group.not && e.value[key]) {
  4654. e.value = { '$nor': [e.value] };
  4655. }
  4656. });
  4657. // Parse "$nor" operator from Mongo
  4658. this.on('parseMongoNode.filter', function(e) {
  4659. var keys = Object.keys(e.value);
  4660. if (keys[0] == '$nor') {
  4661. e.value = e.value[keys[0]][0];
  4662. e.value.not = true;
  4663. }
  4664. });
  4665. // Read "not" from parsed Mongo
  4666. this.on('mongoToGroup.filter', function(e, data) {
  4667. e.value.not = !!data.not;
  4668. });
  4669. }, {
  4670. icon_unchecked: 'glyphicon glyphicon-unchecked',
  4671. icon_checked: 'glyphicon glyphicon-check',
  4672. disable_template: false
  4673. });
  4674. /**
  4675. * From {@link module:plugins.NotGroup}
  4676. * @name not
  4677. * @member {boolean}
  4678. * @memberof Group
  4679. * @instance
  4680. */
  4681. Utils.defineModelProperties(Group, ['not']);
  4682. QueryBuilder.selectors.group_not = QueryBuilder.selectors.group_header + ' [data-not=group]';
  4683. QueryBuilder.extend(/** @lends module:plugins.NotGroup.prototype */ {
  4684. /**
  4685. * Performs actions when a group's not changes
  4686. * @param {Group} group
  4687. * @fires module:plugins.NotGroup.afterUpdateGroupNot
  4688. * @private
  4689. */
  4690. updateGroupNot: function(group) {
  4691. var options = this.plugins['not-group'];
  4692. group.$el.find('>' + QueryBuilder.selectors.group_not)
  4693. .toggleClass('active', group.not)
  4694. .find('i').attr('class', group.not ? options.icon_checked : options.icon_unchecked);
  4695. /**
  4696. * After the group's not flag has been modified
  4697. * @event afterUpdateGroupNot
  4698. * @memberof module:plugins.NotGroup
  4699. * @param {Group} group
  4700. */
  4701. this.trigger('afterUpdateGroupNot', group);
  4702. this.trigger('rulesChanged');
  4703. }
  4704. });
  4705. /**
  4706. * @class Sortable
  4707. * @memberof module:plugins
  4708. * @description Enables drag & drop sort of rules.
  4709. * @param {object} [options]
  4710. * @param {boolean} [options.inherit_no_drop=true]
  4711. * @param {boolean} [options.inherit_no_sortable=true]
  4712. * @param {string} [options.icon='glyphicon glyphicon-sort']
  4713. * @throws MissingLibraryError, ConfigError
  4714. */
  4715. QueryBuilder.define('sortable', function(options) {
  4716. if (!('interact' in window)) {
  4717. Utils.error('MissingLibrary', 'interact.js is required to use "sortable" plugin. Get it here: http://interactjs.io');
  4718. }
  4719. if (options.default_no_sortable !== undefined) {
  4720. Utils.error(false, 'Config', 'Sortable plugin : "default_no_sortable" options is deprecated, use standard "default_rule_flags" and "default_group_flags" instead');
  4721. this.settings.default_rule_flags.no_sortable = this.settings.default_group_flags.no_sortable = options.default_no_sortable;
  4722. }
  4723. // recompute drop-zones during drag (when a rule is hidden)
  4724. interact.dynamicDrop(true);
  4725. // set move threshold to 10px
  4726. interact.pointerMoveTolerance(10);
  4727. var placeholder;
  4728. var ghost;
  4729. var src;
  4730. var moved;
  4731. // Init drag and drop
  4732. this.on('afterAddRule afterAddGroup', function(e, node) {
  4733. if (node == placeholder) {
  4734. return;
  4735. }
  4736. var self = e.builder;
  4737. // Inherit flags
  4738. if (options.inherit_no_sortable && node.parent && node.parent.flags.no_sortable) {
  4739. node.flags.no_sortable = true;
  4740. }
  4741. if (options.inherit_no_drop && node.parent && node.parent.flags.no_drop) {
  4742. node.flags.no_drop = true;
  4743. }
  4744. // Configure drag
  4745. if (!node.flags.no_sortable) {
  4746. interact(node.$el[0])
  4747. .draggable({
  4748. allowFrom: QueryBuilder.selectors.drag_handle,
  4749. onstart: function(event) {
  4750. moved = false;
  4751. // get model of dragged element
  4752. src = self.getModel(event.target);
  4753. // create ghost
  4754. ghost = src.$el.clone()
  4755. .appendTo(src.$el.parent())
  4756. .width(src.$el.outerWidth())
  4757. .addClass('dragging');
  4758. // create drop placeholder
  4759. var ph = $('<div class="rule-placeholder">&nbsp;</div>')
  4760. .height(src.$el.outerHeight());
  4761. placeholder = src.parent.addRule(ph, src.getPos());
  4762. // hide dragged element
  4763. src.$el.hide();
  4764. },
  4765. onmove: function(event) {
  4766. // make the ghost follow the cursor
  4767. ghost[0].style.top = event.clientY - 15 + 'px';
  4768. ghost[0].style.left = event.clientX - 15 + 'px';
  4769. },
  4770. onend: function(event) {
  4771. // starting from Interact 1.3.3, onend is called before ondrop
  4772. if (event.dropzone) {
  4773. moveSortableToTarget(src, $(event.relatedTarget), self);
  4774. moved = true;
  4775. }
  4776. // remove ghost
  4777. ghost.remove();
  4778. ghost = undefined;
  4779. // remove placeholder
  4780. placeholder.drop();
  4781. placeholder = undefined;
  4782. // show element
  4783. src.$el.css('display', '');
  4784. /**
  4785. * After a node has been moved with {@link module:plugins.Sortable}
  4786. * @event afterMove
  4787. * @memberof module:plugins.Sortable
  4788. * @param {Node} node
  4789. */
  4790. self.trigger('afterMove', src);
  4791. self.trigger('rulesChanged');
  4792. }
  4793. });
  4794. }
  4795. if (!node.flags.no_drop) {
  4796. // Configure drop on groups and rules
  4797. interact(node.$el[0])
  4798. .dropzone({
  4799. accept: QueryBuilder.selectors.rule_and_group_containers,
  4800. ondragenter: function(event) {
  4801. moveSortableToTarget(placeholder, $(event.target), self);
  4802. },
  4803. ondrop: function(event) {
  4804. if (!moved) {
  4805. moveSortableToTarget(src, $(event.target), self);
  4806. }
  4807. }
  4808. });
  4809. // Configure drop on group headers
  4810. if (node instanceof Group) {
  4811. interact(node.$el.find(QueryBuilder.selectors.group_header)[0])
  4812. .dropzone({
  4813. accept: QueryBuilder.selectors.rule_and_group_containers,
  4814. ondragenter: function(event) {
  4815. moveSortableToTarget(placeholder, $(event.target), self);
  4816. },
  4817. ondrop: function(event) {
  4818. if (!moved) {
  4819. moveSortableToTarget(src, $(event.target), self);
  4820. }
  4821. }
  4822. });
  4823. }
  4824. }
  4825. });
  4826. // Detach interactables
  4827. this.on('beforeDeleteRule beforeDeleteGroup', function(e, node) {
  4828. if (!e.isDefaultPrevented()) {
  4829. interact(node.$el[0]).unset();
  4830. if (node instanceof Group) {
  4831. interact(node.$el.find(QueryBuilder.selectors.group_header)[0]).unset();
  4832. }
  4833. }
  4834. });
  4835. // Remove drag handle from non-sortable items
  4836. this.on('afterApplyRuleFlags afterApplyGroupFlags', function(e, node) {
  4837. if (node.flags.no_sortable) {
  4838. node.$el.find('.drag-handle').remove();
  4839. }
  4840. });
  4841. // Modify templates
  4842. if (!options.disable_template) {
  4843. this.on('getGroupTemplate.filter', function(h, level) {
  4844. if (level > 1) {
  4845. var $h = $(h.value);
  4846. $h.find(QueryBuilder.selectors.condition_container).after('<div class="drag-handle"><i class="' + options.icon + '"></i></div>');
  4847. h.value = $h.prop('outerHTML');
  4848. }
  4849. });
  4850. this.on('getRuleTemplate.filter', function(h) {
  4851. var $h = $(h.value);
  4852. $h.find(QueryBuilder.selectors.rule_header).after('<div class="drag-handle"><i class="' + options.icon + '"></i></div>');
  4853. h.value = $h.prop('outerHTML');
  4854. });
  4855. }
  4856. }, {
  4857. inherit_no_sortable: true,
  4858. inherit_no_drop: true,
  4859. icon: 'glyphicon glyphicon-sort',
  4860. disable_template: false
  4861. });
  4862. QueryBuilder.selectors.rule_and_group_containers = QueryBuilder.selectors.rule_container + ', ' + QueryBuilder.selectors.group_container;
  4863. QueryBuilder.selectors.drag_handle = '.drag-handle';
  4864. QueryBuilder.defaults({
  4865. default_rule_flags: {
  4866. no_sortable: false,
  4867. no_drop: false
  4868. },
  4869. default_group_flags: {
  4870. no_sortable: false,
  4871. no_drop: false
  4872. }
  4873. });
  4874. /**
  4875. * Moves an element (placeholder or actual object) depending on active target
  4876. * @memberof module:plugins.Sortable
  4877. * @param {Node} node
  4878. * @param {jQuery} target
  4879. * @param {QueryBuilder} [builder]
  4880. * @private
  4881. */
  4882. function moveSortableToTarget(node, target, builder) {
  4883. var parent, method;
  4884. var Selectors = QueryBuilder.selectors;
  4885. // on rule
  4886. parent = target.closest(Selectors.rule_container);
  4887. if (parent.length) {
  4888. method = 'moveAfter';
  4889. }
  4890. // on group header
  4891. if (!method) {
  4892. parent = target.closest(Selectors.group_header);
  4893. if (parent.length) {
  4894. parent = target.closest(Selectors.group_container);
  4895. method = 'moveAtBegin';
  4896. }
  4897. }
  4898. // on group
  4899. if (!method) {
  4900. parent = target.closest(Selectors.group_container);
  4901. if (parent.length) {
  4902. method = 'moveAtEnd';
  4903. }
  4904. }
  4905. if (method) {
  4906. node[method](builder.getModel(parent));
  4907. // refresh radio value
  4908. if (builder && node instanceof Rule) {
  4909. builder.setRuleInputValue(node, node.value);
  4910. }
  4911. }
  4912. }
  4913. /**
  4914. * @class SqlSupport
  4915. * @memberof module:plugins
  4916. * @description Allows to export rules as a SQL WHERE statement as well as populating the builder from an SQL query.
  4917. * @param {object} [options]
  4918. * @param {boolean} [options.boolean_as_integer=true] - `true` to convert boolean values to integer in the SQL output
  4919. */
  4920. QueryBuilder.define('sql-support', function(options) {
  4921. }, {
  4922. boolean_as_integer: true
  4923. });
  4924. QueryBuilder.defaults({
  4925. // operators for internal -> SQL conversion
  4926. sqlOperators: {
  4927. equal: { op: '= ?' },
  4928. not_equal: { op: '!= ?' },
  4929. in: { op: 'IN(?)', sep: ', ' },
  4930. not_in: { op: 'NOT IN(?)', sep: ', ' },
  4931. less: { op: '< ?' },
  4932. less_or_equal: { op: '<= ?' },
  4933. greater: { op: '> ?' },
  4934. greater_or_equal: { op: '>= ?' },
  4935. between: { op: 'BETWEEN ?', sep: ' AND ' },
  4936. not_between: { op: 'NOT BETWEEN ?', sep: ' AND ' },
  4937. begins_with: { op: 'LIKE(?)', mod: '{0}%' },
  4938. not_begins_with: { op: 'NOT LIKE(?)', mod: '{0}%' },
  4939. contains: { op: 'LIKE(?)', mod: '%{0}%' },
  4940. not_contains: { op: 'NOT LIKE(?)', mod: '%{0}%' },
  4941. ends_with: { op: 'LIKE(?)', mod: '%{0}' },
  4942. not_ends_with: { op: 'NOT LIKE(?)', mod: '%{0}' },
  4943. is_empty: { op: '= \'\'' },
  4944. is_not_empty: { op: '!= \'\'' },
  4945. is_null: { op: 'IS NULL' },
  4946. is_not_null: { op: 'IS NOT NULL' }
  4947. },
  4948. // operators for SQL -> internal conversion
  4949. sqlRuleOperator: {
  4950. '=': function(v) {
  4951. return {
  4952. val: v,
  4953. op: v === '' ? 'is_empty' : 'equal'
  4954. };
  4955. },
  4956. '!=': function(v) {
  4957. return {
  4958. val: v,
  4959. op: v === '' ? 'is_not_empty' : 'not_equal'
  4960. };
  4961. },
  4962. 'LIKE': function(v) {
  4963. if (v.slice(0, 1) == '%' && v.slice(-1) == '%') {
  4964. return {
  4965. val: v.slice(1, -1),
  4966. op: 'contains'
  4967. };
  4968. }
  4969. else if (v.slice(0, 1) == '%') {
  4970. return {
  4971. val: v.slice(1),
  4972. op: 'ends_with'
  4973. };
  4974. }
  4975. else if (v.slice(-1) == '%') {
  4976. return {
  4977. val: v.slice(0, -1),
  4978. op: 'begins_with'
  4979. };
  4980. }
  4981. else {
  4982. Utils.error('SQLParse', 'Invalid value for LIKE operator "{0}"', v);
  4983. }
  4984. },
  4985. 'NOT LIKE': function(v) {
  4986. if (v.slice(0, 1) == '%' && v.slice(-1) == '%') {
  4987. return {
  4988. val: v.slice(1, -1),
  4989. op: 'not_contains'
  4990. };
  4991. }
  4992. else if (v.slice(0, 1) == '%') {
  4993. return {
  4994. val: v.slice(1),
  4995. op: 'not_ends_with'
  4996. };
  4997. }
  4998. else if (v.slice(-1) == '%') {
  4999. return {
  5000. val: v.slice(0, -1),
  5001. op: 'not_begins_with'
  5002. };
  5003. }
  5004. else {
  5005. Utils.error('SQLParse', 'Invalid value for NOT LIKE operator "{0}"', v);
  5006. }
  5007. },
  5008. 'IN': function(v) {
  5009. return { val: v, op: 'in' };
  5010. },
  5011. 'NOT IN': function(v) {
  5012. return { val: v, op: 'not_in' };
  5013. },
  5014. '<': function(v) {
  5015. return { val: v, op: 'less' };
  5016. },
  5017. '<=': function(v) {
  5018. return { val: v, op: 'less_or_equal' };
  5019. },
  5020. '>': function(v) {
  5021. return { val: v, op: 'greater' };
  5022. },
  5023. '>=': function(v) {
  5024. return { val: v, op: 'greater_or_equal' };
  5025. },
  5026. 'BETWEEN': function(v) {
  5027. return { val: v, op: 'between' };
  5028. },
  5029. 'NOT BETWEEN': function(v) {
  5030. return { val: v, op: 'not_between' };
  5031. },
  5032. 'IS': function(v) {
  5033. if (v !== null) {
  5034. Utils.error('SQLParse', 'Invalid value for IS operator');
  5035. }
  5036. return { val: null, op: 'is_null' };
  5037. },
  5038. 'IS NOT': function(v) {
  5039. if (v !== null) {
  5040. Utils.error('SQLParse', 'Invalid value for IS operator');
  5041. }
  5042. return { val: null, op: 'is_not_null' };
  5043. }
  5044. },
  5045. // statements for internal -> SQL conversion
  5046. sqlStatements: {
  5047. 'question_mark': function() {
  5048. var params = [];
  5049. return {
  5050. add: function(rule, value) {
  5051. params.push(value);
  5052. return '?';
  5053. },
  5054. run: function() {
  5055. return params;
  5056. }
  5057. };
  5058. },
  5059. 'numbered': function(char) {
  5060. if (!char || char.length > 1) char = '$';
  5061. var index = 0;
  5062. var params = [];
  5063. return {
  5064. add: function(rule, value) {
  5065. params.push(value);
  5066. index++;
  5067. return char + index;
  5068. },
  5069. run: function() {
  5070. return params;
  5071. }
  5072. };
  5073. },
  5074. 'named': function(char) {
  5075. if (!char || char.length > 1) char = ':';
  5076. var indexes = {};
  5077. var params = {};
  5078. return {
  5079. add: function(rule, value) {
  5080. if (!indexes[rule.field]) indexes[rule.field] = 1;
  5081. var key = rule.field + '_' + (indexes[rule.field]++);
  5082. params[key] = value;
  5083. return char + key;
  5084. },
  5085. run: function() {
  5086. return params;
  5087. }
  5088. };
  5089. }
  5090. },
  5091. // statements for SQL -> internal conversion
  5092. sqlRuleStatement: {
  5093. 'question_mark': function(values) {
  5094. var index = 0;
  5095. return {
  5096. parse: function(v) {
  5097. return v == '?' ? values[index++] : v;
  5098. },
  5099. esc: function(sql) {
  5100. return sql.replace(/\?/g, '\'?\'');
  5101. }
  5102. };
  5103. },
  5104. 'numbered': function(values, char) {
  5105. if (!char || char.length > 1) char = '$';
  5106. var regex1 = new RegExp('^\\' + char + '[0-9]+$');
  5107. var regex2 = new RegExp('\\' + char + '([0-9]+)', 'g');
  5108. return {
  5109. parse: function(v) {
  5110. return regex1.test(v) ? values[v.slice(1) - 1] : v;
  5111. },
  5112. esc: function(sql) {
  5113. return sql.replace(regex2, '\'' + (char == '$' ? '$$' : char) + '$1\'');
  5114. }
  5115. };
  5116. },
  5117. 'named': function(values, char) {
  5118. if (!char || char.length > 1) char = ':';
  5119. var regex1 = new RegExp('^\\' + char);
  5120. var regex2 = new RegExp('\\' + char + '(' + Object.keys(values).join('|') + ')', 'g');
  5121. return {
  5122. parse: function(v) {
  5123. return regex1.test(v) ? values[v.slice(1)] : v;
  5124. },
  5125. esc: function(sql) {
  5126. return sql.replace(regex2, '\'' + (char == '$' ? '$$' : char) + '$1\'');
  5127. }
  5128. };
  5129. }
  5130. }
  5131. });
  5132. /**
  5133. * @typedef {object} SqlQuery
  5134. * @memberof module:plugins.SqlSupport
  5135. * @property {string} sql
  5136. * @property {object} params
  5137. */
  5138. QueryBuilder.extend(/** @lends module:plugins.SqlSupport.prototype */ {
  5139. /**
  5140. * Returns rules as a SQL query
  5141. * @param {boolean|string} [stmt] - use prepared statements: false, 'question_mark', 'numbered', 'numbered(@)', 'named', 'named(@)'
  5142. * @param {boolean} [nl=false] output with new lines
  5143. * @param {object} [data] - current rules by default
  5144. * @returns {module:plugins.SqlSupport.SqlQuery}
  5145. * @fires module:plugins.SqlSupport.changer:getSQLField
  5146. * @fires module:plugins.SqlSupport.changer:ruleToSQL
  5147. * @fires module:plugins.SqlSupport.changer:groupToSQL
  5148. * @throws UndefinedSQLConditionError, UndefinedSQLOperatorError
  5149. */
  5150. getSQL: function(stmt, nl, data) {
  5151. data = (data === undefined) ? this.getRules() : data;
  5152. if (!data) {
  5153. return null;
  5154. }
  5155. nl = !!nl ? '\n' : ' ';
  5156. var boolean_as_integer = this.getPluginOptions('sql-support', 'boolean_as_integer');
  5157. if (stmt === true) {
  5158. stmt = 'question_mark';
  5159. }
  5160. if (typeof stmt == 'string') {
  5161. var config = getStmtConfig(stmt);
  5162. stmt = this.settings.sqlStatements[config[1]](config[2]);
  5163. }
  5164. var self = this;
  5165. var sql = (function parse(group) {
  5166. if (!group.condition) {
  5167. group.condition = self.settings.default_condition;
  5168. }
  5169. if (['AND', 'OR'].indexOf(group.condition.toUpperCase()) === -1) {
  5170. Utils.error('UndefinedSQLCondition', 'Unable to build SQL query with condition "{0}"', group.condition);
  5171. }
  5172. if (!group.rules) {
  5173. return '';
  5174. }
  5175. var parts = [];
  5176. group.rules.forEach(function(rule) {
  5177. if (rule.rules && rule.rules.length > 0) {
  5178. parts.push('(' + nl + parse(rule) + nl + ')' + nl);
  5179. }
  5180. else {
  5181. var sql = self.settings.sqlOperators[rule.operator];
  5182. var ope = self.getOperatorByType(rule.operator);
  5183. var value = '';
  5184. if (sql === undefined) {
  5185. Utils.error('UndefinedSQLOperator', 'Unknown SQL operation for operator "{0}"', rule.operator);
  5186. }
  5187. if (ope.nb_inputs !== 0) {
  5188. if (!(rule.value instanceof Array)) {
  5189. rule.value = [rule.value];
  5190. }
  5191. rule.value.forEach(function(v, i) {
  5192. if (i > 0) {
  5193. value += sql.sep;
  5194. }
  5195. if (rule.type == 'boolean' && boolean_as_integer) {
  5196. v = v ? 1 : 0;
  5197. }
  5198. else if (!stmt && rule.type !== 'integer' && rule.type !== 'double' && rule.type !== 'boolean') {
  5199. v = Utils.escapeString(v);
  5200. }
  5201. if (sql.mod) {
  5202. v = Utils.fmt(sql.mod, v);
  5203. }
  5204. if (stmt) {
  5205. value += stmt.add(rule, v);
  5206. }
  5207. else {
  5208. if (typeof v == 'string') {
  5209. v = '\'' + v + '\'';
  5210. }
  5211. value += v;
  5212. }
  5213. });
  5214. }
  5215. var sqlFn = function(v) {
  5216. return sql.op.replace('?', function() {
  5217. return v;
  5218. });
  5219. };
  5220. /**
  5221. * Modifies the SQL field used by a rule
  5222. * @event changer:getSQLField
  5223. * @memberof module:plugins.SqlSupport
  5224. * @param {string} field
  5225. * @param {Rule} rule
  5226. * @returns {string}
  5227. */
  5228. var field = self.change('getSQLField', rule.field, rule);
  5229. var ruleExpression = field + ' ' + sqlFn(value);
  5230. /**
  5231. * Modifies the SQL generated for a rule
  5232. * @event changer:ruleToSQL
  5233. * @memberof module:plugins.SqlSupport
  5234. * @param {string} expression
  5235. * @param {Rule} rule
  5236. * @param {*} value
  5237. * @param {function} valueWrapper - function that takes the value and adds the operator
  5238. * @returns {string}
  5239. */
  5240. parts.push(self.change('ruleToSQL', ruleExpression, rule, value, sqlFn));
  5241. }
  5242. });
  5243. var groupExpression = parts.join(' ' + group.condition + nl);
  5244. /**
  5245. * Modifies the SQL generated for a group
  5246. * @event changer:groupToSQL
  5247. * @memberof module:plugins.SqlSupport
  5248. * @param {string} expression
  5249. * @param {Group} group
  5250. * @returns {string}
  5251. */
  5252. return self.change('groupToSQL', groupExpression, group);
  5253. }(data));
  5254. if (stmt) {
  5255. return {
  5256. sql: sql,
  5257. params: stmt.run()
  5258. };
  5259. }
  5260. else {
  5261. return {
  5262. sql: sql
  5263. };
  5264. }
  5265. },
  5266. /**
  5267. * Convert a SQL query to rules
  5268. * @param {string|module:plugins.SqlSupport.SqlQuery} query
  5269. * @param {boolean|string} stmt
  5270. * @returns {object}
  5271. * @fires module:plugins.SqlSupport.changer:parseSQLNode
  5272. * @fires module:plugins.SqlSupport.changer:getSQLFieldID
  5273. * @fires module:plugins.SqlSupport.changer:sqlToRule
  5274. * @fires module:plugins.SqlSupport.changer:sqlToGroup
  5275. * @throws MissingLibraryError, SQLParseError, UndefinedSQLOperatorError
  5276. */
  5277. getRulesFromSQL: function(query, stmt) {
  5278. if (!('SQLParser' in window)) {
  5279. Utils.error('MissingLibrary', 'SQLParser is required to parse SQL queries. Get it here https://github.com/mistic100/sql-parser');
  5280. }
  5281. var self = this;
  5282. if (typeof query == 'string') {
  5283. query = { sql: query };
  5284. }
  5285. if (stmt === true) stmt = 'question_mark';
  5286. if (typeof stmt == 'string') {
  5287. var config = getStmtConfig(stmt);
  5288. stmt = this.settings.sqlRuleStatement[config[1]](query.params, config[2]);
  5289. }
  5290. if (stmt) {
  5291. query.sql = stmt.esc(query.sql);
  5292. }
  5293. if (query.sql.toUpperCase().indexOf('SELECT') !== 0) {
  5294. query.sql = 'SELECT * FROM table WHERE ' + query.sql;
  5295. }
  5296. var parsed = SQLParser.parse(query.sql);
  5297. if (!parsed.where) {
  5298. Utils.error('SQLParse', 'No WHERE clause found');
  5299. }
  5300. /**
  5301. * 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
  5302. * @event changer:parseSQLNode
  5303. * @memberof module:plugins.SqlSupport
  5304. * @param {object} AST node
  5305. * @returns {object} tree, rule or group
  5306. */
  5307. var data = self.change('parseSQLNode', parsed.where.conditions);
  5308. // a plugin returned a group
  5309. if ('rules' in data && 'condition' in data) {
  5310. return data;
  5311. }
  5312. // a plugin returned a rule
  5313. if ('id' in data && 'operator' in data && 'value' in data) {
  5314. return {
  5315. condition: this.settings.default_condition,
  5316. rules: [data]
  5317. };
  5318. }
  5319. // create root group
  5320. var out = self.change('sqlToGroup', {
  5321. condition: this.settings.default_condition,
  5322. rules: []
  5323. }, data);
  5324. // keep track of current group
  5325. var curr = out;
  5326. (function flatten(data, i) {
  5327. if (data === null) {
  5328. return;
  5329. }
  5330. // allow plugins to manually parse or handle special cases
  5331. data = self.change('parseSQLNode', data);
  5332. // a plugin returned a group
  5333. if ('rules' in data && 'condition' in data) {
  5334. curr.rules.push(data);
  5335. return;
  5336. }
  5337. // a plugin returned a rule
  5338. if ('id' in data && 'operator' in data && 'value' in data) {
  5339. curr.rules.push(data);
  5340. return;
  5341. }
  5342. // data must be a SQL parser node
  5343. if (!('left' in data) || !('right' in data) || !('operation' in data)) {
  5344. Utils.error('SQLParse', 'Unable to parse WHERE clause');
  5345. }
  5346. // it's a node
  5347. if (['AND', 'OR'].indexOf(data.operation.toUpperCase()) !== -1) {
  5348. // create a sub-group if the condition is not the same and it's not the first level
  5349. /**
  5350. * Given an existing group and an AST node, determines if a sub-group must be created
  5351. * @event changer:sqlGroupsDistinct
  5352. * @memberof module:plugins.SqlSupport
  5353. * @param {boolean} create - true by default if the group condition is different
  5354. * @param {object} group
  5355. * @param {object} AST
  5356. * @param {int} current group level
  5357. * @returns {boolean}
  5358. */
  5359. var createGroup = self.change('sqlGroupsDistinct', i > 0 && curr.condition != data.operation.toUpperCase(), curr, data, i);
  5360. if (createGroup) {
  5361. /**
  5362. * Modifies the group generated from the SQL expression (this is called before the group is filled with rules)
  5363. * @event changer:sqlToGroup
  5364. * @memberof module:plugins.SqlSupport
  5365. * @param {object} group
  5366. * @param {object} AST
  5367. * @returns {object}
  5368. */
  5369. var group = self.change('sqlToGroup', {
  5370. condition: self.settings.default_condition,
  5371. rules: []
  5372. }, data);
  5373. curr.rules.push(group);
  5374. curr = group;
  5375. }
  5376. curr.condition = data.operation.toUpperCase();
  5377. i++;
  5378. // some magic !
  5379. var next = curr;
  5380. flatten(data.left, i);
  5381. curr = next;
  5382. flatten(data.right, i);
  5383. }
  5384. // it's a leaf
  5385. else {
  5386. if ($.isPlainObject(data.right.value)) {
  5387. Utils.error('SQLParse', 'Value format not supported for {0}.', data.left.value);
  5388. }
  5389. // convert array
  5390. var value;
  5391. if ($.isArray(data.right.value)) {
  5392. value = data.right.value.map(function(v) {
  5393. return v.value;
  5394. });
  5395. }
  5396. else {
  5397. value = data.right.value;
  5398. }
  5399. // get actual values
  5400. if (stmt) {
  5401. if ($.isArray(value)) {
  5402. value = value.map(stmt.parse);
  5403. }
  5404. else {
  5405. value = stmt.parse(value);
  5406. }
  5407. }
  5408. // convert operator
  5409. var operator = data.operation.toUpperCase();
  5410. if (operator == '<>') {
  5411. operator = '!=';
  5412. }
  5413. var sqlrl = self.settings.sqlRuleOperator[operator];
  5414. if (sqlrl === undefined) {
  5415. Utils.error('UndefinedSQLOperator', 'Invalid SQL operation "{0}".', data.operation);
  5416. }
  5417. var opVal = sqlrl.call(this, value, data.operation);
  5418. // find field name
  5419. var field;
  5420. if ('values' in data.left) {
  5421. field = data.left.values.join('.');
  5422. }
  5423. else if ('value' in data.left) {
  5424. field = data.left.value;
  5425. }
  5426. else {
  5427. Utils.error('SQLParse', 'Cannot find field name in {0}', JSON.stringify(data.left));
  5428. }
  5429. var id = self.getSQLFieldID(field, value);
  5430. /**
  5431. * Modifies the rule generated from the SQL expression
  5432. * @event changer:sqlToRule
  5433. * @memberof module:plugins.SqlSupport
  5434. * @param {object} rule
  5435. * @param {object} AST
  5436. * @returns {object}
  5437. */
  5438. var rule = self.change('sqlToRule', {
  5439. id: id,
  5440. field: field,
  5441. operator: opVal.op,
  5442. value: opVal.val
  5443. }, data);
  5444. curr.rules.push(rule);
  5445. }
  5446. }(data, 0));
  5447. return out;
  5448. },
  5449. /**
  5450. * Sets the builder's rules from a SQL query
  5451. * @see module:plugins.SqlSupport.getRulesFromSQL
  5452. */
  5453. setRulesFromSQL: function(query, stmt) {
  5454. this.setRules(this.getRulesFromSQL(query, stmt));
  5455. },
  5456. /**
  5457. * Returns a filter identifier from the SQL field.
  5458. * Automatically use the only one filter with a matching field, fires a changer otherwise.
  5459. * @param {string} field
  5460. * @param {*} value
  5461. * @fires module:plugins.SqlSupport:changer:getSQLFieldID
  5462. * @returns {string}
  5463. * @private
  5464. */
  5465. getSQLFieldID: function(field, value) {
  5466. var matchingFilters = this.filters.filter(function(filter) {
  5467. return filter.field.toLowerCase() === field.toLowerCase();
  5468. });
  5469. var id;
  5470. if (matchingFilters.length === 1) {
  5471. id = matchingFilters[0].id;
  5472. }
  5473. else {
  5474. /**
  5475. * Returns a filter identifier from the SQL field
  5476. * @event changer:getSQLFieldID
  5477. * @memberof module:plugins.SqlSupport
  5478. * @param {string} field
  5479. * @param {*} value
  5480. * @returns {string}
  5481. */
  5482. id = this.change('getSQLFieldID', field, value);
  5483. }
  5484. return id;
  5485. }
  5486. });
  5487. /**
  5488. * Parses the statement configuration
  5489. * @memberof module:plugins.SqlSupport
  5490. * @param {string} stmt
  5491. * @returns {Array} null, mode, option
  5492. * @private
  5493. */
  5494. function getStmtConfig(stmt) {
  5495. var config = stmt.match(/(question_mark|numbered|named)(?:\((.)\))?/);
  5496. if (!config) config = [null, 'question_mark', undefined];
  5497. return config;
  5498. }
  5499. /**
  5500. * @class UniqueFilter
  5501. * @memberof module:plugins
  5502. * @description Allows to define some filters as "unique": ie which can be used for only one rule, globally or in the same group.
  5503. */
  5504. QueryBuilder.define('unique-filter', function() {
  5505. this.status.used_filters = {};
  5506. this.on('afterUpdateRuleFilter', this.updateDisabledFilters);
  5507. this.on('afterDeleteRule', this.updateDisabledFilters);
  5508. this.on('afterCreateRuleFilters', this.applyDisabledFilters);
  5509. this.on('afterReset', this.clearDisabledFilters);
  5510. this.on('afterClear', this.clearDisabledFilters);
  5511. // Ensure that the default filter is not already used if unique
  5512. this.on('getDefaultFilter.filter', function(e, model) {
  5513. var self = e.builder;
  5514. self.updateDisabledFilters();
  5515. if (e.value.id in self.status.used_filters) {
  5516. var found = self.filters.some(function(filter) {
  5517. 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) {
  5518. e.value = filter;
  5519. return true;
  5520. }
  5521. });
  5522. if (!found) {
  5523. Utils.error(false, 'UniqueFilter', 'No more non-unique filters available');
  5524. e.value = undefined;
  5525. }
  5526. }
  5527. });
  5528. });
  5529. QueryBuilder.extend(/** @lends module:plugins.UniqueFilter.prototype */ {
  5530. /**
  5531. * Updates the list of used filters
  5532. * @param {$.Event} [e]
  5533. * @private
  5534. */
  5535. updateDisabledFilters: function(e) {
  5536. var self = e ? e.builder : this;
  5537. self.status.used_filters = {};
  5538. if (!self.model) {
  5539. return;
  5540. }
  5541. // get used filters
  5542. (function walk(group) {
  5543. group.each(function(rule) {
  5544. if (rule.filter && rule.filter.unique) {
  5545. if (!self.status.used_filters[rule.filter.id]) {
  5546. self.status.used_filters[rule.filter.id] = [];
  5547. }
  5548. if (rule.filter.unique == 'group') {
  5549. self.status.used_filters[rule.filter.id].push(rule.parent);
  5550. }
  5551. }
  5552. }, function(group) {
  5553. walk(group);
  5554. });
  5555. }(self.model.root));
  5556. self.applyDisabledFilters(e);
  5557. },
  5558. /**
  5559. * Clear the list of used filters
  5560. * @param {$.Event} [e]
  5561. * @private
  5562. */
  5563. clearDisabledFilters: function(e) {
  5564. var self = e ? e.builder : this;
  5565. self.status.used_filters = {};
  5566. self.applyDisabledFilters(e);
  5567. },
  5568. /**
  5569. * Disabled filters depending on the list of used ones
  5570. * @param {$.Event} [e]
  5571. * @private
  5572. */
  5573. applyDisabledFilters: function(e) {
  5574. var self = e ? e.builder : this;
  5575. // re-enable everything
  5576. self.$el.find(QueryBuilder.selectors.filter_container + ' option').prop('disabled', false);
  5577. // disable some
  5578. $.each(self.status.used_filters, function(filterId, groups) {
  5579. if (groups.length === 0) {
  5580. self.$el.find(QueryBuilder.selectors.filter_container + ' option[value="' + filterId + '"]:not(:selected)').prop('disabled', true);
  5581. }
  5582. else {
  5583. groups.forEach(function(group) {
  5584. group.each(function(rule) {
  5585. rule.$el.find(QueryBuilder.selectors.filter_container + ' option[value="' + filterId + '"]:not(:selected)').prop('disabled', true);
  5586. });
  5587. });
  5588. }
  5589. });
  5590. // update Selectpicker
  5591. if (self.settings.plugins && self.settings.plugins['bt-selectpicker']) {
  5592. self.$el.find(QueryBuilder.selectors.rule_filter).selectpicker('render');
  5593. }
  5594. }
  5595. });
  5596. /*!
  5597. * jQuery QueryBuilder 2.5.2
  5598. * Locale: English (en)
  5599. * Author: Damien "Mistic" Sorel, http://www.strangeplanet.fr
  5600. * Licensed under MIT (https://opensource.org/licenses/MIT)
  5601. */
  5602. QueryBuilder.regional['en'] = {
  5603. "__locale": "English (en)",
  5604. "__author": "Damien \"Mistic\" Sorel, http://www.strangeplanet.fr",
  5605. "add_rule": "Add rule",
  5606. "add_group": "Add group",
  5607. "delete_rule": "Delete",
  5608. "delete_group": "Delete",
  5609. "conditions": {
  5610. "AND": "AND",
  5611. "OR": "OR"
  5612. },
  5613. "operators": {
  5614. "equal": "equal",
  5615. "not_equal": "not equal",
  5616. "in": "in",
  5617. "not_in": "not in",
  5618. "less": "less",
  5619. "less_or_equal": "less or equal",
  5620. "greater": "greater",
  5621. "greater_or_equal": "greater or equal",
  5622. "between": "between",
  5623. "not_between": "not between",
  5624. "begins_with": "begins with",
  5625. "not_begins_with": "doesn't begin with",
  5626. "contains": "contains",
  5627. "not_contains": "doesn't contain",
  5628. "ends_with": "ends with",
  5629. "not_ends_with": "doesn't end with",
  5630. "is_empty": "is empty",
  5631. "is_not_empty": "is not empty",
  5632. "is_null": "is null",
  5633. "is_not_null": "is not null"
  5634. },
  5635. "errors": {
  5636. "no_filter": "No filter selected",
  5637. "empty_group": "The group is empty",
  5638. "radio_empty": "No value selected",
  5639. "checkbox_empty": "No value selected",
  5640. "select_empty": "No value selected",
  5641. "string_empty": "Empty value",
  5642. "string_exceed_min_length": "Must contain at least {0} characters",
  5643. "string_exceed_max_length": "Must not contain more than {0} characters",
  5644. "string_invalid_format": "Invalid format ({0})",
  5645. "number_nan": "Not a number",
  5646. "number_not_integer": "Not an integer",
  5647. "number_not_double": "Not a real number",
  5648. "number_exceed_min": "Must be greater than {0}",
  5649. "number_exceed_max": "Must be lower than {0}",
  5650. "number_wrong_step": "Must be a multiple of {0}",
  5651. "number_between_invalid": "Invalid values, {0} is greater than {1}",
  5652. "datetime_empty": "Empty value",
  5653. "datetime_invalid": "Invalid date format ({0})",
  5654. "datetime_exceed_min": "Must be after {0}",
  5655. "datetime_exceed_max": "Must be before {0}",
  5656. "datetime_between_invalid": "Invalid values, {0} is greater than {1}",
  5657. "boolean_not_valid": "Not a boolean",
  5658. "operator_not_multiple": "Operator \"{1}\" cannot accept multiple values"
  5659. },
  5660. "invert": "Invert",
  5661. "NOT": "NOT"
  5662. };
  5663. QueryBuilder.defaults({ lang_code: 'en' });
  5664. return QueryBuilder;
  5665. }));