TomSelect::Field()   B
last analyzed

Complexity

Conditions 7
Paths 32

Size

Total Lines 38
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
eloc 19
c 1
b 0
f 0
nc 32
nop 1
dl 0
loc 38
rs 8.8333
1
<?php
2
3
namespace LeKoala\FormElements;
4
5
use SilverStripe\ORM\DataObject;
6
use SilverStripe\View\Requirements;
7
8
/**
9
 * Implements AjaxPoweredField + TagsField
10
 */
11
trait TomSelect
12
{
13
    use BaseElement;
14
    use Localize;
15
    use Autocompleter;
16
17
    /**
18
     * @var boolean
19
     */
20
    protected $multiple = false;
21
22
    /**
23
     * Callback to create tags
24
     * See docs/on-new-tag.md for sample code
25
     *
26
     * @var Callable
27
     */
28
    protected $onNewTag = null;
29
30
    /**
31
     * @config
32
     *
33
     * @var boolean
34
     */
35
    private static $enable_requirements = true;
36
37
    /**
38
     * @link https://github.com/orchidjs/tom-select/blob/master/src/defaults.ts
39
     * @config
40
     * @var array
41
     */
42
    private static $default_config = [
43
        "valueField" => "id",
44
        "labelField" => "text",
45
        "searchField" => ["text"]
46
    ];
47
48
    public function __construct($name, $title = null, $source = [], $value = null)
49
    {
50
        parent::__construct($name, $title, $source, $value);
51
        $this->setAllowClear(true);
52
        $this->mergeDefaultConfig();
53
    }
54
55
    public function Type()
56
    {
57
        return 'tomselect';
58
    }
59
60
    public function extraClass()
61
    {
62
        return 'no-chosen ' . parent::extraClass();
63
    }
64
65
    public function getServerVars()
66
    {
67
        return [
68
            'queryParam' => 'q',
69
            'dataKey' => 'data',
70
            'valueField' => 'id',
71
            'labelField' => 'text',
72
        ];
73
    }
74
75
    public function setValue($value, $data = null)
76
    {
77
        if ($data instanceof DataObject) {
78
            $this->loadFromDataObject($data);
79
        } else {
80
            // For ajax, we need to add the option to the list
81
            $class = $this->getAjaxClass();
82
            if ($value && $class) {
83
                $arr = $value;
84
                if (!is_array($arr)) {
85
                    $arr = [$value];
86
                }
87
                foreach ($arr as $id) {
88
                    $record = DataObject::get_by_id($class, $id);
89
                    $this->addRecordToSource($record);
90
                }
91
            }
92
93
            $this->value = $value;
0 ignored issues
show
Bug Best Practice introduced by
The property value does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
94
        }
95
        return $this;
96
    }
97
98
    public function setSubmittedValue($value, $data = null)
99
    {
100
        return $this->setValue($value, $data);
101
    }
102
103
    /**
104
     * Check if the given value is disabled
105
     * Override base function otherwise it returns false when field is disabled
106
     *
107
     * @param string $value
108
     * @return bool
109
     */
110
    protected function isDisabledValue($value)
111
    {
112
        return in_array($value, $this->getDisabledItems() ?? []);
0 ignored issues
show
Bug introduced by
It seems like getDisabledItems() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

112
        return in_array($value, $this->/** @scrutinizer ignore-call */ getDisabledItems() ?? []);
Loading history...
113
    }
114
115
116
    public function performReadonlyTransformation()
117
    {
118
        $field = $this->castedCopy(get_class($this));
0 ignored issues
show
Bug introduced by
It seems like castedCopy() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

118
        /** @scrutinizer ignore-call */ 
119
        $field = $this->castedCopy(get_class($this));
Loading history...
119
        if ($this->getForm()) {
0 ignored issues
show
Bug introduced by
It seems like getForm() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

119
        if ($this->/** @scrutinizer ignore-call */ getForm()) {
Loading history...
120
            $field->setForm($this->getForm());
121
        }
122
        $field->setDisabled(true);
123
        $field->setSource($this->getSource());
0 ignored issues
show
Bug introduced by
It seems like getSource() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

123
        $field->setSource($this->/** @scrutinizer ignore-call */ getSource());
Loading history...
124
        $field->setReadonly(true);
125
        // Required to properly set value if no source set
126
        if ($this->ajaxClass) {
127
            $field->setAjaxClass($this->getAjaxClass());
128
        }
129
        return $field;
130
    }
131
132
    public function getPlugin($plugin)
133
    {
134
        if (isset($this->config['plugins'][$plugin])) {
135
            return $this->config['plugins'][$plugin];
136
        }
137
    }
138
139
    public function removePlugin($plugin)
140
    {
141
        if (isset($this->config['plugins'][$plugin])) {
142
            unset($this->config['plugins'][$plugin]);
143
        }
144
        return $this;
145
    }
146
147
    public function setPlugin($plugin, $config = [])
148
    {
149
        $plugins = $this->config['plugins'] ?? [];
150
        if (empty($plugins)) {
151
            $this->config['plugins'] = $plugins;
152
        }
153
        $pluginConfig = $plugins[$plugin] ?? [];
154
        $this->config['plugins'][$plugin] = array_merge($pluginConfig, $config);
155
        return $this;
156
    }
157
158
    public function getTags()
159
    {
160
        return $this->getConfig('create');
161
    }
162
163
    public function setTags($value, $blur = true)
164
    {
165
        if ($value) {
166
            $this->setConfig('createOnBlur', $blur);
167
        }
168
        return $this->setConfig('create', $value);
169
    }
170
171
    public function getPlaceholder()
172
    {
173
        return $this->getConfig('placeholder');
174
    }
175
176
    public function setPlaceholder($value)
177
    {
178
        return $this->setConfig('placeholder', $value);
179
    }
180
181
    public function getAllowClear()
182
    {
183
        return $this->getConfig('remove_button');
184
    }
185
186
    public function setAllowClear($value)
187
    {
188
        // @link https://tom-select.js.org/plugins/remove-button/
189
        if ($value) {
190
            return $this->setPlugin('remove_button', ['title' => _t('TomSelect.Remove', 'Remove')]);
191
        } else {
192
            return $this->removePlugin('remove_button');
193
        }
194
    }
195
196
    public function getTokenSeparators()
197
    {
198
        return $this->getConfig('delimiter');
199
    }
200
201
    public function setTokenSeparator($value)
202
    {
203
        return $this->setConfig('delimiter', $value);
204
    }
205
206
    public function setAjaxLoad($callbackName, $valueField = "id", $labelField = "text", $searchField = "text")
207
    {
208
        $this->setConfig('load', $callbackName);
209
        $this->setConfig('valueField', $valueField);
210
        $this->setConfig('labelField', $labelField);
211
        $this->setConfig('searchField', $searchField);
212
        return $this;
213
    }
214
215
    public function getAjax()
216
    {
217
        return $this->getConfig('_ajax');
218
    }
219
220
    public function setAjax($url, $opts = [])
221
    {
222
        $ajax = array_merge([
223
            'url' => $url,
224
            'paramName' => "q",
225
            'params' => [
226
                'SecurityID' => $this->getForm()->getSecurityToken()->getValue()
227
            ]
228
        ], $opts);
229
        return $this->setConfig('_ajax', $ajax);
230
    }
231
232
    /**
233
     * @return boolean
234
     */
235
    public function isAjax()
236
    {
237
        return $this->ajaxClass || $this->getConfig('_ajax');
238
    }
239
240
    /**
241
     * @return Callable
242
     */
243
    public function getOnNewTag()
244
    {
245
        return $this->onNewTag;
246
    }
247
248
    /**
249
     * The callback should return the new id
250
     *
251
     * @param Callable $locale
252
     * @return $this
253
     */
254
    public function setOnNewTag($callback)
255
    {
256
        $this->onNewTag = $callback;
257
        return $this;
258
    }
259
260
    public function Field($properties = array())
261
    {
262
        // Set lang based on locale
263
        $lang = substr($this->getLocale(), 0, 2);
264
        if ($lang != 'en') {
265
            $this->setConfig('language', $lang);
266
        }
267
268
        if ($this->isDisabled() || $this->isReadonly()) {
0 ignored issues
show
Bug introduced by
It seems like isReadonly() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

268
        if ($this->isDisabled() || $this->/** @scrutinizer ignore-call */ isReadonly()) {
Loading history...
Bug introduced by
The method isDisabled() does not exist on LeKoala\FormElements\TomSelect. Did you maybe mean isDisabledValue()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

268
        if ($this->/** @scrutinizer ignore-call */ isDisabled() || $this->isReadonly()) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
269
            $this->setConfig('disabled', true);
270
            $this->setAllowClear(false);
271
        }
272
273
        // Set RTL
274
        $dir = $this->getScriptDir();
275
        if ($dir == 'rtl') {
276
            $this->setConfig('dir', $dir);
277
        }
278
279
        // Ajax wizard, needs a form to get controller link
280
        if ($this->ajaxClass) {
281
            $url = $this->Link('autocomplete');
282
            $this->setAjax($url);
283
        }
284
285
        if (self::config()->enable_requirements) {
286
            self::requirements();
287
        }
288
289
        $html = parent::Field($properties);
290
        $config = $this->getConfigAsJson();
291
292
        $html = str_replace("form-select", "", $html);
293
294
        // Simply wrap with custom element and set config
295
        $html = "<tom-select data-config='" . $config . "'>" . $html . '</tom-select>';
296
297
        return $html;
298
    }
299
300
    public static function requirements()
301
    {
302
        Requirements::javascript("lekoala/silverstripe-form-elements: client/custom-elements/tom-select.min.js");
303
    }
304
305
    /**
306
     * Validate this field
307
     *
308
     * @param Validator $validator
0 ignored issues
show
Bug introduced by
The type LeKoala\FormElements\Validator was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
309
     * @return bool
310
     */
311
    public function validate($validator)
312
    {
313
        // Tags can be created on the fly and cannot be validated
314
        if ($this->getTags()) {
315
            return true;
316
        }
317
318
        if ($this->isAjax()) {
319
            return true;
320
        }
321
322
        return parent::validate($validator);
323
    }
324
}
325