UNPKG

11.4 kBJavaScriptView Raw
1/**
2 * @module settings-panel
3 */
4'use strict';
5
6const Emitter = require('events').EventEmitter;
7const inherits = require('inherits');
8const extend = require('just-extend');
9const css = require('dom-css');
10const uid = require('get-uid');
11const fs = require('fs');
12const insertCss = require('insert-styles');
13const isPlainObject = require('is-plain-obj');
14const format = require('param-case');
15const px = require('add-px-to-style');
16const scopeCss = require('scope-css');
17
18module.exports = Panel
19
20
21insertCss(fs.readFileSync(__dirname + '/index.css', 'utf-8'));
22
23
24/**
25 * @constructor
26 */
27function Panel (items, opts) {
28 if (!(this instanceof Panel)) return new Panel(items, opts)
29
30 extend(this, opts);
31
32 //ensure container
33 if (this.container === undefined) this.container = document.body || document.documentElement;
34
35 this.container.classList.add('settings-panel-container');
36
37 //create element
38 if (!this.id) this.id = uid();
39 this.element = document.createElement('div')
40 this.element.className = 'settings-panel settings-panel-' + this.id;
41 if (this.className) this.element.className += ' ' + this.className;
42
43 //create title
44 if (this.title) {
45 this.titleEl = this.element.appendChild(document.createElement('h2'));
46 this.titleEl.className = 'settings-panel-title';
47 }
48
49 //create collapse button
50 if (this.collapsible && this.title) {
51 // this.collapseEl = this.element.appendChild(document.createElement('div'));
52 // this.collapseEl.className = 'settings-panel-collapse';
53 this.element.classList.add('settings-panel--collapsible');
54 this.titleEl.addEventListener('click', () => {
55 if (this.collapsed) {
56 this.collapsed = false;
57 this.element.classList.remove('settings-panel--collapsed');
58 }
59 else {
60 this.collapsed = true;
61 this.element.classList.add('settings-panel--collapsed');
62 }
63 });
64 }
65
66 //state is values of items
67 this.state = {};
68
69 //items is all items settings
70 this.items = {};
71
72 //create fields
73 this.set(items);
74
75 if (this.container) {
76 this.container.appendChild(this.element)
77 }
78
79 //create theme style
80 this.update();
81}
82
83inherits(Panel, Emitter);
84
85
86/**
87 * Set item value/options
88 */
89Panel.prototype.set = function (name, value) {
90 //handle list of properties
91 if (Array.isArray(name)) {
92 let items = name;
93 items.forEach((item) => {
94 this.set(item.id || item.label, item);
95 });
96
97 return this;
98 }
99
100 //handle plain object
101 if (isPlainObject(name)) {
102 let items = name;
103 let list = [];
104 for (let key in items) {
105 if (!isPlainObject(items[key])) {
106 items[key] = {value: items[key]};
107 }
108 if (items[key].id == null) items[key].id = key;
109 list.push(items[key]);
110 }
111 list = list.sort((a, b) => (a.order||0) - (b.order||0));
112
113 return this.set(list);
114 }
115
116 //format name
117 name = name || '';
118 name = name.replace(/\-/g,'dash-');
119 name = format(name);
120
121 if (name) {
122 var item = this.items[name];
123 if (!item) item = this.items[name] = { id: name, panel: this };
124 }
125 //noname items should not be saved in state
126 else {
127 var item = {id: null, panel: this};
128 }
129
130 var initialValue = item.value;
131 var isBefore = item.before;
132 var isAfter = item.after;
133
134 if (isPlainObject(value)) {
135 item = extend(item, value);
136 }
137 else {
138 //ignore nothing-changed set
139 if (value === item.value && value !== undefined) return this;
140 item.value = value;
141 }
142
143 if (item.value === undefined) item.value = item.default;
144
145 if (name) this.state[name] = item.value;
146
147 //define label via name
148 if (item.label === undefined && item.id) {
149 item.label = item.id;
150 }
151
152 //detect type
153 if (!item.type) {
154 if (item.value && Array.isArray(item.value)) {
155 if (typeof item.value[0] === 'string') {
156 item.type = 'checkbox';
157 }
158 else {
159 item.type = 'interval'
160 }
161 } else if (item.scale || item.max || item.steps || item.step || typeof item.value === 'number') {
162 item.type = 'range'
163 } else if (item.options) {
164 if (Array.isArray(item.options) && item.options.join('').length < 90 ) {
165 item.type = 'switch'
166 }
167 else {
168 item.type = 'select'
169 }
170 } else if (item.format) {
171 item.type = 'color'
172 } else if (typeof item.value === 'boolean') {
173 item.type = 'checkbox'
174 } else if (item.content != null) {
175 item.type = 'raw'
176 } else {
177 if (item.value && (item.value.length > 140 || /\n/.test(item.value))) {
178 item.type = 'textarea'
179 }
180 else {
181 item.type = 'text'
182 }
183 }
184 }
185
186 var field, fieldId;
187
188 if (item.id != null) {
189 fieldId = 'settings-panel-field-' + item.id;
190 field = this.element.querySelector('#' + fieldId);
191 }
192
193 //create field container
194 if (!field) {
195 field = document.createElement('div');
196 if (fieldId != null) field.id = fieldId;
197 this.element.appendChild(field);
198 item.field = field;
199 }
200 else {
201 //clean previous before/after
202 if (isBefore) {
203 this.element.removeChild(field.prevSibling);
204 }
205 if (isAfter) {
206 this.element.removeChild(field.nextSibling);
207 }
208 }
209
210 field.className = 'settings-panel-field settings-panel-field--' + item.type;
211
212 if (item.orientation) field.className += ' settings-panel-orientation-' + item.orientation;
213
214 if (item.className) field.className += ' ' + item.className;
215
216 if (item.style) {
217 if (isPlainObject(item.style)) {
218 css(field, item.style);
219 }
220 else if (typeof item.style === 'string') {
221 field.style.cssText = item.style;
222 }
223 }
224 else if (item.style !== undefined) {
225 field.style = null;
226 }
227
228 if (item.hidden) {
229 field.setAttribute('hidden', true);
230 }
231 else {
232 field.removeAttribute('hidden');
233 }
234
235 //createe container for the input
236 let inputContainer = field.querySelector('.settings-panel-input');
237
238 if (!inputContainer) {
239 inputContainer = document.createElement('div');
240 inputContainer.className = 'settings-panel-input';
241 item.container = inputContainer;
242 field.appendChild(inputContainer);
243 }
244
245 if (item.disabled) field.className += ' settings-panel-field--disabled';
246
247 let components = this.components;
248 let component = item.component;
249
250 if (!component) {
251 item.component = component = (components[item.type] || components.text)(item);
252
253 if (component.on) {
254 component.on('init', (data) => {
255 item.value = data
256 if (item.id) this.state[item.id] = item.value;
257 let state = extend({}, this.state);
258
259 item.init && item.init(data, state)
260 this.emit('init', item.id, data, state)
261 item.change && item.change(data, state)
262 this.emit('change', item.id, data, state)
263 });
264
265 component.on('input', (data) => {
266 item.value = data
267 if (item.id) this.state[item.id] = item.value;
268 let state = extend({}, this.state);
269
270 item.input && item.input(data, state)
271 this.emit('input', item.id, data, state)
272 item.change && item.change(data, state)
273 this.emit('change', item.id, data, state)
274 });
275
276 component.on('change', (data) => {
277 item.value = data
278 if (item.id) this.state[item.id] = item.value;
279 let state = extend({}, this.state);
280
281 item.change && item.change(data, state)
282 this.emit('change', item.id, data, state)
283 });
284 }
285 }
286 else {
287 component.update(item);
288 }
289
290 //create field label
291 if (component.label !== false && (item.label || item.label === '')) {
292 let label = field.querySelector('.settings-panel-label');
293 if (!label) {
294 label = document.createElement('label')
295 label.className = 'settings-panel-label';
296 field.insertBefore(label, inputContainer);
297 }
298
299 label.htmlFor = item.id;
300 label.innerHTML = item.label;
301 label.title = item.title || item.label;
302 }
303
304 //handle after and before
305 // if (item.before) {
306 // let before = item.before;
307 // if (before instanceof Function) {
308 // before = item.before.call(item, component);
309 // }
310 // if (before instanceof HTMLElement) {
311 // this.element.insertBefore(before, field);
312 // }
313 // else {
314 // field.insertAdjacentHTML('beforebegin', before);
315 // }
316 // }
317 // if (item.after) {
318 // let after = item.after;
319 // if (after instanceof Function) {
320 // after = item.after.call(item, component);
321 // }
322 // if (after instanceof HTMLElement) {
323 // this.element.insertBefore(after, field.nextSibling);
324 // }
325 // else {
326 // field.insertAdjacentHTML('afterend', after);
327 // }
328 // }
329
330 //emit change
331 if (initialValue !== item.value) {
332 this.emit('change', item.id, item.value, this.state)
333 }
334
335 return this;
336}
337
338
339/**
340 * Return property value or a list
341 */
342Panel.prototype.get = function (name) {
343 if (name == null) return this.state;
344 return this.state[name];
345}
346
347
348/**
349 * Update theme
350 */
351Panel.prototype.update = function (opts) {
352 extend(this, opts);
353
354 //FIXME: decide whether we have to reset these params
355 // if (opts && opts.theme) {
356 // if (opts.theme.fontSize) this.fontSize = opts.theme.fontSize;
357 // if (opts.theme.inputHeight) this.inputHeight = opts.theme.inputHeight;
358 // if (opts.theme.fontFamily) this.fontFamily = opts.theme.fontFamily;
359 // if (opts.theme.labelWidth) this.labelWidth = opts.theme.labelWidth;
360 // if (opts.theme.palette) this.palette = opts.theme.palette;
361 // }
362
363 //update title, if any
364 if (this.titleEl) this.titleEl.innerHTML = this.title;
365
366 //update orientation
367 this.element.classList.remove('settings-panel-orientation-top');
368 this.element.classList.remove('settings-panel-orientation-bottom');
369 this.element.classList.remove('settings-panel-orientation-left');
370 this.element.classList.remove('settings-panel-orientation-right');
371 this.element.classList.add('settings-panel-orientation-' + this.orientation);
372
373 //apply style
374 let cssStr = '';
375 if (this.theme instanceof Function) {
376 cssStr = this.theme.call(this, this);
377 }
378 else if (typeof this.theme === 'string') {
379 cssStr = this.theme;
380 }
381
382 //append extra css
383 if (this.css) {
384 if (this.css instanceof Function) {
385 cssStr += this.css.call(this, this);
386 }
387 else if (typeof this.css === 'string') {
388 cssStr += this.css;
389 }
390 }
391
392 //scope each rule
393 cssStr = scopeCss(cssStr || '', '.settings-panel-' + this.id) || '';
394
395 insertCss(cssStr.trim(), {
396 id: this.id
397 });
398
399 if (this.style) {
400 if (isPlainObject(this.style)) {
401 css(this.element, this.style);
402 }
403 else if (typeof this.style === 'string') {
404 this.element.style.cssText = this.style;
405 }
406 }
407 else if (this.style !== undefined) {
408 this.element.style = null;
409 }
410
411 return this;
412}
413
414//instance theme
415Panel.prototype.theme = require('./theme/none');
416
417/**
418 * Registered components
419 */
420Panel.prototype.components = {
421 range: require('./src/range'),
422
423 button: require('./src/button'),
424 text: require('./src/text'),
425 textarea: require('./src/textarea'),
426
427 checkbox: require('./src/checkbox'),
428 toggle: require('./src/checkbox'),
429
430 switch: require('./src/switch'),
431
432 color: require('./src/color'),
433
434 interval: require('./src/interval'),
435 multirange: require('./src/interval'),
436
437 custom: require('./src/custom'),
438 raw: require('./src/custom'),
439
440 select: require('./src/select')
441};
442
443
444/**
445 * Additional class name
446 */
447Panel.prototype.className;
448
449
450/**
451 * Additional visual setup
452 */
453Panel.prototype.orientation = 'left';
454
455
456/** Display collapse button */
457Panel.prototype.collapsible = false;
\No newline at end of file