dbjs-dom
Version:
dbjs DOM bindings
325 lines (310 loc) • 11.2 kB
JavaScript
'use strict';
var separate = require('es5-ext/array/#/separate')
, assign = require('es5-ext/object/assign')
, forEach = require('es5-ext/object/for-each')
, isPlainObject = require('es5-ext/object/is-plain-object')
, callable = require('es5-ext/object/valid-callable')
, validObject = require('es5-ext/object/valid-object')
, validValue = require('es5-ext/object/valid-value')
, memoize = require('memoizee/plain')
, memoizeMethods = require('memoizee/methods-plain')
, getNormalizer = require('memoizee/normalizers/get-1')
, d = require('d')
, autoBind = require('d/auto-bind')
, isObservable = require('observable-value/is-observable')
, isObservableArray = require('observable-array/is-observable-array')
, isObservableSet = require('observable-set/is-observable-set')
, clear = require('dom-ext/element/#/clear')
, exclude = require('dom-ext/element/#/exclude')
, include = require('dom-ext/element/#/include')
, replace = require('dom-ext/element/#/replace')
, replaceContent = require('dom-ext/element/#/replace-content')
, isNested = require('dbjs/is-dbjs-nested-object')
, resolveOptions = require('./utils/resolve-options')
, DOMInput = require('./_controls/input')
, DOMRadio = require('./_controls/radio')
, DOMSelect = require('./_controls/select')
, DOMMultiple = require('./_multiple/checkbox')
, DOMComposite = require('./_composite')
, map = Array.prototype.map, create = Object.create, defineProperties = Object.defineProperties
, getName = Object.getOwnPropertyDescriptor(DOMInput.prototype, 'name').get
, createOption = DOMSelect.prototype.createOption
, createRadio = DOMRadio.prototype.createOption
, getId = function (args) { return args[0].__id__; }
, Radio, Select, Edit, Multiple, resolveDbOptions, getResolver;
getResolver = memoize(function (type) {
return function (obj) {
if (!obj || ((obj.constructor !== type) && !type.isPrototypeOf(obj.constructor))) {
throw new TypeError(obj + " is not a " + type.__id__);
}
return obj;
};
}, { normalizer: getNormalizer() });
resolveDbOptions = function (type, options) {
var list = (typeof options.list === 'function') ? options.list.call(this, options) : options.list;
if (list != null) {
if (isObservableSet(list)) {
this.dbOptions = list.toArray().map(getResolver(type), this);
} else if (isObservableArray(list)) {
this.dbOptions = list.map(getResolver(type), this);
} else if (typeof list === 'function') {
this.dbOptions = map.call(list, getResolver(type), this);
} else {
this.dbOptions = map.call(list, getResolver(type), this);
}
} else {
this.dbOptions = type.instances.toArray(options.compare);
}
if (!this.group) this.dbOptions = this.dbOptions.map(this.createOption, this);
if (typeof this.dbOptions.on !== 'function') return;
this.dbOptions.on('change', this.reload);
};
Select = function (document, type/*, options*/) {
var options = arguments[2];
this.type = type;
options = resolveOptions(options, type);
DOMSelect.call(this, document, type, options);
if (options.getOptionLabel != null) this.getOptionLabel = callable(options.getOptionLabel);
else this.property = options.property;
if (options.group != null) {
this.group = validObject(options.group);
validValue(this.group.propertyName);
}
resolveDbOptions.call(this, type, options);
this.reload();
};
Select.prototype = Object.create(DOMSelect.prototype, assign({
constructor: d(Select)
}, memoizeMethods({
createOption: d(function (obj) {
var value;
if (this.getOptionLabel) {
value = this.getOptionLabel(obj);
if (isObservable(value)) value = value.toDOM(this.document);
} else if (this.property) {
value = obj._get(this.property);
if (isObservable(value)) value = value.toDOM(this.document);
} else {
value = this.document.createTextNode(obj);
}
return createOption.call(this, obj.__id__, value);
}, { normalizer: getId }),
createOptgroup: d(function (obj) {
var el = this.document.createElement('optgroup'), value;
if (this.group.labelPropertyName) {
value = obj._get(this.group.labelPropertyName);
if (isObservable(value)) value = value.toDOMAttr(el, 'label');
else el.setAttribute('label', value);
} else {
el.setAttribute('label', value);
}
return el;
}, { normalizer: getId })
}), autoBind({
reload: d(function () {
var els, done, options = this.dbOptions;
if (this.group) {
els = [];
done = create(null);
options.forEach(function (obj) {
var group = obj[this.group.propertyName], optgroup, option;
if (!group) {
console.warn("No group found for", obj.__id__, "searched by", this.group.propertyName);
return;
}
optgroup = this.createOptgroup(group);
option = this.createOption(obj);
if (!done[group.__id__]) {
clear.call(optgroup);
done[group.__id__] = true;
els.push(optgroup);
}
optgroup.appendChild(option);
}, this);
} else {
els = options;
}
replaceContent.call(this.control, this.chooseOption, els);
})
})));
Radio = function (document, type/*, options*/) {
var options = arguments[2];
this.type = type;
options = resolveOptions(options, type);
DOMRadio.call(this, document, type, options);
this.dom.classList.add('object-list');
this.property = options.property;
resolveDbOptions.call(this, type, options);
this.reload();
};
Radio.prototype = Object.create(DOMRadio.prototype, assign({
constructor: d(Radio),
createOption: d(function (obj) {
return createRadio.call(this, obj.__id__,
this.property ? obj._get(this.property).toDOM(this.document) :
this.document.createTextNode(obj));
})
}, autoBind({
reload: d(function () { replaceContent.call(this.dom, this.dbOptions); })
})));
Edit = function (document, type/*, options*/) {
var options = arguments[2];
this.type = type;
options = resolveOptions(options, type);
if (options.render) this._render = callable(options.render);
DOMComposite.call(this, document, type, options);
if (options.render) delete this._render;
this.objInput = this.dom.appendChild(this.make('input', { type: 'hidden' }));
exclude.call(this.objInput);
};
Edit.prototype = Object.create(DOMComposite.prototype, {
constructor: d(Edit),
_render: d(function (options) {
var el = this.make, props, desc = options.dbOptions
, obj = this.type.prototype;
props = options.inputProperties || desc.inputProperties ||
this.type.inputProperties ||
this.type.prototype.toSet('key').toArray();
this.dom = el('div', { class: 'inputs' }, separate.call(props.map(function (name) {
var observable = obj._get(name);
return this.addItem(observable.toDOMInput(this.document,
this.getOptions(observable.descriptor)), name);
}, this), ' '));
}),
name: d.gs(getName, function (name) {
this._name = name;
name = this.name;
if (name) {
if (this.obj) {
this.objInput.setAttribute('name', name);
} else {
forEach(this.items, function (item, propName) {
item.name = name + '.' + propName;
});
}
} else if (this.obj) {
this.objInput.removeAttribute('name');
} else {
forEach(this.items, function (item) { item.name = null; });
}
}),
value: d.gs(function () { return this.inputValue; }, function (value) {
var obj;
if (value === undefined) value = null;
if (this.obj === value) return;
obj = value || this.type.prototype;
forEach(this.items, function (oldInput, name) {
var nuInput, observable = obj._get(name);
oldInput.destroy();
nuInput = observable.toDOMInput(this.document,
this.getOptions(observable.descriptor));
replace.call(oldInput.dom, nuInput.dom);
if (!value) nuInput.name = this.name + '.' + name;
this.addItem(nuInput, name);
}, this);
if (value && !isNested(value)) {
this.objInput.setAttribute('value', value.__id__);
include.call(this.objInput);
} else {
this.objInput.removeAttribute('value');
exclude.call(this.objInput);
}
this.obj = value;
this.name = this._name;
})
});
Multiple = function (document, type/*, options*/) {
var options = arguments[2], getLabel, list, toData;
this.type = type;
options = resolveOptions(options, type);
getLabel = function (obj) {
var label = options.itemLabel;
if (typeof label === 'function') return label(obj);
if (typeof label === 'string') return obj._get(label);
return obj;
};
if (options.list) {
list = options.list;
} else {
list = type.instances.toArray(options.sort);
}
toData = function (obj) {
return { label: getLabel(obj), value: obj.__id__ };
};
this.dbList = list.map(toData);
if (isObservable(this.dbList)) this.dbList.on('change', this.reload);
DOMMultiple.call(this, document, type, options);
};
Multiple.prototype = Object.create(DOMMultiple.prototype, {
constructor: d(Multiple)
});
module.exports = exports = function (db) {
defineProperties(db.Object, {
choosLabel: d("Choose:"),
fromInputValue: d(function (value) {
var empty, result, owner, index, resolvedPath;
if (value == null) return null;
if (isPlainObject(value)) {
empty = true;
forEach(value, function (subValue, name) {
var sKey = this._serialize_(name)
, desc = this._getDescriptor_(sKey);
subValue = desc.type.fromInputValue(subValue);
value[name] = subValue;
if (subValue != null) empty = false;
}, this.prototype);
return empty ? null : value;
}
value = value.trim();
if (!value) return null;
result = this.getById(value);
if (result) return result;
if (db.objects.getById(value)) return null;
// If it's about nested, it might be that it's not loaded into memory
// Below logic ensures it's retrieved properly
index = value.indexOf('/');
if (index === -1) return null;
owner = db.objects.getById(value.slice(0, index));
if (!owner) return null;
resolvedPath = owner.resolveSKeyPath(value.slice(index + 1));
if (!resolvedPath) return null;
value = resolvedPath.value;
if (value && (value instanceof this)) return value;
return null;
}),
toInputValue: d(function (value) {
if (value == null) return null;
if (!this.is(value)) return null;
return value.__id__;
}),
DOMRadio: d(Radio),
DOMSelect: d(Select),
DOMEdit: d(Edit),
DOMInput: d(Select),
DOMMultipleCheckbox: d(Multiple),
toDOMInput: d(function (document/*, options*/) {
var options = Object(arguments[1]);
if (options.multiple) {
if (options.type === 'checkbox') {
return new Multiple(document, this, options);
}
return new this.DOMMultipleInput(document, this, options);
}
if (options.type === 'edit') {
return new this.DOMEdit(document, this, options);
}
if (options.type === 'radio') {
return new this.DOMRadio(document, this, options);
}
return new this.DOMInput(document, this, options);
})
});
defineProperties(db.Object.$getOwn('chooseLabel'), {
type: d('', db.String),
required: d(true)
});
};
exports.Select = Select;
exports.Radio = Radio;
exports.Edit = Edit;
exports.Multiple = Multiple;