2324 lines
70 KiB
JavaScript
Executable File
2324 lines
70 KiB
JavaScript
Executable File
/*
|
||
* @property {boolean} editable - флаг, показывает можно ли изменять модель (добавлять/обновлять).
|
||
|
||
В чем проблема с binded полями?
|
||
Есть поле deviceID, которое получаем из SELECT. Поле deviceID нужно отправить на сервер.
|
||
Но при построении интерфейса нужно больше:
|
||
- объект device,
|
||
чтобы вывести имя объекта в списке ListModel, например "Sharky 775 (m-bus)"
|
||
чтобы заполнить addr SELECT, который зависит от device.connector.connectorID
|
||
- список объектов из device SELECT, чтобы заполнить SELECT без обращения к серверу,
|
||
когда юзер будет кликать по модемам, пытаясь что-то отредактировать.
|
||
- deviceID, чтобы отправить на сервер как поле объекта и чтобы выбрать в SELECT
|
||
нужный пункт, когда юзер щелкает пытаясь что-то отредактировать.
|
||
|
||
Список можно не хранить, а загружать снова с сервера.
|
||
Но объект и ID хранить обязательно.
|
||
|
||
Либо можно вызывать обработчик, которому передавать объект, а тот в свою очередь
|
||
будет строить addr SELECT, либо внедрит поле 'name' в formobj, которое будет
|
||
выводится как заголовок в списке ListModel. Минус - на сервер будет отправляться
|
||
поле 'name', которое там не ждут.
|
||
|
||
Можно пойти еще дальше. Обработчик внедряет поле '_name', с префиксом '_'. Когда мы
|
||
получаем полный объект для отправки на сервер (Форма без привязанной модели) - рекурсивно
|
||
обходит полученный объект и удаляем все служебные поля с префиксом '_'
|
||
|
||
*/
|
||
|
||
function Formobj(opt) {
|
||
Emiter.call(this)
|
||
|
||
// Родительская форма
|
||
this._parent = opt.parent
|
||
this._isEditable = opt.editable
|
||
this._form = getElemSafe('form', opt.form)
|
||
|
||
//console.log('BUILD:', this._form)
|
||
//
|
||
this._errorClass = opt.errorClass || 'errmsg'
|
||
this._validate = opt.validate
|
||
|
||
// Для FIX
|
||
this._version = 0
|
||
|
||
//this._embed = opt.embed || {}
|
||
this._fields = {}
|
||
|
||
this._predefined = opt.predefined || {}
|
||
//this._schema = opt.schema || {}
|
||
//this._nonUpdatable = opt.nonUpdatable || {}
|
||
this._transformData = opt.transformData
|
||
this._userOnSubmit = opt.onSubmit
|
||
// result - успешный ответ API функции submitFunc
|
||
// API ошибка автоматически вызовет showError
|
||
this._onSubmitResult = opt.onSubmitResult
|
||
|
||
|
||
if (opt.model) {
|
||
this._model = opt.model
|
||
this._model.on('select', obj => this.setObj(obj))
|
||
} else if (opt.models) {
|
||
this._models = opt.models
|
||
this._getModel = opt.getModel
|
||
|
||
if (typeof opt.getModel != 'function') {
|
||
throw new Error(`'models' option requires 'getModel' option`)
|
||
}
|
||
|
||
for (let prop in this._models) {
|
||
let model = this._models[prop]
|
||
model.on('select', obj => {
|
||
if (obj) {
|
||
// снимаем выделение с других моделей, если есть
|
||
for (let modelKey in this._models) {
|
||
let item = this._models[modelKey]
|
||
if (modelKey != prop) {
|
||
console.log('UNSELECT:', modelKey)
|
||
item.unselect()
|
||
}
|
||
}
|
||
}
|
||
this.setObj(obj)
|
||
})
|
||
}
|
||
}
|
||
|
||
this._tree = this._buildTree(opt)
|
||
console.log('TREE:', this._tree)
|
||
|
||
|
||
|
||
|
||
//this._plain = x.plain
|
||
//this._custom = x.custom
|
||
//this._embed = x.embed
|
||
|
||
this._form.addEventListener('submit', (e) => {
|
||
e.preventDefault()
|
||
this._onSubmit()
|
||
})
|
||
|
||
if (opt.submit) {
|
||
this._submit = getElemSafe('submit', opt.submit)
|
||
} else {
|
||
// Поиск submit-кнопки
|
||
for (let i=0; i<this._form.elements.length; i++) {
|
||
let elem = this._form.elements.item(i)
|
||
if (elem.getAttribute('type') == 'submit') {
|
||
this._submit = elem
|
||
}
|
||
}
|
||
}
|
||
//if (!this._submit) {
|
||
// throw new Error('submit button not found')
|
||
//}
|
||
|
||
if (this._submit) {
|
||
if (this._submit.tagName == 'INPUT') {
|
||
this._btnSubmitText = this._submit.value
|
||
} else if (this._submit.tagName == 'BUTTON') {
|
||
this._btnSubmitText = this._submit.textContent
|
||
}
|
||
|
||
this._isSubmitDisabledByDefault = this._submit.disabled
|
||
}
|
||
|
||
|
||
this._btnUpdateText = opt.btnUpdateText || this._btnSubmitText
|
||
|
||
this._api = opt.api
|
||
//this._updateFunc = opt.updateFunc
|
||
if (opt.submitFunc) {
|
||
this._getSubmitFunc = function() {
|
||
return opt.submitFunc
|
||
}
|
||
} else if (opt.getSubmitFunc) {
|
||
this._getSubmitFunc = opt.getSubmitFunc
|
||
}
|
||
//this._submitFunc = opt.submitFunc
|
||
//this._removeFunc = opt.removeFunc
|
||
|
||
if (opt.removeFunc) {
|
||
this._getRemoveFunc = function() {
|
||
return opt.removeFunc
|
||
}
|
||
} else if (opt.getRemoveFunc) {
|
||
this._getRemoveFunc = opt.getRemoveFunc
|
||
}
|
||
|
||
this._getRemoveData = opt.getRemoveData
|
||
this._btnRemoveText = opt.btnRemoveText
|
||
this._btnRemoveClass = opt.btnRemoveClass
|
||
this._key = opt.key
|
||
|
||
/*
|
||
if (this._getRemoveFunc) {
|
||
if (!this._model) {
|
||
throw new Error(`'removeFunc' option requires 'model' option`)
|
||
}
|
||
//if (!this._) {
|
||
//throw new Error(`'removeFunc' option requires 'key' option`)
|
||
//}
|
||
|
||
}
|
||
*/
|
||
|
||
|
||
if (opt.btnRemove) {
|
||
this._btnRemove = getElemSafe('btnRemove', opt.btnRemove)
|
||
if (!this._getRemoveFunc) {
|
||
throw new Error(`'btnRemove' option requires 'removeFunc' option`)
|
||
}
|
||
this._isBtnRemoveDisabledByDefault = this._btnRemove.disabled
|
||
|
||
this._btnRemove.addEventListener('click', () => {
|
||
|
||
// На модель не проверяем, ибо уже проверили в конструкторе
|
||
let selected = this._model.getSelected()
|
||
if (selected) {
|
||
let p = {}
|
||
|
||
if (this._getRemoveData) {
|
||
p = this._getRemoveData(selected)
|
||
} else {
|
||
let id = selected[this._key]
|
||
|
||
//p[this._key] = id
|
||
p = id // data - ключ вместо объекта
|
||
}
|
||
|
||
this._btnRemove.disabled = true
|
||
|
||
let url = this._getRemoveFunc(selected)
|
||
|
||
this._api.req({
|
||
func: url,
|
||
data: p,
|
||
onError: err => {
|
||
this._btnRemove.disabled = false
|
||
this.showError(err)
|
||
},
|
||
onSuccess: resp => {
|
||
if (this._model) {
|
||
// Именно перезагружаем, чтобы избежать ошибок, если модель с параметром.
|
||
console.log('model reload, form reset (removeFunc)')
|
||
this._model.reload()
|
||
this.reset()
|
||
} else {
|
||
if (this._isSubmitDisabledByDefault) {
|
||
this._calcSignature()
|
||
}
|
||
if (this._submit) {
|
||
this._submit.disabled = this._isSubmitDisabledByDefault
|
||
}
|
||
this._btnRemove.disabled = this._isBtnRemoveDisabledByDefault
|
||
}
|
||
// FIX
|
||
if (this._userOnSubmit) {
|
||
this._userOnSubmit()
|
||
}
|
||
}
|
||
})
|
||
return
|
||
}
|
||
})
|
||
}
|
||
/*
|
||
if (this._updateFunc) {
|
||
if (!this._api) {
|
||
throw new Error("'updateFunc' requires 'api' option")
|
||
}
|
||
}
|
||
*/
|
||
if (this._getSubmitFunc) {
|
||
if (!this._api) {
|
||
throw new Error("'submitFunc' requires 'api' option")
|
||
}
|
||
}
|
||
|
||
// добавить setifdisabled
|
||
this._createObjects(this._tree)
|
||
|
||
|
||
if (this._isSubmitDisabledByDefault) {
|
||
//this._listenChanges()
|
||
this._calcSignature()
|
||
}
|
||
|
||
|
||
// init custom elements
|
||
|
||
//console.log('INIT:', this._tree.custom)
|
||
|
||
for (let prop in this._tree.custom) {
|
||
let c = this._tree.custom[prop]
|
||
if (c.getFunc) {
|
||
this._loadData(c)
|
||
}
|
||
}
|
||
}
|
||
|
||
Formobj.prototype = Object.create(Emiter.prototype)
|
||
Formobj.prototype.constructor = Formobj
|
||
|
||
Formobj.EDITABLE = 1
|
||
Formobj.READONLY = 2
|
||
Formobj.DISABLED = 3
|
||
|
||
Formobj.prototype.isDisabled = function() {
|
||
/*
|
||
if (this._parent) {
|
||
return this._parent.isDisabled()
|
||
}
|
||
if (this._model && !this._isEditable && this._model.getSelected()) {
|
||
return true
|
||
}
|
||
return false
|
||
*/
|
||
if (this._isEditable) {
|
||
return false
|
||
} else {
|
||
if (this._settingObj) {
|
||
return true
|
||
}
|
||
return false
|
||
}
|
||
}
|
||
|
||
/*
|
||
Formobj.prototype.restoreState = function() {
|
||
let stateStr = localStorage.getItem('x')
|
||
if (stateStr) {
|
||
let state = JSON.parse(stateStr)
|
||
|
||
console.log('SET STATE:', stateStr)
|
||
this.setObj(state)
|
||
}
|
||
}
|
||
*/
|
||
|
||
// po - parent object
|
||
Formobj.prototype._loadData = function(item, po, id) {
|
||
//console.log('_loadData:', item, po, id)
|
||
// В кэше нет - загружаем
|
||
let version = this._version
|
||
if (this._settingObj) {
|
||
this._settingObj.loadingCount++
|
||
}
|
||
|
||
item.loading = true
|
||
|
||
this._api.req({
|
||
func: item.getFunc(po),
|
||
data: id,
|
||
onError: err => {
|
||
item.loading = false
|
||
if (version !== this._version) {
|
||
//console.log('version changed! this._version != version', this._version, version)
|
||
return
|
||
}
|
||
item.divLoadError = insertError({
|
||
after: item.elem.getAnchor(),
|
||
err: err,
|
||
class: this._errorClass
|
||
})
|
||
//console.log("load:", err)
|
||
if (this._settingObj) {
|
||
this._settingObj.loadingCount--
|
||
this._onFormLoaded()
|
||
}
|
||
},
|
||
onSuccess: resp => {
|
||
item.loading = false
|
||
if (version !== this._version) {
|
||
//console.log('version changed! this._version != version', this._version, version)
|
||
return
|
||
}
|
||
// Добавляем в кэш
|
||
// fix перенести в Attached
|
||
//let m = this._cache[item.prop]
|
||
//m.set(id, resp)
|
||
|
||
let cleanResp = resp
|
||
if (item.responseModifier) {
|
||
cleanResp = item.responseModifier(resp)
|
||
}
|
||
|
||
item.cache.set(id, cleanResp)
|
||
|
||
item.elem.setList(cleanResp)
|
||
|
||
this._setValueIfEditMode(item)
|
||
|
||
if (this._settingObj) {
|
||
this._settingObj.loadingCount--
|
||
this._onFormLoaded()
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
Formobj.prototype._onFormLoaded = function() {
|
||
if (this._settingObj.loadingCount == 0) {
|
||
//console.log('_onFormLoaded:', this, this._isSubmitDisabledByDefault)
|
||
if (this._isSubmitDisabledByDefault) {
|
||
this._calcSignature()
|
||
}
|
||
this._settingObj = null
|
||
}
|
||
}
|
||
|
||
Formobj.prototype._calcSignature = function() {
|
||
// Нет проблемы если AJAX элементы не загрузились.
|
||
// Все 'list' элементы вернут [], все 'select' элементы вернут undefined
|
||
this._signature = JSON.stringify(this.cleanObj(this.getFormData()))
|
||
//this._signature = JSON.stringify(this.getFormData())
|
||
console.log('sign:', this._signature)
|
||
}
|
||
/*
|
||
Formobj.prototype._listenChanges = function() {
|
||
//console.log('listenChanges: ', this, JSON.stringify(this._tree))
|
||
if (!this._isSubmitDisabledByDefault) {
|
||
return
|
||
|
||
}
|
||
|
||
let x = this._tree
|
||
|
||
|
||
|
||
// Повесим на все элементы формы обработчики FIX
|
||
for (let i=0; i<this._form.elements.length; i++) {
|
||
let elem = this._form.elements[i]
|
||
|
||
//console.log('elem:', elem.name)
|
||
//console.log('x.plain:', x.plain)
|
||
//console.log('x.keys:', Object.keys(x))
|
||
// Если такого поля нет в схеме
|
||
if (x.plain && !x.plain[elem.name]) {
|
||
continue
|
||
}
|
||
let eventName = 'input' // для input, textarea
|
||
// Чтобы избежать багов в разных браузерах - для select, checkbox и
|
||
// radio - повесим обработчик на другое событие - 'change'
|
||
if (elem.nodeName == 'SELECT') {
|
||
eventName = 'change'
|
||
} else if (elem.nodeName == 'INPUT') {
|
||
if (elem.type == 'radio' || elem.type == 'checkbox') {
|
||
eventName = 'change'
|
||
}
|
||
}
|
||
elem.addEventListener(eventName, () => this._onAnyChange())
|
||
}
|
||
|
||
for (let prop in x.embed) {
|
||
x.embed[prop].model.on('listChanged', () => {
|
||
//console.log('listChanged -> on any change')
|
||
this._onAnyChange()
|
||
})
|
||
}
|
||
|
||
// только топ уровень
|
||
if (x.custom) {
|
||
//let items = this._attachedObj.listItems()
|
||
for (let prop in x.custom) {
|
||
let a = x.custom[prop]
|
||
//console.log('listen select on:', a.type)
|
||
switch (a.elem.getType()) {
|
||
case 'select':
|
||
// Select, RadioList
|
||
a.elem.on('select', () => this._onAnyChange())
|
||
break;
|
||
|
||
case 'list':
|
||
// InputList, CheckList
|
||
a.elem.on('changed', () => this._onAnyChange())
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
this._calcSignature()
|
||
}
|
||
*/
|
||
|
||
|
||
Formobj.prototype._onAnyChange = function() {
|
||
//console.log('select changed:', this.isDisabled(), JSON.stringify(this.cleanObj(this.getFormData())))
|
||
|
||
//if (this._loadingCount > 0) {
|
||
// console.log('loadingCount > 0')
|
||
// return
|
||
//}
|
||
if (this._reseting) {
|
||
//console.log('reseting:', this._reseting, this._form)
|
||
return
|
||
}
|
||
if (this._settingObj) {
|
||
//console.log('settingObj:', this._settingObj, this._form)
|
||
return
|
||
}
|
||
if (this.isDisabled()) {
|
||
//console.log('disabled:', this._form)
|
||
return
|
||
}
|
||
//console.log('anyChange current:', this._signature)
|
||
//console.log('anyChange new:', JSON.stringify(this.cleanObj(this.getFormData())))
|
||
let stateStr = JSON.stringify(this.cleanObj(this.getFormData()))
|
||
|
||
//console.log('anyChange current sign:', this._signature)
|
||
//console.log('anyChange current:', stateStr)
|
||
|
||
if (this._signature != stateStr) {
|
||
this._submit.disabled = false
|
||
} else {
|
||
this._submit.disabled = true
|
||
}
|
||
|
||
//localStorage.setItem('x', stateStr)
|
||
|
||
// fix
|
||
//console.log("form changed")
|
||
//this.emit('change')
|
||
}
|
||
|
||
//Formobj.prototype.isDisabled = function() {
|
||
//}
|
||
|
||
Formobj.prototype._setValueIfEditMode = function(item) {
|
||
//console.log('!!!!!!!!!! setValueIfEditMode:', item)
|
||
|
||
// если режим редактирования
|
||
if (this._settingObj) {
|
||
|
||
if (item.manage) {
|
||
//console.log('setValueIfEditMode', item)
|
||
let nestedObj = this._settingObj.obj[item.prop]
|
||
// удалили, после выбора юзером другого пункта в процессе редактирования
|
||
//console.log('!!!!!!!_setValueIfEditMode:', this._version, item, nestedObj)
|
||
//if (nestedObj) {
|
||
switch (item.elem.getType()) {
|
||
case 'select':
|
||
//let nestedObj = this._settingObj.obj[item.prop]
|
||
if (nestedObj) {
|
||
//console.log('select (edit mode): ', nestedObj)
|
||
if (item.key) {
|
||
//console.log('SELECT: ', nestedObj[b.key], b.elem)
|
||
item.elem.select(nestedObj[item.key])
|
||
} else {
|
||
item.elem.select(nestedObj)
|
||
}
|
||
}
|
||
//item.elem.select(nestedObj[item.key])
|
||
break
|
||
|
||
case 'list':
|
||
// Хитрый код для такого кейса:
|
||
// отмечаем пункты галочками - на сервер уходит массив ключей, например,
|
||
// commandIDs. С сервера получаем массив объектов commands. Чтобы отметить
|
||
// галочками нужные пункты при setObj, нужно знать имя поля (commands) с
|
||
// массивом объектов, а также имя поля с ID объекта (commandID). Для этой
|
||
// служат опции setProp и setKey
|
||
|
||
if (nestedObj) {
|
||
// Просмотр добавленого через JS, до отправки на сервер
|
||
item.elem.setValues(nestedObj)
|
||
} else if (item.setProp && item.setKey) {
|
||
let objects = this._settingObj.obj[item.setProp]
|
||
if (objects) {
|
||
let keys = []
|
||
objects.forEach(o => {
|
||
keys.push(o[item.setKey])
|
||
})
|
||
item.elem.setValues(keys)
|
||
}
|
||
}
|
||
break;
|
||
|
||
case 'input':
|
||
if (nestedObj) {
|
||
if (item.key) {
|
||
item.elem.setValue(nestedObj[item.key])
|
||
} else {
|
||
item.elem.setValue(nestedObj)
|
||
}
|
||
//item.elem.setValue(nestedObj)
|
||
}
|
||
break;
|
||
}
|
||
|
||
// let obj = this._settingObj.obj
|
||
|
||
|
||
let t = item.tree
|
||
if (t) {
|
||
//console.log('tree:', t)
|
||
if (t.current) {
|
||
this._setObj(t.current)
|
||
}
|
||
}
|
||
|
||
// отключаем зависимые элементы, которые depends от кого-то
|
||
/*
|
||
if (this.isDisabled()) {
|
||
item.elem.disable()
|
||
}
|
||
*/
|
||
|
||
//}
|
||
}
|
||
|
||
// отключаем независимые attached элементы (у которых нет depends),
|
||
if (this._isEditable) {
|
||
// Если форма редактируемая, а поле нет - отключаем
|
||
if (item.editmode != Formobj.EDITABLE) {
|
||
item.elem.disable()
|
||
}
|
||
} else {
|
||
// Если форма НЕ редактируемая - просто отключаем
|
||
item.elem.disable()
|
||
}
|
||
|
||
/*
|
||
if (this.isDisabled()) {
|
||
item.elem.disable()
|
||
} else {
|
||
if (item.readonly) {
|
||
item.elem.disable()
|
||
}
|
||
// WARNING! NEW FUTURE
|
||
// SCHEMA FUTURE
|
||
|
||
//if (this._nonUpdatable[item.prop]) {
|
||
// item.elem.disable()
|
||
// }
|
||
|
||
}
|
||
*/
|
||
}
|
||
}
|
||
|
||
/*
|
||
Formobj.prototype.setIsDisabledFunc = function(f) {
|
||
this.isDisabled = f
|
||
|
||
for (let prop in this._tree.embed) {
|
||
let embed = this._tree.embed[prop]
|
||
embed.form.setIsDisabledFunc(f)
|
||
|
||
if (embed.model) {
|
||
embed.model.setIsDisabledFunc(f)
|
||
} else {
|
||
for (let modelKey in embed.multiModel) {
|
||
embed.multiModel[modelKey].setIsDisabledFunc(f)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
*/
|
||
|
||
Formobj.prototype._resetTree = function() {
|
||
}
|
||
|
||
Formobj.prototype._resetFields = function(x) {
|
||
if (x.schema) {
|
||
for (let prop in x.schema) {
|
||
let f = x.schema[prop]
|
||
//if (elem)
|
||
//console.log('RESET prop:', prop, 'value:', f.defaultValue)
|
||
f.elem.value = f.defaultValue
|
||
f.elem.disabled = false
|
||
}
|
||
}
|
||
|
||
|
||
if (this._disabledElems) {
|
||
this._disabledElems.forEach((elem) => {
|
||
//console.log('ENABLE:', elem.name)
|
||
elem.disabled = false
|
||
})
|
||
this._disabledElems = null
|
||
}
|
||
|
||
if (this._readonlyElems) {
|
||
this._readonlyElems.forEach((elem) => {
|
||
elem.readOnly = false
|
||
})
|
||
this._readonlyElems = null
|
||
}
|
||
|
||
// reset custom
|
||
if (x.custom) {
|
||
for (let prop in x.custom) {
|
||
let c = x.custom[prop]
|
||
// независимые элемены сбрасываем, а зависимые очищаем
|
||
if (c.manage) {
|
||
c.elem.reset()
|
||
}
|
||
// делаем unlock для независимых элементов
|
||
// зависимые очищаются (clear), поэтому enable делать не смысла
|
||
c.elem.enable()
|
||
|
||
//c.chains.forEach((nested) => {
|
||
// this._clearDepended(nested)
|
||
//})
|
||
let t = c.tree
|
||
if (t) {
|
||
if (t.current) {
|
||
this._resetFields(t.current)
|
||
}
|
||
//for (let prop in c.tree) {
|
||
//let t = c.tree[prop]
|
||
//console.log('reset tree:', t)
|
||
//}
|
||
}
|
||
}
|
||
}
|
||
|
||
for (let prop in x.embed) {
|
||
let embed = x.embed[prop]
|
||
//console.log('reset embed')
|
||
embed.form.reset()
|
||
|
||
if (embed.model) {
|
||
embed.model.clear()
|
||
} else {
|
||
for (let modelKey in embed.multiModel) {
|
||
embed.multiModel[modelKey].clear()
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
Formobj.prototype.reset = function() {
|
||
//console.log('reset:', this)
|
||
|
||
this._reseting = true
|
||
this.hideError()
|
||
//this._form.reset()
|
||
|
||
this._resetFields(this._tree)
|
||
|
||
//console.log('ENABLE list:', this._disabledElems, this)
|
||
//console.log(this._readonlyElems)
|
||
|
||
if (this._submit) {
|
||
this._submit.classList.remove(this._btnRemoveClass)
|
||
|
||
if (this.isDisabled()) {
|
||
this._submit.disabled = true
|
||
} else {
|
||
this._setSubmitText(this._btnSubmitText)
|
||
this._submit.disabled = this._isSubmitDisabledByDefault
|
||
}
|
||
}
|
||
|
||
if (this._btnRemove) {
|
||
this._btnRemove.disabled = this._isBtnRemoveDisabledByDefault
|
||
}
|
||
|
||
this._version++
|
||
this._settingObj = null
|
||
this._reseting = false
|
||
//console.log('reseted form:', this._form)
|
||
}
|
||
|
||
Formobj.prototype._setObj = function(x) {
|
||
let obj = this._settingObj.obj
|
||
console.log('_setObj:', x, obj)
|
||
//console.log('setSchemaFields:', this)
|
||
// Быстрые ссылки на disabled элементы из schema. В список попадают только элементы
|
||
// c disabled = false
|
||
this._disabledElems = []
|
||
this._readonlyElems = []
|
||
|
||
//if (x.plain)
|
||
for (let prop in x.schema) {
|
||
let f = x.schema[prop] // field
|
||
let elem = f.elem
|
||
|
||
//console.log('set schema value:', prop, f, obj[prop])
|
||
|
||
if (obj[prop] !== undefined) {
|
||
let v = obj[prop]
|
||
|
||
if (f.dataType == 'bool') {
|
||
if (v) {
|
||
f.elem.checked = true
|
||
} else {
|
||
f.elem.checked = false
|
||
}
|
||
} else {
|
||
if (f.dataType == 'hex') {
|
||
if (v) {
|
||
v = base64ToHex(v)
|
||
}
|
||
}
|
||
|
||
if (f.set) {
|
||
elem.value = f.set(v)
|
||
} else {
|
||
elem.value = v
|
||
}
|
||
}
|
||
}
|
||
|
||
if (this._isEditable) {
|
||
switch (f.editmode) {
|
||
case Formobj.DISABLED:
|
||
elem.disabled = true
|
||
this._disabledElems.push(elem)
|
||
break
|
||
|
||
case Formobj.READONLY:
|
||
if (elem.tagName == 'SELECT') {
|
||
elem.disabled = true
|
||
this._disabledElems.push(elem)
|
||
} else {
|
||
elem.readOnly = true
|
||
this._readonlyElems.push(elem)
|
||
}
|
||
break
|
||
|
||
//case Formobj.EDITABLE:
|
||
|
||
// break
|
||
}
|
||
} else {
|
||
elem.disabled = true
|
||
this._disabledElems.push(elem)
|
||
}
|
||
|
||
|
||
|
||
/*
|
||
if (this.isDisabled()) {
|
||
console.log('disable:', prop)
|
||
if (!elem.disabled) {
|
||
elem.disabled = true
|
||
this._disabledElems.push(elem)
|
||
}
|
||
} else {
|
||
console.log('not disabled:', prop, f)
|
||
// WARNING! NEW FUTURE
|
||
if (f.readonly) {
|
||
if (elem.tagName == 'SELECT') {
|
||
elem.disabled = true
|
||
this._disabledElems.push(elem)
|
||
} else {
|
||
elem.readOnly = true
|
||
this._readonlyElems.push(elem)
|
||
}
|
||
}
|
||
}
|
||
*/
|
||
}
|
||
|
||
//console.log('DISABLED:', this._disabledElems)
|
||
|
||
if (x.custom) {
|
||
// только топ уровень
|
||
for (let prop in x.custom) {
|
||
let c = x.custom[prop]
|
||
|
||
console.log('custom:', c)
|
||
//if (!c.manage) {
|
||
//continue
|
||
//}
|
||
|
||
if (c.loading) {
|
||
console.log('loading')
|
||
continue
|
||
}
|
||
|
||
let nestedObj = obj[prop]
|
||
|
||
//console.log('c:', c, 'nestedObj:', nestedObj, 'type:', c.elem.getType())
|
||
|
||
switch (c.elem.getType()) {
|
||
case 'select':
|
||
|
||
if (c.key) {
|
||
//console.log('select (edit mode): ', nestedObj[c.key])
|
||
c.elem.select(nestedObj[c.key])
|
||
} else {
|
||
// console.log('select (edit mode): ', nestedObj)
|
||
c.elem.select(nestedObj)
|
||
}
|
||
|
||
//console.log('selected:', c.elem.getSelected())
|
||
//item.elem.select(nestedObj[item.key])
|
||
break
|
||
|
||
case 'list':
|
||
c.elem.setValues(nestedObj)
|
||
break;
|
||
|
||
case 'input':
|
||
if (c.key) {
|
||
c.elem.setValue(nestedObj[c.key])
|
||
} else {
|
||
c.elem.setValue(nestedObj)
|
||
}
|
||
//item.elem.setValue(nestedObj)
|
||
break;
|
||
}
|
||
|
||
if (this._isEditable) {
|
||
if (c.editmode != Formobj.EDITABLE) {
|
||
c.elem.disable()
|
||
}
|
||
} else {
|
||
c.elem.disable()
|
||
}
|
||
|
||
let t = c.tree
|
||
if (t) {
|
||
//console.log('tree:', t)
|
||
if (t.current) {
|
||
this._setObj(t.current)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if (x.embed) {
|
||
for (let prop in x.embed) {
|
||
let embed = x.embed[prop]
|
||
|
||
if (embed.model) {
|
||
embed.model.setList(obj[prop])
|
||
} else {
|
||
for (let modelKey in embed.multiModel) {
|
||
embed.multiModel[modelKey].setList(obj[modelKey])
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// типа expand
|
||
Formobj.prototype.setObj = function(obj) {
|
||
console.log('setObj:', obj, this)
|
||
//if (!obj) {
|
||
//console.log('caller:', arguments.callee.caller.toString())
|
||
//console.trace()
|
||
//}
|
||
|
||
this.reset()
|
||
|
||
if (!obj) {
|
||
return
|
||
}
|
||
|
||
this._settingObj = {
|
||
loadingCount: 0,
|
||
obj: obj,
|
||
}
|
||
|
||
this._setObj(this._tree)
|
||
|
||
//if (this._attachedObj) {
|
||
//this._attachedObj.setObj(obj)
|
||
//}
|
||
|
||
// this._setSchemaFields(this._schema, obj)
|
||
|
||
//for (let prop in this._embed) {
|
||
// this._embed[prop].model.setList(obj[prop])
|
||
//}
|
||
|
||
|
||
// /console.log('SETOBJ: isDisabled:', this.isDisabled())
|
||
if (this.isDisabled()) {
|
||
if (this._submit) {
|
||
/*
|
||
if (this._removeFunc) {
|
||
this._setSubmitText(this._btnRemoveText)
|
||
this._submit.classList.add(this._btnRemoveClass)
|
||
this._submit.disabled = false
|
||
}
|
||
*/
|
||
}
|
||
//this._submit.disabled = true // кнопка установлена в reset()
|
||
} else {
|
||
if (this._submit) {
|
||
this._setSubmitText(this._btnUpdateText)
|
||
}
|
||
// Если форма целиком заполнена - считаем signature
|
||
// Кнопку отключать не нужно, ибо в reset уже отключили
|
||
this._onFormLoaded()
|
||
}
|
||
|
||
if (this._btnRemove) {
|
||
this._btnRemove.disabled = false
|
||
}
|
||
}
|
||
|
||
Formobj.prototype._setSubmitText = function(txt) {
|
||
if (this._submit.tagName == 'INPUT') {
|
||
this._submit.value = txt
|
||
} else if (this._submit.tagName == 'BUTTON') {
|
||
this._submit.textContent = txt
|
||
}
|
||
}
|
||
|
||
/*
|
||
Сделать общую мапу для error элементов?
|
||
|
||
Ошибки:
|
||
- для plain элементов,
|
||
- для custom элементов,
|
||
- для отдельного пункта в списке (списки простых значений допускаются только в custom элементах)
|
||
- для модели в целом (например пустой список недопустим)
|
||
|
||
_fields
|
||
*/
|
||
|
||
Formobj.prototype.getElem = function(name) {
|
||
let f = this._fields[name]
|
||
if (!f) {
|
||
throw new Error(`Unknown field ${name}`)
|
||
}
|
||
if (f.type == 'custom' || f.type == 'plain') {
|
||
return f.elem
|
||
}
|
||
throw new Error(`Unknown field ${name}`)
|
||
}
|
||
|
||
Formobj.prototype.showError = function(err) {
|
||
this.hideError()
|
||
|
||
//console.log('showErr: ', err)
|
||
//console.log('fields:', this._fields)
|
||
if (Array.isArray(err.path) && err.path.length > 0) {
|
||
/*
|
||
let leg = err.path.shift()
|
||
let embed = this._tree.embed[leg.field]
|
||
if (!embed) {
|
||
throw new Error(`Wrong leg: ${JSON.stringify(leg)}`)
|
||
}
|
||
if (embed.model.getSelected()) {
|
||
embed.model.unselect()
|
||
}
|
||
embed.model.select(leg.idx)
|
||
embed.form.showError(err)
|
||
*/
|
||
let leg = err.path.shift()
|
||
let f = this._fields[leg.field]
|
||
if (!f || f.type != 'embed') {
|
||
throw new Error(`Wrong leg: ${JSON.stringify(leg)}`)
|
||
}
|
||
if (f.model.getSelected()) {
|
||
f.model.unselect()
|
||
}
|
||
f.model.select(leg.idx)
|
||
f.form.showError(err)
|
||
} else {
|
||
let anchor;
|
||
if (err.field) {
|
||
let f = this._fields[err.field]
|
||
if (!f) {
|
||
throw new Error(`Error message for unknown field ${err.field}`)
|
||
}
|
||
|
||
switch (f.type) {
|
||
case 'custom':
|
||
anchor = f.elem.getAnchor()
|
||
break;
|
||
case 'plain':
|
||
anchor = f.elem
|
||
break;
|
||
case 'embed':
|
||
this._error = f.model.setError(err)
|
||
return
|
||
/*
|
||
for (let prop in this._tree.embed) {
|
||
let embed = this._embed[prop]
|
||
if (embed.model.getSelected()) {
|
||
embed.model.unselect()
|
||
}
|
||
}
|
||
*/
|
||
default:
|
||
throw new Error(`Unknown field type ${f.type}`)
|
||
}
|
||
} else if (err.list) {
|
||
let f = this._fields[err.list]
|
||
if (!f || f.type != "custom") {
|
||
throw new Error(`Error message for unknown list ${err.list}`)
|
||
}
|
||
anchor = f.elem.getAnchor(err.idx)
|
||
}
|
||
|
||
if (anchor) {
|
||
this._error = insertError({
|
||
after: anchor,
|
||
err: err,
|
||
class: this._errorClass
|
||
})
|
||
} else {
|
||
// Если якорь не найден (поля field и list пустые) - ошибка относится к форме
|
||
// целиком. Поэтому показываем перед submit кнопкой.
|
||
this._error = insertError({
|
||
before: this._submit,
|
||
err: err,
|
||
class: this._errorClass
|
||
})
|
||
}
|
||
// нужно ли ?
|
||
/* // multiModel ???
|
||
for (let prop in this._tree.embed) {
|
||
let embed = this._tree.embed[prop]
|
||
if (embed.model.getSelected()) {
|
||
embed.model.unselect()
|
||
}
|
||
}
|
||
*/
|
||
}
|
||
}
|
||
|
||
|
||
Formobj.prototype.hideError = function() {
|
||
if (this._error) {
|
||
this._error.remove()
|
||
}
|
||
}
|
||
|
||
Formobj.prototype._onSubmit = function() {
|
||
this.hideError()
|
||
|
||
let obj = this.getFormData()
|
||
|
||
if (this._validate) {
|
||
let err = this._validate(obj)
|
||
if (err) {
|
||
this.showError(err)
|
||
return
|
||
}
|
||
}
|
||
|
||
//console.log('formData:', obj)
|
||
|
||
// Кейсы:
|
||
// 1. Модель загружается по api. Форма добавляет объект по api. Редактирования нет.
|
||
// Привязка нужна для просмотра.
|
||
// 2. Модель загружается по api. Форма добавляет объект по api, редактирует по api.
|
||
// Привязка нужна для редактирования. Модель canUpdate = false, но непустой
|
||
// _updateFunc позволит не отключать форму.
|
||
// 3. Модель заполняется и редактируется из формы.
|
||
/*
|
||
if (this._removeFunc) {
|
||
// На модель не проверяем, ибо уже проверили в конструкторе
|
||
let selected = this._model.getSelected()
|
||
if (selected) {
|
||
let id = selected[this._key]
|
||
let p = {}
|
||
p[this._key] = id
|
||
|
||
this._submit.disabled = true
|
||
|
||
this._api.req({
|
||
"func": this._removeFunc,
|
||
"data": p,
|
||
"onError": err => {
|
||
this._submit.disabled = false
|
||
this.showError(err)
|
||
},
|
||
"onSuccess": resp => {
|
||
if (this._model) {
|
||
// Именно перезагружаем, чтобы избежать ошибок, если модель с параметром.
|
||
console.log('model reload, form reset (removeFunc)')
|
||
this._model.reload()
|
||
this.reset()
|
||
} else {
|
||
if (this._isSubmitDisabledByDefault) {
|
||
this._calcSignature()
|
||
}
|
||
this._setSubmitText(this._btnSubmitText)
|
||
this._submit.disabled = this._isSubmitDisabledByDefault
|
||
}
|
||
// FIX
|
||
if (this._userOnSubmit) {
|
||
this._userOnSubmit()
|
||
}
|
||
}
|
||
})
|
||
return
|
||
}
|
||
}
|
||
*/
|
||
|
||
if (this._getSubmitFunc) {
|
||
let url = this._getSubmitFunc(obj)
|
||
if (!url) {
|
||
console.log(`getSubmitFunc returns: ${url}`)
|
||
return
|
||
}
|
||
console.log('SUBMIT:', this.cleanObj(obj))
|
||
this._submit.disabled = true
|
||
|
||
this._api.req({
|
||
"func": url,
|
||
"data": this.cleanObj(obj),
|
||
"onError": err => {
|
||
this._submit.disabled = false
|
||
this.showError(err)
|
||
},
|
||
"onSuccess": resp => {
|
||
if (this._model) {
|
||
// Именно перезагружаем, чтобы избежать ошибок, если модель с параметром.
|
||
console.log('model reload, form reset (submitFunc)')
|
||
this._model.reload()
|
||
this.reset()
|
||
//} else if (this._models) {
|
||
// this.reset()
|
||
} else {
|
||
if (this._isSubmitDisabledByDefault) {
|
||
this._calcSignature()
|
||
}
|
||
this._setSubmitText(this._btnSubmitText)
|
||
this._submit.disabled = this._isSubmitDisabledByDefault
|
||
}
|
||
// FIX
|
||
if (this._userOnSubmit) {
|
||
this._userOnSubmit()
|
||
}
|
||
if (this._onSubmitResult) {
|
||
this._onSubmitResult(resp)
|
||
}
|
||
}
|
||
})
|
||
} else if (this._model) {
|
||
// без ajax
|
||
if (this._model.getSelected()) {
|
||
//console.log('UPDATE MODEL:', obj)
|
||
this._model.updateSelected(obj)
|
||
// Если обновление пройдет успешно - получим событие 'select'
|
||
// с пустым аргументом и форма сбросится автоматически
|
||
} else {
|
||
//console.log('ADD TO MODEL:', obj)
|
||
let ok = this._model.add(obj)
|
||
if (ok) {
|
||
//console.log('ADDED.')
|
||
console.log('after model add')
|
||
this.reset()
|
||
}
|
||
}
|
||
} else if (this._models) {
|
||
// без ajax
|
||
let modelKey = this._getModel(obj)
|
||
let model = this._models[modelKey]
|
||
|
||
if (!model) {
|
||
throw new Error(`not found model for modelKey ${modelKey}`)
|
||
}
|
||
|
||
if (model.getSelected()) {
|
||
//console.log('UPDATE MODEL:', obj)
|
||
model.updateSelected(obj)
|
||
// Если обновление пройдет успешно - получим событие 'select'
|
||
// с пустым аргументом и форма сбросится автоматически
|
||
} else {
|
||
//console.log('ADD TO MODEL:', obj)
|
||
let ok = model.add(obj)
|
||
if (ok) {
|
||
//console.log('ADDED.')
|
||
console.log('after model add')
|
||
this.reset()
|
||
}
|
||
}
|
||
} else if (this._userOnSubmit) {
|
||
// без ajax, без модели
|
||
this._userOnSubmit(obj)
|
||
}
|
||
/*
|
||
if (this._model) {
|
||
if (this._model.getSelected()) {
|
||
if (this._updateFunc) {
|
||
this._sendUpdateReq(obj)
|
||
} else {
|
||
// без ajax
|
||
this._model.updateSelected(obj)
|
||
// Если обновление пройдет успешно - получим событие 'select'
|
||
// с пустым аргументом и форма сбросится автоматически
|
||
}
|
||
} else {
|
||
if (this._submitFunc) {
|
||
this._sendSubmitReq(obj)
|
||
} else {
|
||
// без ajax
|
||
let ok = this._model.add(obj)
|
||
if (ok) {
|
||
this.reset()
|
||
}
|
||
}
|
||
}
|
||
} else {
|
||
if (this._submitFunc) {
|
||
this._sendSubmitReq(obj)
|
||
} else if (this._userOnSubmit) {
|
||
// без ajax, без модели
|
||
this._userOnSubmit(obj)
|
||
}
|
||
}
|
||
*/
|
||
}
|
||
/*
|
||
Formobj.prototype._sendUpdateReq = function(obj) {
|
||
this._submit.disabled = true
|
||
let x = this.cleanObj(obj)
|
||
|
||
this._api.req({
|
||
func: this._updateFunc,
|
||
data: x,
|
||
onError: err => this._onAjaxSubmitError(err),
|
||
onSuccess: resp => this._onAjaxSubmitSuccess(resp)
|
||
})
|
||
}
|
||
|
||
Formobj.prototype._sendSubmitReq = function(obj) {
|
||
this._submit.disabled = true
|
||
let x = this.cleanObj(obj)
|
||
|
||
this._api.req({
|
||
func: this._submitFunc,
|
||
data: x,
|
||
onError: err => this._onAjaxSubmitError(err),
|
||
onSuccess: resp => this._onAjaxSubmitSuccess(resp)
|
||
})
|
||
}
|
||
|
||
Formobj.prototype._onAjaxSubmitError = function(err) {
|
||
this._submit.disabled = false
|
||
this.showError(err)
|
||
}
|
||
|
||
Formobj.prototype._onAjaxSubmitSuccess = function(resp) {
|
||
if (this._model) {
|
||
// Именно перезагружаем, чтобы избежать ошибок, если модель с параметром.
|
||
this._model.reload()
|
||
this.reset()
|
||
} else {
|
||
if (this._isSubmitDisabledByDefault) {
|
||
this._calcSignature()
|
||
}
|
||
this._setSubmitText(this._btnSubmitText)
|
||
this._submit.disabled = this._isSubmitDisabledByDefault
|
||
}
|
||
}
|
||
*/
|
||
|
||
|
||
Formobj.prototype.getCustomElemData = function(custom) {
|
||
let obj = {}
|
||
for (let prop in custom) {
|
||
|
||
let a = custom[prop]
|
||
|
||
//console.log('getCustomElemData:', a)
|
||
switch (a.elem.getType()) {
|
||
case 'list':
|
||
obj[prop] = a.elem.getValues()
|
||
break;
|
||
|
||
case 'select':
|
||
obj[prop] = a.elem.getSelected()
|
||
break
|
||
|
||
case 'input':
|
||
obj[prop] = a.elem.getValue()
|
||
//console.log('X:', prop, obj[prop])
|
||
break
|
||
}
|
||
|
||
if (a.chains) {
|
||
// fix добавить рекурсивный обход только custom
|
||
obj = Object.assign(obj, this.getCustomElemData(a.chains))
|
||
}
|
||
|
||
if (a.tree) {
|
||
let cur = a.tree.current
|
||
if (cur) {
|
||
//console.log('cur:', cur)
|
||
obj = Object.assign(obj, this.getData(cur))
|
||
}
|
||
}
|
||
}
|
||
return obj
|
||
}
|
||
|
||
|
||
Formobj.prototype.getFormData = function() {
|
||
let obj = {}
|
||
|
||
for (let name in this._predefined) {
|
||
obj[name] = this._predefined[name]
|
||
}
|
||
|
||
obj = Object.assign(obj, this.getData(this._tree))
|
||
|
||
if (this._transformData) {
|
||
obj = this._transformData(obj)
|
||
}
|
||
|
||
//console.log('getFOrmData:', JSON.stringify(obj))
|
||
|
||
return obj
|
||
}
|
||
|
||
Formobj.prototype.getData = function(x) {
|
||
//console.log('gteData x:', x)
|
||
let obj = {}
|
||
|
||
// SCHEMA FUTURE
|
||
if (x.schema) {
|
||
let fd = new FormData(this._form)
|
||
|
||
// SCHEMA
|
||
for (let prop in x.schema) {
|
||
let dataType = x.schema[prop].dataType
|
||
let value = fd.get(prop)
|
||
let v
|
||
|
||
// Обрезаем пробелы
|
||
if (typeof value == 'string') {
|
||
value = value.trim()
|
||
}
|
||
|
||
switch (dataType) {
|
||
case 'int':
|
||
v = parseInt(value) || 0
|
||
break
|
||
case 'float':
|
||
v = parseFloat(value) || 0
|
||
break
|
||
case 'bool':
|
||
console.log('bool:', value)
|
||
if (value == 'on') {
|
||
//if ()
|
||
v = true
|
||
} else {
|
||
v = false
|
||
}
|
||
break
|
||
case 'str':
|
||
v = value || ''
|
||
break
|
||
|
||
case 'hex':
|
||
try {
|
||
v = hexToBytes(value)
|
||
} catch (e) {
|
||
v = ''
|
||
}
|
||
break
|
||
|
||
default:
|
||
throw new Error(`Unknown type: ${dataType}`)
|
||
}
|
||
obj[prop] = v
|
||
}
|
||
}
|
||
|
||
// fix как подружить объекты и схемы?
|
||
|
||
// type, elem, chains, tree
|
||
if (x.custom) {
|
||
obj = Object.assign(obj, this.getCustomElemData(x.custom))
|
||
}
|
||
|
||
if (x.embed) {
|
||
for (let prop in x.embed) {
|
||
let embed = x.embed[prop]
|
||
|
||
if (embed.model) {
|
||
obj[prop] = embed.model.getList()
|
||
} else if (embed.multiModel) {
|
||
for (let modelKey in embed.multiModel) {
|
||
obj[modelKey] = embed.multiModel[modelKey].getList()
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return obj
|
||
}
|
||
|
||
Formobj.prototype._buildTree = function(opt) {
|
||
let r = {
|
||
//plain: opt.schema,
|
||
custom: {},
|
||
embed: opt.embed,
|
||
// temp
|
||
schema: {}
|
||
}
|
||
|
||
if (opt.schema) {
|
||
for (let prop in opt.schema) {
|
||
let v = opt.schema[prop]
|
||
if (typeof v == 'string') {
|
||
r.schema[prop] = {
|
||
dataType: v,
|
||
editmode: Formobj.EDITABLE
|
||
}
|
||
} else {
|
||
let f = {
|
||
dataType: v.dataType,
|
||
get: v.get,
|
||
set: v.set,
|
||
}
|
||
|
||
if (v.editmode) {
|
||
switch (v.editmode) {
|
||
case Formobj.DISABLED:
|
||
f.editmode = Formobj.DISABLED
|
||
break
|
||
|
||
case Formobj.READONLY:
|
||
f.editmode = Formobj.READONLY
|
||
break
|
||
|
||
case Formobj.EDITABLE:
|
||
f.editmode = Formobj.EDITABLE
|
||
break
|
||
|
||
default:
|
||
throw new Error(`Unknown '${prop}' editmode: ${v.editmode}`)
|
||
}
|
||
} else {
|
||
f.editmode = Formobj.EDITABLE
|
||
}
|
||
|
||
r.schema[prop] = f
|
||
}
|
||
}
|
||
}
|
||
|
||
//console.log('buildTree:', opt)
|
||
|
||
if (opt.attached) {
|
||
for (let prop in opt.attached) {
|
||
let c = opt.attached[prop]
|
||
|
||
r.custom[prop] = this._buildCustom(c, prop)
|
||
}
|
||
}
|
||
|
||
//console.log('returnned tree:', opt, r)
|
||
return r
|
||
}
|
||
|
||
Formobj.prototype._buildCustom = function(c, prop) {
|
||
if (!c.elemConstructor) {
|
||
throw new Error(`'elemConstructor' option is required for custom element ${prop}`)
|
||
}
|
||
|
||
let f = {
|
||
prop: prop,
|
||
setProp: c.setProp,
|
||
setKey: c.setKey,
|
||
//readonly: c.readonly,
|
||
virtual: c.virtual, // вирутальное поле, например RadioList, в form.elements не проверять
|
||
elemConstructor: c.elemConstructor,
|
||
opts: c.opts,
|
||
//func: c.func,
|
||
//getFunc: c.getFunc,
|
||
key: c.key,
|
||
manage: true,
|
||
eventHandler: c.eventHandler,
|
||
cache: new Map(),
|
||
requestModifier: c.requestModifier,
|
||
responseModifier: c.responseModifier,
|
||
|
||
chains: c.chains || {}
|
||
}
|
||
|
||
if (c.editmode) {
|
||
switch (c.editmode) {
|
||
case Formobj.DISABLED:
|
||
f.editmode = Formobj.DISABLED
|
||
break
|
||
|
||
case Formobj.READONLY:
|
||
f.editmode = Formobj.READONLY
|
||
break
|
||
|
||
case Formobj.EDITABLE:
|
||
f.editmode = Formobj.EDITABLE
|
||
break
|
||
|
||
default:
|
||
throw new Error(`Unknown '${prop}' editmode: ${c.editmode}`)
|
||
}
|
||
} else {
|
||
f.editmode = Formobj.EDITABLE
|
||
}
|
||
|
||
if (c.func) {
|
||
f.getFunc = function() {
|
||
return c.func
|
||
}
|
||
} else if (c.getFunc) {
|
||
f.getFunc = c.getFunc
|
||
}
|
||
|
||
if ((f.setProp && !f.setKey) || (!f.setProp && f.setKey)) {
|
||
throw new Error("'setKey' and 'setProp' options must be set or not set both")
|
||
}
|
||
|
||
//console.log('opt/f', opt, f)
|
||
|
||
if (c.div) {
|
||
f.div = getElemSafe('div', c.div)
|
||
}
|
||
|
||
if (c.manage === false) {
|
||
f.manage = false
|
||
}
|
||
// создаем экземпляр кастомного элемента для проверки
|
||
//let elem = new c.elem(c.opts)
|
||
// для правильного считывания/установки значения
|
||
//c.type = elem.type
|
||
|
||
// tree
|
||
let t = c.tree
|
||
if (t) {
|
||
let branches = new Map()
|
||
let x = {
|
||
getValue: t.getValue,
|
||
container: t.container,
|
||
branches: branches,
|
||
}
|
||
|
||
t.cases.forEach(b => {
|
||
let branch = {
|
||
templateId: b.templateId,
|
||
templates: b.templates,
|
||
value: b.value,
|
||
values: b.values,
|
||
}
|
||
|
||
let tmp = this._buildTree(b)
|
||
branch = Object.assign(branch, tmp)
|
||
|
||
if (b.value) {
|
||
branches.set(branch.value, branch)
|
||
} else if (Array.isArray(b.values)) {
|
||
b.values.forEach(value => {
|
||
branches.set(value, branch)
|
||
})
|
||
} else {
|
||
throw new Error(`'value' or 'values' option required in branch settings`)
|
||
}
|
||
})
|
||
|
||
f.tree = x
|
||
}
|
||
|
||
for (let prop in c.chains) {
|
||
let d = c.chains[prop]
|
||
|
||
f.chains[prop] = this._buildCustom(d, prop)
|
||
}
|
||
return f
|
||
}
|
||
|
||
Formobj.prototype.cleanObj = function(obj) {
|
||
//console.log('cleanData:', JSON.stringify(obj))
|
||
let r = {}
|
||
this._cleanData(r, this._tree, obj)
|
||
|
||
//console.log('after cleanData:', JSON.stringify(r))
|
||
|
||
if (this._transformData) {
|
||
r = this._transformData(r)
|
||
}
|
||
//console.log('getCleanData return:', r)
|
||
return r
|
||
}
|
||
|
||
// r - result object, a - custom object
|
||
Formobj.prototype._cleanCustomElem = function(r, a, obj) { //, prop) {
|
||
//r.xxxx = 1900000
|
||
//console.log('CLEAN ATTACHED:', a)
|
||
//console.log('CLEAN ATTACHED obj:', obj)
|
||
|
||
let valueObj = obj[a.prop]
|
||
if (valueObj !== undefined) {
|
||
if (a.key) {
|
||
// копируем только ID
|
||
|
||
// Объекта может не быть, если юзер ничего не выбрал (например, в выпадающем списке)
|
||
|
||
r[a.key] = valueObj[a.key]
|
||
// console.log('REPLACED:', a.key, id, 'DELETED', prop)
|
||
} else {
|
||
//console.log('DELETED', prop)
|
||
r[a.prop] = valueObj
|
||
}
|
||
}
|
||
|
||
if (a.chains) {
|
||
for (let prop in a.chains) {
|
||
this._cleanCustomElem(r, a.chains[prop], obj)
|
||
//console.log('_CLEAN CUSTOM: AFTER CLEAN ATTACHED:', JSON.stringify(r))
|
||
}
|
||
}
|
||
|
||
let tree = a.tree
|
||
if (tree) {
|
||
var v;
|
||
if (tree.getValue) {
|
||
v = tree.getValue(valueObj) // obj
|
||
} else {
|
||
if (a.key) {
|
||
v = r[a.key]
|
||
} else {
|
||
v = valueObj
|
||
}
|
||
}
|
||
|
||
let branch = tree.branches.get(v)
|
||
//console.log('v:', v, 'branch:', branch)
|
||
if (branch) {
|
||
if (branch.custom) {
|
||
r = this._cleanData(r, branch, obj)
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
//console.log('CLEAN ATTACHED:', JSON.stringify(r))
|
||
}
|
||
|
||
Formobj.prototype._cleanData = function(r, x, obj) {
|
||
/*
|
||
SCHEMA FUTURE
|
||
if (x.plain) {
|
||
for (let prop in x.plain) {
|
||
r[prop] = obj[prop]
|
||
}
|
||
}
|
||
*/
|
||
if (x.schema) {
|
||
for (let prop in x.schema) {
|
||
r[prop] = obj[prop]
|
||
}
|
||
}
|
||
|
||
if (x.custom) {
|
||
for (let prop in x.custom) {
|
||
//let a = x.custom[prop]
|
||
|
||
this._cleanCustomElem(r, x.custom[prop], obj) //, prop)
|
||
|
||
//console.log('_cleanDATA: AFTER CLEAN ATTACHED:', JSON.stringify(r), a)
|
||
}
|
||
}
|
||
|
||
if (x.embed) {
|
||
for (let prop in x.embed) {
|
||
|
||
let embed = x.embed[prop]
|
||
|
||
console.log('_cleanData embed:', embed)
|
||
if (embed.model) {
|
||
//console.log('e:', e)
|
||
let dirtyList = obj[prop]
|
||
|
||
//console.log('dirtyList:', dirtyList)
|
||
|
||
let cleanItems = []
|
||
|
||
if (Array.isArray(dirtyList)) {
|
||
dirtyList.forEach(dirtyItem => {
|
||
let cleanItem = embed.form.cleanObj(dirtyItem)
|
||
cleanItems.push(cleanItem)
|
||
})
|
||
}
|
||
|
||
r[prop] = cleanItems
|
||
} else if (embed.multiModel) {
|
||
for (let modelKey in embed.multiModel) {
|
||
let dirtyList = obj[modelKey]
|
||
|
||
//console.log('dirtyList:', dirtyList)
|
||
|
||
let cleanItems = []
|
||
|
||
if (Array.isArray(dirtyList)) {
|
||
dirtyList.forEach(dirtyItem => {
|
||
let cleanItem = embed.form.cleanObj(dirtyItem)
|
||
cleanItems.push(cleanItem)
|
||
})
|
||
}
|
||
|
||
r[modelKey] = cleanItems
|
||
}
|
||
} else {
|
||
//throw new Error(`bug: embed doesn't contain 'model' or 'multiModel' option`)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
Formobj.prototype._createCustomElemsOnly = function(customElems) {
|
||
if (customElems) {
|
||
for (let prop in customElems) {
|
||
let c = customElems[prop]
|
||
|
||
let opts = Object.assign({}, c.opts)
|
||
|
||
// для удобного отображения ошибок
|
||
let name = prop
|
||
if (c.key) {
|
||
name = c.key
|
||
}
|
||
|
||
if (!c.virtual) {
|
||
let field = this._form.elements[name]
|
||
if (!field) {
|
||
throw new Error(`Field '${name}' not found`)
|
||
}
|
||
|
||
opts.field = field
|
||
}
|
||
|
||
//if (c.type != 'select' && c.type != 'list' && item.type != 'input') {
|
||
// throw new Error(`Unknown attached elem type ${item.type}. Valid types: select, list, input`)
|
||
//}
|
||
// создаем экземпляр кастомного элемента
|
||
|
||
|
||
c.elem = new c.elemConstructor(opts)
|
||
|
||
|
||
this._fields[name] = {
|
||
type: 'custom',
|
||
elem: c.elem
|
||
}
|
||
|
||
if (this._isSubmitDisabledByDefault) {
|
||
switch (c.elem.getType()) {
|
||
case 'select':
|
||
// Select, RadioList
|
||
c.elem.on('select', () => this._onAnyChange())
|
||
break;
|
||
|
||
case 'list':
|
||
// InputList, CheckList
|
||
c.elem.on('changed', () => this._onAnyChange())
|
||
break;
|
||
}
|
||
}
|
||
|
||
//if (c.func) {
|
||
// console.log(this, 'loadData')
|
||
//this._loadData(c)
|
||
//}
|
||
|
||
/*
|
||
Пофиксить
|
||
if (item.key) {
|
||
this._attachedElems[item.key] = item.elem
|
||
} else {
|
||
this._attachedElems[prop] = item.elem
|
||
}
|
||
*/
|
||
// для правильного считывания/установки значения
|
||
//c.type = elem.type
|
||
|
||
|
||
if (c.tree || c.chains) {
|
||
c.event = c.elem.getActionType()
|
||
if (c.event != 'select' && c.event != 'input') {
|
||
throw new Error(`Custom element '${prop}' has wrong action type '${c.event}'`)
|
||
}
|
||
// fix
|
||
c.elem.on(c.event, (id) => this._onElemEvent(id, c))
|
||
}
|
||
// объекты из chains создаем сразу, а заполняем (и, по желанию,
|
||
// скрываем/показываем) в ответ на событие
|
||
if (c.chains) {
|
||
// создаем дальше, chains - тоже map
|
||
this._createCustomElemsOnly(c.chains)
|
||
}
|
||
// объекты из Tree создаются только в ответ на событие
|
||
}
|
||
}
|
||
}
|
||
|
||
Formobj.prototype._createObjects = function(opt) {
|
||
|
||
if (opt.schema) {
|
||
for (let prop in opt.schema) {
|
||
let f = opt.schema[prop]
|
||
let elem = this._form.elements[prop]
|
||
if (!elem) {
|
||
throw new Error(`Field '${prop}' not found`)
|
||
}
|
||
if (!(elem instanceof Element)) {
|
||
//throw new Error(`Form element '${prop}' not found`)
|
||
throw new Error(`Field ${prop} must be instanceof Element. Not ${Object.prototype.toString.call(elem)}`)
|
||
}
|
||
f.elem = elem
|
||
f.defaultValue = elem.value
|
||
// для удобного отображения ошибок
|
||
this._fields[prop] = {
|
||
type: 'plain',
|
||
elem: elem
|
||
}
|
||
|
||
if (f.dataType == 'bool') {
|
||
if (elem.nodeName != 'INPUT') {
|
||
throw new Error('bool elem must be checkbox')
|
||
}
|
||
if (elem.type != 'checkbox') {
|
||
throw new Error('bool elem must be checkbox')
|
||
}
|
||
//elem.value = ''
|
||
}
|
||
|
||
// Ловим события изменения
|
||
if (this._isSubmitDisabledByDefault) {
|
||
let eventName = 'input' // для input, textarea
|
||
// Чтобы избежать багов в разных браузерах - для select, checkbox и
|
||
// radio - повесим обработчик на другое событие - 'change'
|
||
if (elem.nodeName == 'SELECT') {
|
||
eventName = 'change'
|
||
} else if (elem.nodeName == 'INPUT') {
|
||
if (elem.type == 'radio' || elem.type == 'checkbox') {
|
||
eventName = 'change'
|
||
}
|
||
}
|
||
elem.addEventListener(eventName, () => this._onAnyChange())
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
// SCHEMA FUTURE
|
||
opt.plainElems = {}
|
||
if (opt.plain) {
|
||
for (let prop in opt.plain) {
|
||
let elem = this._form.elements[prop]
|
||
if (!elem) {
|
||
throw new Error(`Field '${prop}' not found`)
|
||
}
|
||
if (!(elem instanceof Element)) {
|
||
//throw new Error(`Form element '${prop}' not found`)
|
||
throw new Error(`Field ${prop} must be instanceof Element. Not ${Object.prototype.toString.call(elem)}`)
|
||
}
|
||
opt.plainElems[prop] = elem
|
||
// для удобного отображения ошибок
|
||
this._fields[prop] = {
|
||
type: 'plain',
|
||
elem: elem
|
||
}
|
||
|
||
// Ловим события изменения
|
||
if (this._isSubmitDisabledByDefault) {
|
||
let eventName = 'input' // для input, textarea
|
||
// Чтобы избежать багов в разных браузерах - для select, checkbox и
|
||
// radio - повесим обработчик на другое событие - 'change'
|
||
if (elem.nodeName == 'SELECT') {
|
||
eventName = 'change'
|
||
} else if (elem.nodeName == 'INPUT') {
|
||
if (elem.type == 'radio' || elem.type == 'checkbox') {
|
||
eventName = 'change'
|
||
}
|
||
}
|
||
elem.addEventListener(eventName, () => this._onAnyChange())
|
||
}
|
||
}
|
||
}
|
||
*/
|
||
|
||
if (opt.custom) {
|
||
this._createCustomElemsOnly(opt.custom)
|
||
}
|
||
|
||
for (let prop in opt.embed) {
|
||
|
||
//console.log('listenChanges:', this._listenChanges)
|
||
|
||
let e = opt.embed[prop]
|
||
|
||
if (e.modelOpts) {
|
||
let model = new ListModel(e.modelOpts)
|
||
|
||
let formOpts = Object.assign({}, e.formOpts)
|
||
formOpts.model = model
|
||
formOpts.parent = this
|
||
|
||
let form = new Formobj(formOpts)
|
||
//form.setIsDisabledFunc(this.isDisabled)
|
||
model.setIsDisabledFunc(this.isDisabled.bind(this))
|
||
|
||
//embed[prop] = {
|
||
// model: model,
|
||
// form: form
|
||
//}
|
||
e.model = model
|
||
e.form = form
|
||
|
||
// для удобного отображения ошибок
|
||
this._fields[prop] = {
|
||
type: 'embed',
|
||
model: model,
|
||
form: form
|
||
}
|
||
|
||
|
||
if (this._isSubmitDisabledByDefault) {
|
||
model.on('listChanged', () => {
|
||
//console.log('listChanged -> on any change')
|
||
this._onAnyChange()
|
||
})
|
||
}
|
||
} else if (e.multiModelOpts) {
|
||
let models = {}
|
||
|
||
for (let modelKey in e.multiModelOpts) {
|
||
let modelOpts = e.multiModelOpts[modelKey]
|
||
|
||
let model = new ListModel(modelOpts)
|
||
model.setIsDisabledFunc(this.isDisabled.bind(this))
|
||
|
||
if (this._isSubmitDisabledByDefault) {
|
||
model.on('listChanged', () => {
|
||
//console.log('listChanged -> on any change')
|
||
this._onAnyChange()
|
||
})
|
||
}
|
||
|
||
models[modelKey] = model
|
||
}
|
||
|
||
let formOpts = Object.assign({}, e.formOpts)
|
||
formOpts.models = models
|
||
formOpts.parent = this
|
||
|
||
let form = new Formobj(formOpts)
|
||
//form.setIsDisabledFunc(this.isDisabled)
|
||
|
||
|
||
for (let modelKey in models) {
|
||
let model = models[modelKey]
|
||
|
||
// для удобного отображения ошибок
|
||
this._fields[modelKey] = {
|
||
type: 'embed',
|
||
model: model,
|
||
form: form
|
||
}
|
||
}
|
||
e.multiModel = models
|
||
e.form = form
|
||
} else {
|
||
throw new Error(`embed requires 'modelOpts' or 'multiModelOpts'`)
|
||
}
|
||
}
|
||
|
||
//this._onAnyChange()
|
||
//this._calcSignature()
|
||
|
||
|
||
//console.log('_createObjects:', opt)
|
||
// Загружаем все AJAX-элементы топ уровня (которые ни от кого не зависят)
|
||
// fix
|
||
/*
|
||
this._chains.forEach(c => {
|
||
if (!c.func) {
|
||
return
|
||
}
|
||
this._loadData(item)
|
||
})
|
||
*/
|
||
}
|
||
|
||
Formobj.prototype._onElemEvent = function(id, c) {
|
||
console.log('elem event:', id, c)
|
||
|
||
let p;
|
||
switch (c.elem.getType()) {
|
||
case 'list':
|
||
p = c.elem.getValues()
|
||
break;
|
||
|
||
case 'select':
|
||
p = c.elem.getSelected()
|
||
break
|
||
|
||
case 'input':
|
||
p = c.elem.getValue()
|
||
//console.log('X:', prop, obj[prop])
|
||
break
|
||
}
|
||
|
||
if (c.eventHandler) {
|
||
if (!c.manage && this._settingObj) {
|
||
// pass
|
||
} else {
|
||
c.eventHandler(p)
|
||
}
|
||
}
|
||
|
||
if (c.chains) {
|
||
for (let prop in c.chains) {
|
||
this._clearDepended(c.chains[prop]) // fix elem param?
|
||
}
|
||
|
||
if (id) {
|
||
for (let prop in c.chains) {
|
||
let d = c.chains[prop] // dependend
|
||
//console.log('chain item:', d)
|
||
/*
|
||
// fix цикл по chains
|
||
if (item.doFunc) {
|
||
let data;
|
||
switch (c.event) {
|
||
case 'select':
|
||
data = elem.getSelected()
|
||
break
|
||
|
||
case 'input':
|
||
data = elem.getValue()
|
||
break
|
||
}
|
||
// что-нибудь типа setList для InputList, либо buildAddrSelect для addr
|
||
c.doFunc(data)
|
||
//console.log('SETvALUE after doFunc')
|
||
this._setValueIfEditMode(c) // fix elem param?
|
||
} else {
|
||
*/
|
||
// Если данные есть в кэше - достаем из кэша
|
||
//let m = this._cache[item.prop]
|
||
//let list = m.get(id)
|
||
let cleanParam = id
|
||
if (d.requestModifier) {
|
||
cleanParam = d.requestModifier(p)
|
||
}
|
||
|
||
//console.log('d:', d)
|
||
//console.log('p:', p)
|
||
//console.log('id:', id)
|
||
//console.log('cleanParam:', cleanParam)
|
||
|
||
let list = d.cache.get(cleanParam)
|
||
if (list) {
|
||
d.elem.setList(list)
|
||
this._setValueIfEditMode(d) // fix elem param?
|
||
} else {
|
||
this._loadData(d, p, cleanParam) // fix elem param?
|
||
}
|
||
}
|
||
//}
|
||
|
||
//if (c.div) {
|
||
// c.div.hidden = false
|
||
//}
|
||
}
|
||
}
|
||
|
||
let t = c.tree
|
||
if (t) {
|
||
let cleanId = id
|
||
if (t.getValue) {
|
||
cleanId = t.getValue(p)
|
||
}
|
||
|
||
let branch = t.branches.get(cleanId)
|
||
|
||
//console.log(id)
|
||
console.log('branch:', branch)
|
||
console.log(t)
|
||
//console.log('container:', t.container)
|
||
|
||
let container = document.getElementById(t.container)
|
||
if (!container) {
|
||
throw new Error(`container '${t.container}' not found ('${t.prop}' tree)`)
|
||
}
|
||
|
||
if (!branch) {
|
||
if (container) {
|
||
container.innerHTML = ''
|
||
}
|
||
|
||
if (t.containers) {
|
||
t.containers.forEach(containerId => {
|
||
document.getElementById(containerId).innerHTML = ''
|
||
})
|
||
t.containers = null
|
||
}
|
||
t.current = undefined
|
||
|
||
// fix удалить elems в текущем и дочерних элементах
|
||
//console.log('t:', t)
|
||
} else {
|
||
if (t.containers) {
|
||
t.containers.forEach(containerId => {
|
||
document.getElementById(containerId).innerHTML = ''
|
||
})
|
||
t.containers = null
|
||
}
|
||
// Шаблон необязателен. Пример: один объект состоит только из полей по умолчанию
|
||
// Второй объект требует дополнительных полей (например для COM-порта нужны
|
||
// наcтройки интерфейса RS485)
|
||
if (branch.templateId) {
|
||
copyTemplateInto(branch.templateId, container)
|
||
|
||
this._createObjects(branch)
|
||
} else if (branch.templates) {
|
||
t.containers = []
|
||
for (let container in branch.templates) {
|
||
let templateId = branch.templates[container]
|
||
|
||
if (templateId != '') {
|
||
copyTemplateInto(templateId, container)
|
||
t.containers.push(container) // id
|
||
} else {
|
||
document.getElementById(container).innerHTML = ''
|
||
}
|
||
|
||
|
||
}
|
||
this._createObjects(branch)
|
||
}
|
||
|
||
t.current = branch
|
||
|
||
for (let prop in branch.custom) {
|
||
let c = branch.custom[prop]
|
||
if (c.getFunc) {
|
||
let cleanParam = id
|
||
if (c.requestModifier) {
|
||
cleanParam = c.requestModifier(p)
|
||
}
|
||
//console.log('id:', id, 'p:', p)
|
||
|
||
this._loadData(c, p, cleanParam) // fix
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if (c.div) {
|
||
if (id) {
|
||
c.div.hidden = false
|
||
} else {
|
||
c.div.hidden = true
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
Formobj.prototype._clearDepended = function(c) {
|
||
c.elem.clear()
|
||
|
||
if (c.div) {
|
||
c.div.hidden = true
|
||
}
|
||
// FIX if item.after ?
|
||
if (c.divLoadError) {
|
||
c.divLoadError.remove()
|
||
}
|
||
|
||
if (c.chains) {
|
||
for (let prop in c.chains) {
|
||
this._clearDepended(c.chains[prop])
|
||
}
|
||
}
|
||
}
|
||
|
||
// hex fields
|
||
|
||
|
||
function base64Decode(text){
|
||
if (!text) {
|
||
return []
|
||
}
|
||
|
||
text = text.replace(/\s/g,"");
|
||
|
||
if(!(/^[a-z0-9\+\/\s]+\={0,2}$/i.test(text)) || text.length % 4 > 0){
|
||
throw new Error("Not a base64-encoded string.");
|
||
}
|
||
|
||
//local variables
|
||
var digits = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
|
||
cur, prev, digitNum,
|
||
i=0,
|
||
result = [];
|
||
|
||
text = text.replace(/=/g, "");
|
||
|
||
while(i < text.length){
|
||
|
||
cur = digits.indexOf(text.charAt(i));
|
||
digitNum = i % 4;
|
||
|
||
switch(digitNum){
|
||
|
||
//case 0: first digit - do nothing, not enough info to work with
|
||
|
||
case 1: //second digit
|
||
result.push(prev << 2 | cur >> 4);
|
||
break;
|
||
|
||
case 2: //third digit
|
||
result.push((prev & 0x0f) << 4 | cur >> 2);
|
||
break;
|
||
|
||
case 3: //fourth digit
|
||
result.push((prev & 3) << 6 | cur);
|
||
break;
|
||
}
|
||
|
||
prev = cur;
|
||
i++;
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
function base64ToHex(base64Str) {
|
||
let buf = base64Decode(base64Str)
|
||
let hexes = []
|
||
buf.forEach(b => {
|
||
let hex = b.toString(16)
|
||
if (hex.length == 1) {
|
||
hex = '0' + hex
|
||
}
|
||
hexes.push(hex)
|
||
})
|
||
return hexes.join(' ')
|
||
}
|
||
|
||
// принимает строку с опциональными пробелами между байтами
|
||
function hexToBytes(str) {
|
||
let bytes = []
|
||
let arr = str.split(' ')
|
||
arr.forEach(elem => {
|
||
if (elem == '') {
|
||
return
|
||
}
|
||
if (elem.length % 2) {
|
||
throw new Error("Wrong hex string")
|
||
}
|
||
// секции вида: 0c, ddff
|
||
//console.log('elem:', elem)
|
||
for (let i=0; i<elem.length; i+=2) {
|
||
let hex = elem.substr(i, 2)
|
||
//console.log('i:', i, 'hex:', hex)
|
||
let num = parseInt(hex, 16)
|
||
if (isNaN(num)) {
|
||
throw new Error("Wrong hex string")
|
||
}
|
||
bytes.push(num)
|
||
}
|
||
})
|
||
return bytes
|
||
} |