Built files from Bizgaze WebServer
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

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. }));