Skip to content

Commit 5ce928a

Browse files
sukhwinder33445nilmerg
authored andcommitted
Add class SimpleSearchField and simpleSuggestions
1 parent 20a7a22 commit 5ce928a

File tree

2 files changed

+366
-0
lines changed

2 files changed

+366
-0
lines changed

src/Control/SimpleSearchField.php

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
<?php
2+
3+
namespace ipl\Web\Control;
4+
5+
use ipl\Html\Attributes;
6+
use ipl\Html\Form;
7+
use ipl\Html\FormElement\InputElement;
8+
use ipl\Html\FormElement\SubmitElement;
9+
use ipl\Html\HtmlElement;
10+
use ipl\Web\Url;
11+
use ipl\Web\Widget\Icon;
12+
13+
use function ipl\I18n\t;
14+
15+
/**
16+
* Form for simple value based search
17+
*/
18+
class SimpleSearchField extends Form
19+
{
20+
protected $defaultAttributes = [
21+
'class' => 'search-field',
22+
'name' => 'search-field',
23+
'role' => 'search'
24+
];
25+
26+
/** @var string The term separator */
27+
public const TERM_SEPARATOR = ',';
28+
29+
/** @var string The default search parameter */
30+
public const DEFAULT_SEARCH_PARAM = 'q';
31+
32+
/** @var string The search parameter */
33+
protected $searchParameter;
34+
35+
/** @var Url The suggestion url */
36+
protected $suggestionUrl;
37+
38+
/** @var string Submit label */
39+
protected $submitLabel;
40+
41+
/**
42+
* Set the search parameter
43+
*
44+
* @param string $name
45+
*
46+
* @return $this
47+
*/
48+
public function setSearchParameter(string $name): self
49+
{
50+
$this->searchParameter = $name;
51+
52+
return $this;
53+
}
54+
55+
/**
56+
* Get the search parameter
57+
*
58+
* @return string
59+
*/
60+
public function getSearchParameter(): string
61+
{
62+
return $this->searchParameter ?: self::DEFAULT_SEARCH_PARAM;
63+
}
64+
65+
/**
66+
* Set the suggestion url
67+
*
68+
* @param Url $url
69+
*
70+
* @return $this
71+
*/
72+
public function setSuggestionUrl(Url $url): self
73+
{
74+
$this->suggestionUrl = $url;
75+
76+
return $this;
77+
}
78+
79+
/**
80+
* Get the suggestion url
81+
*
82+
* @return Url
83+
*/
84+
public function getSuggestionUrl(): Url
85+
{
86+
return $this->suggestionUrl;
87+
}
88+
89+
/**
90+
* Set submit label
91+
*
92+
* @param string $label
93+
*
94+
* @return $this
95+
*/
96+
public function setSubmitLabel(string $label): self
97+
{
98+
$this->submitLabel = $label;
99+
100+
return $this;
101+
}
102+
103+
/**
104+
* Get submit label
105+
*
106+
* @return string
107+
*/
108+
public function getSubmitLabel(): string
109+
{
110+
return $this->submitLabel ?? t('Submit');
111+
}
112+
113+
public function assemble()
114+
{
115+
$filterInput = new InputElement(null, [
116+
'type' => 'text',
117+
'placeholder' => t('Type to search'),
118+
'class' => 'search-field',
119+
'id' => 'search-filed',
120+
'autocomplete' => 'off',
121+
'required' => true,
122+
'data-no-auto-submit' => true,
123+
'data-no-js-placeholder' => true,
124+
'data-enrichment-type' => 'terms',
125+
'data-term-separator' => self::TERM_SEPARATOR,
126+
'data-term-mode' => 'read-only',
127+
'data-term-direction' => 'vertical',
128+
'data-data-input' => '#data-input',
129+
'data-term-input' => '#term-input',
130+
'data-term-container' => '#term-container',
131+
'data-term-suggestions' => '#term-suggestions',
132+
'data-suggest-url' => $this->getSuggestionUrl()
133+
]);
134+
135+
$dataInput = new InputElement('data', ['type' => 'hidden', 'id' => 'data-input']);
136+
137+
$termInput = new InputElement($this->getSearchParameter(), ['type' => 'hidden', 'id' => 'term-input']);
138+
$this->registerElement($termInput);
139+
140+
$termContainer = new HtmlElement(
141+
'div',
142+
Attributes::create(['id' => 'term-container', 'class' => 'term-container'])
143+
);
144+
145+
$termSuggestions = new HtmlElement(
146+
'div',
147+
Attributes::create(['id' => 'term-suggestions', 'class' => 'search-suggestions'])
148+
);
149+
150+
$submitButton = new SubmitElement('submit', ['label' => $this->getSubmitLabel()]);
151+
152+
$this->registerElement($submitButton);
153+
154+
$this->add([
155+
HtmlElement::create(
156+
'div',
157+
null,
158+
[
159+
new Icon('search', ['class' => 'search-icon']),
160+
$filterInput,
161+
$termSuggestions,
162+
$dataInput,
163+
$termInput,
164+
$submitButton
165+
]
166+
),
167+
$termContainer
168+
]);
169+
}
170+
}

src/Control/SimpleSuggestions.php

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
<?php
2+
3+
namespace ipl\Web\Control;
4+
5+
use ArrayIterator;
6+
use ipl\Html\Attributes;
7+
use ipl\Html\BaseHtmlElement;
8+
use ipl\Html\FormattedString;
9+
use ipl\Html\FormElement\ButtonElement;
10+
use ipl\Html\FormElement\InputElement;
11+
use ipl\Html\HtmlElement;
12+
use ipl\Html\Text;
13+
use IteratorIterator;
14+
use LimitIterator;
15+
use Psr\Http\Message\ServerRequestInterface;
16+
17+
abstract class SimpleSuggestions extends BaseHtmlElement
18+
{
19+
/** @var int Suggestions limit */
20+
public const DEFAULT_LIMIT = 10;
21+
22+
/** @var string Class name for suggestion title */
23+
public const SUGGESTION_TITLE_CLASS = 'suggestion-title';
24+
25+
protected $tag = 'ul';
26+
27+
/** @var string The given input for search */
28+
protected $searchTerm;
29+
30+
/** @var mixed Fetched data for given input */
31+
protected $data;
32+
33+
/** @var string Default first suggestion in the suggestion list */
34+
protected $default;
35+
36+
/**
37+
* Set the search term
38+
*
39+
* @param string $searchTerm
40+
*
41+
* @return $this
42+
*/
43+
public function setSearchTerm(string $searchTerm): self
44+
{
45+
$this->searchTerm = $searchTerm;
46+
47+
return $this;
48+
}
49+
50+
/**
51+
* Set the fetched data
52+
*
53+
* @param mixed $data
54+
*
55+
* @return $this
56+
*/
57+
public function setData($data): self
58+
{
59+
$this->data = $data;
60+
61+
return $this;
62+
}
63+
64+
/**
65+
* Set the default suggestion
66+
*
67+
* @param string $default
68+
*
69+
* @return $this
70+
*/
71+
public function setDefault(string $default): self
72+
{
73+
$this->default = trim($default, '"\'');
74+
75+
return $this;
76+
}
77+
78+
/**
79+
* Fetch suggestions according to the input in the search field
80+
*
81+
* @param string $searchTerm The given input in the search field
82+
* @param array $exclude Already added terms to be excluded from the suggestion list
83+
*
84+
* @return mixed
85+
*/
86+
abstract protected function fetchSuggestions(string $searchTerm, array $exclude = []);
87+
88+
protected function assembleDefault(): void
89+
{
90+
if ($this->default === null) {
91+
return;
92+
}
93+
94+
$attributes = [
95+
'type' => 'button',
96+
'tabindex' => -1,
97+
'data-label' => $this->default,
98+
'value' => $this->default,
99+
];
100+
101+
$button = new ButtonElement(null, $attributes);
102+
$button->addHtml(FormattedString::create(
103+
t('Add %s'),
104+
new HtmlElement('em', null, Text::create($this->default))
105+
));
106+
107+
$this->prependHtml(new HtmlElement('li', Attributes::create(['class' => 'default']), $button));
108+
}
109+
110+
protected function assemble()
111+
{
112+
if ($this->data === null) {
113+
$data = [];
114+
} else {
115+
$data = $this->data;
116+
if (is_array($data)) {
117+
$data = new ArrayIterator($data);
118+
}
119+
120+
$data = new LimitIterator(new IteratorIterator($data), 0, self::DEFAULT_LIMIT);
121+
}
122+
123+
foreach ($data as $term => $label) {
124+
if (is_int($term)) {
125+
$term = $label;
126+
}
127+
128+
$attributes = [
129+
'type' => 'button',
130+
'tabindex' => -1,
131+
'data-search' => $term
132+
];
133+
134+
$attributes['value'] = $label;
135+
$attributes['data-label'] = $label;
136+
137+
$this->addHtml(new HtmlElement('li', null, new InputElement(null, $attributes)));
138+
}
139+
140+
$showDefault = true;
141+
if ($this->searchTerm && $this->count() === 1) {
142+
// The default option is not shown if the user's input result in an exact match
143+
$input = $this->getFirst('li')->getFirst('input');
144+
$showDefault = $input->getValue() != $this->searchTerm
145+
&& $input->getAttributes()->get('data-search')->getValue() != $this->searchTerm;
146+
}
147+
148+
if ($showDefault) {
149+
$this->assembleDefault();
150+
}
151+
}
152+
153+
/**
154+
* Load suggestions as requested by the client
155+
*
156+
* @param ServerRequestInterface $request
157+
*
158+
* @return $this
159+
*/
160+
public function forRequest(ServerRequestInterface $request): self
161+
{
162+
if ($request->getMethod() !== 'POST') {
163+
return $this;
164+
}
165+
166+
$requestData = json_decode($request->getBody()->read(8192), true);
167+
if (empty($requestData)) {
168+
return $this;
169+
}
170+
171+
$search = $requestData['term']['search'];
172+
$label = $requestData['term']['label'];
173+
$exclude = $requestData['exclude'];
174+
175+
$this->setSearchTerm($search);
176+
177+
$this->setData($this->fetchSuggestions($label, $exclude));
178+
179+
if (! empty($search)) {
180+
$this->setDefault($search);
181+
}
182+
183+
return $this;
184+
}
185+
186+
public function renderUnwrapped()
187+
{
188+
$this->ensureAssembled();
189+
190+
if ($this->isEmpty()) {
191+
return '';
192+
}
193+
194+
return parent::renderUnwrapped();
195+
}
196+
}

0 commit comments

Comments
 (0)