1313use ipl \Validator \CallbackValidator ;
1414use ipl \Web \Common \FormUid ;
1515use ipl \Web \Control \SearchBar \Terms ;
16+ use ipl \Web \Control \SearchBar \ValidatedColumn ;
17+ use ipl \Web \Control \SearchBar \ValidatedOperator ;
18+ use ipl \Web \Control \SearchBar \ValidatedValue ;
1619use ipl \Web \Filter \ParseException ;
1720use ipl \Web \Filter \QueryString ;
1821use ipl \Web \Url ;
@@ -22,8 +25,17 @@ class SearchBar extends Form
2225{
2326 use FormUid;
2427
25- /** @var string Emitted in case of an auto submit */
26- const ON_CHANGE = 'on_change ' ;
28+ /** @var string Emitted in case the user added a new condition */
29+ const ON_ADD = 'on_add ' ;
30+
31+ /** @var string Emitted in case the user inserted a new condition */
32+ const ON_INSERT = 'on_insert ' ;
33+
34+ /** @var string Emitted in case the user changed an existing condition */
35+ const ON_SAVE = 'on_save ' ;
36+
37+ /** @var string Emitted in case the user removed a condition */
38+ const ON_REMOVE = 'on_remove ' ;
2739
2840 protected $ defaultAttributes = [
2941 'data-enrichment-type ' => 'search-bar ' ,
@@ -204,13 +216,49 @@ private function protectId($id)
204216
205217 public function isValidEvent ($ event )
206218 {
207- if ($ event !== self ::ON_CHANGE ) {
208- return parent ::isValidEvent ($ event );
219+ switch ($ event ) {
220+ case self ::ON_ADD :
221+ case self ::ON_SAVE :
222+ case self ::ON_INSERT :
223+ case self ::ON_REMOVE :
224+ return true ;
225+ default :
226+ return parent ::isValidEvent ($ event );
227+ }
228+ }
229+
230+ private function validateCondition ($ eventType , $ indices , $ termsData , &$ changes )
231+ {
232+ // TODO: In case of the query string validation, all three are guaranteed to be set.
233+ // The Parser also provides defaults, why shouldn't we here?
234+ $ column = ValidatedColumn::fromTermData ($ termsData [0 ]);
235+ $ operator = isset ($ termsData [1 ])
236+ ? ValidatedOperator::fromTermData ($ termsData [1 ])
237+ : null ;
238+ $ value = isset ($ termsData [2 ])
239+ ? ValidatedValue::fromTermData ($ termsData [2 ])
240+ : null ;
241+
242+ $ this ->emit ($ eventType , [$ column , $ operator , $ value ]);
243+
244+ if ($ eventType !== self ::ON_REMOVE ) {
245+ if (! $ column ->isValid () || $ column ->hasBeenChanged ()) {
246+ $ changes [$ indices [0 ]] = array_merge ($ termsData [0 ], $ column ->toTermData ());
247+ }
248+
249+ if ($ operator && ! $ operator ->isValid ()) {
250+ $ changes [$ indices [1 ]] = array_merge ($ termsData [1 ], $ operator ->toTermData ());
251+ }
252+
253+ if ($ value && (! $ value ->isValid () || $ value ->hasBeenChanged ())) {
254+ $ changes [$ indices [2 ]] = array_merge ($ termsData [2 ], $ value ->toTermData ());
255+ }
209256 }
210257
211- return true ;
258+ return $ column -> isValid () && (! $ operator || $ operator -> isValid ()) && (! $ value || $ value -> isValid ()) ;
212259 }
213260
261+
214262 protected function assemble ()
215263 {
216264 $ termContainerId = $ this ->protectId ('terms ' );
@@ -243,32 +291,72 @@ protected function assemble()
243291 return true ;
244292 }
245293
246- $ validatedData = $ data ;
247- // TODO: I'd like to not pass a ref here, but the Events trait does not support event return values
248- $ this ->emit (self ::ON_CHANGE , [&$ validatedData ]);
294+ switch ($ data ['type ' ]) {
295+ case 'add ' :
296+ case 'exchange ' :
297+ $ type = self ::ON_ADD ;
298+
299+ break ;
300+ case 'insert ' :
301+ $ type = self ::ON_INSERT ;
302+
303+ break ;
304+ case 'save ' :
305+ $ type = self ::ON_SAVE ;
306+
307+ break ;
308+ case 'remove ' :
309+ $ type = self ::ON_REMOVE ;
310+
311+ break ;
312+ default :
313+ return true ;
314+ }
249315
250316 $ changes = [];
251- foreach (isset ($ validatedData ['terms ' ]) ? $ validatedData ['terms ' ] : [] as $ termIndex => $ termData ) {
252- if ($ termData !== $ data ['terms ' ][$ termIndex ]) {
253- if (isset ($ termData ['invalidMsg ' ]) && ! isset ($ termData ['pattern ' ])) {
254- $ termData ['pattern ' ] = sprintf (
255- '^\s*(?!%s\b).*\s*$ ' ,
256- $ data ['terms ' ][$ termIndex ]['label ' ]
257- );
258- }
317+ $ invalid = false ;
318+ $ indices = [null , null , null ];
319+ $ termsData = [null , null , null ];
320+ foreach (isset ($ data ['terms ' ]) ? $ data ['terms ' ] : [] as $ termIndex => $ termData ) {
321+ switch ($ termData ['type ' ]) {
322+ case 'column ' :
323+ $ indices [0 ] = $ termIndex ;
324+ $ termsData [0 ] = $ termData ;
325+
326+ break ;
327+ case 'operator ' :
328+ $ indices [1 ] = $ termIndex ;
329+ $ termsData [1 ] = $ termData ;
330+
331+ break ;
332+ case 'value ' :
333+ $ indices [2 ] = $ termIndex ;
334+ $ termsData [2 ] = $ termData ;
335+
336+ break ;
337+ default :
338+ if ($ termsData [0 ] !== null ) {
339+ if (! $ this ->validateCondition ($ type , $ indices , $ termsData , $ changes )) {
340+ $ invalid = true ;
341+ }
342+ }
343+
344+ $ indices = $ termsData = [null , null , null ];
345+ }
346+ }
259347
260- $ changes [$ termIndex ] = $ termData ;
348+ if ($ termsData [0 ] !== null ) {
349+ if (! $ this ->validateCondition ($ type , $ indices , $ termsData , $ changes )) {
350+ $ invalid = true ;
261351 }
262352 }
263353
264354 if (! empty ($ changes )) {
265355 $ this ->changes = ['# ' . $ searchInputId , $ changes ];
266356 $ termContainer ->applyChanges ($ changes );
267- // TODO: Not every change must be invalid, change this once there are Event objects
268- return false ;
269357 }
270358
271- return true ;
359+ return ! $ invalid ;
272360 })
273361 ]
274362 ]);
@@ -291,8 +379,42 @@ protected function assemble()
291379 'data-choose-column ' => t ('Please enter a valid column. ' ),
292380 'validators ' => [
293381 new CallbackValidator (function ($ q , CallbackValidator $ validator ) {
382+ $ invalid = false ;
383+
384+ $ parser = QueryString::fromString ($ q );
385+ $ parser ->on (QueryString::ON_CONDITION , function (Filter \Condition $ condition ) use (&$ invalid ) {
386+ $ columnIndex = $ condition ->metaData ()->get ('columnIndex ' );
387+ if (isset ($ this ->changes [1 ][$ columnIndex ])) {
388+ $ change = $ this ->changes [1 ][$ columnIndex ];
389+ $ condition ->setColumn ($ change ['search ' ]);
390+ } elseif (empty ($ this ->changes )) {
391+ $ column = ValidatedColumn::fromFilterCondition ($ condition );
392+ $ operator = ValidatedOperator::fromFilterCondition ($ condition );
393+ $ value = ValidatedValue::fromFilterCondition ($ condition );
394+ $ this ->emit (self ::ON_ADD , [$ column , $ operator , $ value ]);
395+
396+ $ condition ->setColumn ($ column ->getSearchValue ());
397+ $ condition ->setValue ($ value ->getSearchValue ());
398+
399+ if (! $ column ->isValid ()) {
400+ $ invalid = true ;
401+ $ condition ->metaData ()->merge ($ column ->toMetaData ());
402+ }
403+
404+ if (! $ operator ->isValid ()) {
405+ $ invalid = true ;
406+ $ condition ->metaData ()->merge ($ operator ->toMetaData ());
407+ }
408+
409+ if (! $ value ->isValid ()) {
410+ $ invalid = true ;
411+ $ condition ->metaData ()->merge ($ value ->toMetaData ());
412+ }
413+ }
414+ });
415+
294416 try {
295- $ filter = QueryString:: parse ($ q );
417+ $ filter = $ parser -> parse ();
296418 } catch (ParseException $ e ) {
297419 $ charAt = $ e ->getCharPos () - 1 ;
298420 $ char = $ e ->getChar ();
@@ -301,7 +423,8 @@ protected function assemble()
301423 ->setValue (substr ($ q , $ charAt ))
302424 ->addAttributes ([
303425 'title ' => sprintf (t ('Unexpected %s at start of input ' ), $ char ),
304- 'pattern ' => sprintf ('^(?!%s).* ' , $ char === ') ' ? '\) ' : $ char )
426+ 'pattern ' => sprintf ('^(?!%s).* ' , $ char === ') ' ? '\) ' : $ char ),
427+ 'data-has-syntax-error ' => true
305428 ]);
306429
307430 $ probablyValidQueryString = substr ($ q , 0 , $ charAt );
@@ -311,7 +434,8 @@ protected function assemble()
311434
312435 $ this ->getElement ($ this ->getSearchParameter ())->setValue ('' );
313436 $ this ->setFilter ($ filter );
314- return true ;
437+
438+ return ! $ invalid ;
315439 })
316440 ]
317441 ]);
0 commit comments