Files
tipper/static/formobj.js

2324 lines
70 KiB
JavaScript
Raw Normal View History

2026-07-21 05:29:37 +03:00
/*
* @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-порта нужны
// наройки интерфейса 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
}