UNPKG

12.1 kBJavaScriptView Raw
1import {BaseDropdown} from './baseDropdown';
2import {createElm, createOpt, elm} from '../dom';
3import {has} from '../array';
4import {matchCase} from '../string';
5import {addEvt, targetEvt} from '../event';
6import {SELECT, MULTIPLE, NONE} from '../const';
7import {defaultsStr, defaultsBool} from '../settings';
8
9/**
10 * Dropdown filter UI component
11 * @export
12 * @class Dropdown
13 * @extends {BaseDropdown}
14 */
15export class Dropdown extends BaseDropdown {
16
17 /**
18 * Creates an instance of Dropdown
19 * @param {TableFilter} tf TableFilter instance
20 */
21 constructor(tf) {
22 super(tf, Dropdown);
23
24 // Configuration object
25 let f = this.config;
26
27 /**
28 * Enable the reset filter option as first item
29 * @type {Boolean}
30 */
31 this.enableSlcResetFilter =
32 defaultsBool(f.enable_slc_reset_filter, true);
33
34 /**
35 * Non empty option text
36 * @type {String}
37 */
38 this.nonEmptyText = defaultsStr(f.non_empty_text, '(Non empty)');
39
40 /**
41 * Tooltip text appearing on multiple select
42 * @type {String}
43 */
44 this.multipleSlcTooltip = defaultsStr(f.multiple_slc_tooltip,
45 'Use Ctrl/Cmd key for multiple selections');
46 }
47
48
49 /**
50 * Drop-down filter focus event handler
51 * @param {Event} e DOM Event
52 * @private
53 */
54 onSlcFocus(e) {
55 let elm = targetEvt(e);
56 let tf = this.tf;
57 // select is populated when element has focus
58 if (tf.loadFltOnDemand && elm.getAttribute('filled') === '0') {
59 let ct = elm.getAttribute('ct');
60 this.build(ct);
61 }
62 this.emitter.emit('filter-focus', tf, elm);
63 }
64
65 /**
66 * Drop-down filter change event handler
67 * @private
68 */
69 onSlcChange() {
70 if (this.tf.onSlcChange) {
71 this.tf.filter();
72 }
73 }
74
75 /**
76 * Refresh all drop-down filters
77 */
78 refreshAll() {
79 let selectFlts = this.tf.getFiltersByType(SELECT, true);
80 let multipleFlts = this.tf.getFiltersByType(MULTIPLE, true);
81 let colIdxs = selectFlts.concat(multipleFlts);
82 this.refreshFilters(colIdxs);
83 }
84
85 /**
86 * Initialize drop-down filter
87 * @param {Number} colIndex Column index
88 * @param {Boolean} isExternal External filter flag
89 * @param {DOMElement} container Dom element containing the filter
90 */
91 init(colIndex, isExternal, container) {
92 let tf = this.tf;
93 let col = tf.getFilterType(colIndex);
94 let externalFltTgtId = isExternal ?
95 tf.externalFltIds[colIndex] : null;
96
97 let slc = createElm(SELECT,
98 ['id', tf.buildFilterId(colIndex)],
99 ['ct', colIndex], ['filled', '0']
100 );
101
102 if (col === MULTIPLE) {
103 slc.multiple = MULTIPLE;
104 slc.title = this.multipleSlcTooltip;
105 }
106 slc.className = col.toLowerCase() === SELECT ?
107 tf.fltCssClass : tf.fltMultiCssClass;
108
109 //filter is appended in container element
110 if (externalFltTgtId) {
111 elm(externalFltTgtId).appendChild(slc);
112 } else {
113 container.appendChild(slc);
114 }
115
116 tf.fltIds.push(slc.id);
117
118 if (!tf.loadFltOnDemand) {
119 this.build(colIndex);
120 } else {
121 //1st option is created here since build isn't invoked
122 let opt0 = createOpt(tf.getClearFilterText(colIndex), '');
123 slc.appendChild(opt0);
124 }
125
126 addEvt(slc, 'change', () => this.onSlcChange());
127 addEvt(slc, 'focus', (e) => this.onSlcFocus(e));
128
129 this.emitter.on(
130 ['build-select-filter'],
131 (tf, colIndex, isLinked, isExternal) =>
132 this.build(colIndex, isLinked, isExternal)
133 );
134 this.emitter.on(
135 ['select-options'],
136 (tf, colIndex, values) => this.selectOptions(colIndex, values)
137 );
138 this.emitter.on(['rows-changed'], () => this.refreshAll());
139
140 this.emitter.on(['after-filtering'], () => this.linkFilters());
141
142 /** @inherited */
143 this.initialized = true;
144 }
145
146 /**
147 * Build drop-down filter UI
148 * @param {Number} colIndex Column index
149 * @param {Boolean} isLinked Enable linked filters behaviour
150 */
151 build(colIndex, isLinked = false) {
152 let tf = this.tf;
153 colIndex = Number(colIndex);
154
155 this.emitter.emit('before-populating-filter', tf, colIndex);
156
157 /** @inherited */
158 this.opts = [];
159 /** @inherited */
160 this.optsTxt = [];
161
162 let slc = tf.getFilterElement(colIndex);
163
164 //custom select test
165 /** @inherited */
166 this.isCustom = tf.isCustomOptions(colIndex);
167
168 //Retrieves custom values
169 if (this.isCustom) {
170 let customValues = tf.getCustomOptions(colIndex);
171 this.opts = customValues[0];
172 this.optsTxt = customValues[1];
173 }
174
175 //custom selects text
176 let activeIdx;
177 let activeFilterId = tf.getActiveFilterId();
178 if (isLinked && activeFilterId) {
179 activeIdx = tf.getColumnIndexFromFilterId(activeFilterId);
180 }
181
182 let excludedOpts = null,
183 filteredDataCol = null;
184 if (isLinked && tf.disableExcludedOptions) {
185 excludedOpts = [];
186 filteredDataCol = [];
187 }
188
189 let eachRow = tf.eachRow();
190 eachRow(
191 (row) => {
192 let cellValue = tf.getCellValue(row.cells[colIndex]);
193 //Vary Peter's patch
194 let cellString = matchCase(cellValue, tf.caseSensitive);
195
196 // checks if celldata is already in array
197 if (!has(this.opts, cellString, tf.caseSensitive)) {
198 this.opts.push(cellValue);
199 }
200
201 if (isLinked && tf.disableExcludedOptions) {
202 let filteredCol = filteredDataCol[colIndex];
203 if (!filteredCol) {
204 filteredCol = tf.getVisibleColumnValues(colIndex);
205 }
206 if (!has(filteredCol, cellString, tf.caseSensitive) &&
207 !has(excludedOpts, cellString, tf.caseSensitive)) {
208 excludedOpts.push(cellValue);
209 }
210 }
211 },
212 // continue conditions function
213 (row, k) => {
214 // excluded rows don't need to appear on selects as always valid
215 if (tf.excludeRows.indexOf(k) !== -1) {
216 return true;
217 }
218
219 // checks if row has expected number of cells
220 if (row.cells.length !== tf.nbCells || this.isCustom) {
221 return true;
222 }
223
224 if (isLinked && !this.isValidLinkedValue(k, activeIdx)) {
225 return true;
226 }
227 }
228 );
229
230 //sort options
231 this.opts = this.sortOptions(colIndex, this.opts);
232 if (excludedOpts) {
233 excludedOpts = this.sortOptions(colIndex, excludedOpts);
234 }
235
236 //populates drop-down
237 this.addOptions(colIndex, slc, isLinked, excludedOpts);
238
239 this.emitter.emit('after-populating-filter', tf, colIndex, slc);
240 }
241
242 /**
243 * Add drop-down options
244 * @param {Number} colIndex Column index
245 * @param {Object} slc Select Dom element
246 * @param {Boolean} isLinked Enable linked refresh behaviour
247 * @param {Array} excludedOpts Array of excluded options
248 */
249 addOptions(colIndex, slc, isLinked, excludedOpts) {
250 let tf = this.tf,
251 slcValue = slc.value;
252
253 slc.innerHTML = '';
254 slc = this.addFirstOption(slc);
255
256 for (let y = 0; y < this.opts.length; y++) {
257 if (this.opts[y] === '') {
258 continue;
259 }
260 let val = this.opts[y]; //option value
261 let lbl = this.isCustom ? this.optsTxt[y] : val; //option text
262 let isDisabled = false;
263 if (isLinked && tf.disableExcludedOptions &&
264 has(excludedOpts, matchCase(val, tf.caseSensitive),
265 tf.caseSensitive)) {
266 isDisabled = true;
267 }
268
269 let opt;
270 //fill select on demand
271 if (tf.loadFltOnDemand && slcValue === this.opts[y] &&
272 tf.getFilterType(colIndex) === SELECT) {
273 opt = createOpt(lbl, val, true);
274 } else {
275 opt = createOpt(lbl, val, false);
276 }
277 if (isDisabled) {
278 opt.disabled = true;
279 }
280 slc.appendChild(opt);
281 }// for y
282
283 slc.setAttribute('filled', '1');
284 }
285
286 /**
287 * Add drop-down header option
288 * @param {Object} slc Select DOM element
289 */
290 addFirstOption(slc) {
291 let tf = this.tf;
292 let colIdx = tf.getColumnIndexFromFilterId(slc.id);
293 let opt0 = createOpt((!this.enableSlcResetFilter ?
294 '' : tf.getClearFilterText(colIdx)), '');
295 if (!this.enableSlcResetFilter) {
296 opt0.style.display = NONE;
297 }
298 slc.appendChild(opt0);
299 if (tf.enableEmptyOption) {
300 let opt1 = createOpt(tf.emptyText, tf.emOperator);
301 slc.appendChild(opt1);
302 }
303 if (tf.enableNonEmptyOption) {
304 let opt2 = createOpt(tf.nonEmptyText, tf.nmOperator);
305 slc.appendChild(opt2);
306 }
307 return slc;
308 }
309
310 /**
311 * Select filter options programmatically
312 * @param {Number} colIndex Column index
313 * @param {Array} values Array of option values to select
314 */
315 selectOptions(colIndex, values = []) {
316 let tf = this.tf;
317 if (values.length === 0) {
318 return;
319 }
320 let slc = tf.getFilterElement(colIndex);
321 [].forEach.call(slc.options, (option) => {
322 // Empty value means clear all selections and first option is the
323 // clear all option
324 if (values[0] === '' || option.value === '') {
325 option.selected = false;
326 }
327
328 if (option.value !== '' && has(values, option.value, true)) {
329 option.selected = true;
330 }//if
331 });
332 }
333
334 /**
335 * Get filter values for a given column index
336 * @param {Number} colIndex Column index
337 * @returns {Array} values Array of selected values
338 */
339 getValues(colIndex) {
340 let tf = this.tf;
341 let slc = tf.getFilterElement(colIndex);
342 let values = [];
343
344 // IE >= 9 does not support the selectedOptions property :(
345 if (slc.selectedOptions) {
346 [].forEach.call(slc.selectedOptions,
347 option => values.push(option.value));
348 } else {
349 [].forEach.call(slc.options, (option) => {
350 if (option.selected) {
351 values.push(option.value);
352 }
353 });
354 }
355
356 return values;
357 }
358
359 /**
360 * Destroy Dropdown instance
361 */
362 destroy() {
363 this.emitter.off(
364 ['build-select-filter'],
365 (colIndex, isLinked, isExternal) =>
366 this.build(colIndex, isLinked, isExternal)
367 );
368 this.emitter.off(
369 ['select-options'],
370 (tf, colIndex, values) => this.selectOptions(colIndex, values)
371 );
372 this.emitter.off(['rows-changed'], () => this.refreshAll());
373 this.emitter.off(['after-filtering'], () => this.linkFilters());
374 this.initialized = false;
375 }
376}