3550 lines
99 KiB
JavaScript
3550 lines
99 KiB
JavaScript
|
|
/*
|
|||
|
|
Начальный расчет signature делаем после загрузки главной формы.
|
|||
|
|
Как узнать когда загрузилась главная форма?
|
|||
|
|
- Заводим для формы счетчик loadingCount,
|
|||
|
|
увеличиваем его при создании элемента с функцией loadFunc, уменьшаем при заврешении
|
|||
|
|
http-запроса loadFunc (с ошибкой или без). Уменьшив счетчик проверяем - если 0 - форма
|
|||
|
|
загрузилась.
|
|||
|
|
Таким же образом рассчитываем начальный signature после restore / edit.
|
|||
|
|
|
|||
|
|
Как правильно отобразить ошибку, если юзер удалил ошибочные элементы после submit формы
|
|||
|
|
на сервер но до получения ответа от сервера?
|
|||
|
|
- Перед отправкой на сервер clean объекта, нужно сохранить dirty объект.
|
|||
|
|
Если сервер вернул ошибку - делаем restore(dirty) и затем showError(err)
|
|||
|
|
|
|||
|
|
Все элементы формы генерируют события когда юзер что-то набирает в текстовом поле либо
|
|||
|
|
выбирает в списке. Обработчик события извлекает 2 значения: clean и dirty. Затем
|
|||
|
|
обновляет цепочку объектов до топ-уровня (обновляя _clean и _dirty объекты).
|
|||
|
|
Затем (!) если есть зависимые элементы (fork, chains) - генерирует последующие события.
|
|||
|
|
|
|||
|
|
_clean - это объект, который будет отправлен на сервер, а также выступает в роли
|
|||
|
|
текущей signature.
|
|||
|
|
|
|||
|
|
_dirty - это объект который нужен для восстановления (restore) состояния формы.
|
|||
|
|
Записывается в sessionStorage / localStorage для восстановления формы после перезагрузки
|
|||
|
|
страницы. Снимок состояния в момент submit формы на сервер. Если сервер вернет ошибку -
|
|||
|
|
можно восстановить форму и корректно показать ошибку несмотря на изменения, внесенные
|
|||
|
|
юзером во время выполнения http-запроса.
|
|||
|
|
|
|||
|
|
Методы:
|
|||
|
|
|
|||
|
|
fill - заполняет поля формы будто это делает человек, не меняя режим filling. Зачем нужен этот метод?
|
|||
|
|
|
|||
|
|
// Представим ситуацию: есть кликабельный список модемов, у каждого модема есть список приборов, у каждого прибора есть список метрик. На странице модема
|
|||
|
|
|
|||
|
|
edit - заполняет поля формы, устанавливает режим в editing, чтобы можно было отключить (disabled=true) нередактируемые поля формы, а также рассчитывает initSignature после заполнения всех полей.
|
|||
|
|
|
|||
|
|
restore - восстанавливает состояние формы из снимка (snapshot)
|
|||
|
|
|
|||
|
|
Snapshot
|
|||
|
|
Снимок формы хранит - dirty объект, initSignature
|
|||
|
|
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
Разделение на getObj и cleanObj.
|
|||
|
|
getObj - возвращает объект в том виде, в котором можно вызвать setObj, то есть device вместо
|
|||
|
|
deviceID, queries вместо queryIDs.
|
|||
|
|
|
|||
|
|
Если имеем многоуровневую форму и после setObj вызываем getObj, единственный способ получить
|
|||
|
|
"чистый" объект для отправки на сервер - это сделать clean операцию на уже готовом объекте.
|
|||
|
|
|
|||
|
|
|
|||
|
|
Список объектов для Formobj.
|
|||
|
|
|
|||
|
|
Одноуровневый список. Описываем в schema, создаем под капотом.
|
|||
|
|
|
|||
|
|
|
|||
|
|
Топ-уровень - это модель, а вложенные списки объектов - это ObjectList?
|
|||
|
|
|
|||
|
|
Операции:
|
|||
|
|
- add, с проверкой fingerprint
|
|||
|
|
- replaceSelected
|
|||
|
|
- removeChecked
|
|||
|
|
- removeSelected
|
|||
|
|
|
|||
|
|
События:
|
|||
|
|
- select (если параметр undefined - это unselect)
|
|||
|
|
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
Модель (многоуровневая) для отображения данных из БД (или сервера).
|
|||
|
|
|
|||
|
|
У каждого объекта должен быть первичный ключ
|
|||
|
|
|
|||
|
|
- updateSelected, когда сервер присылает только некоторые поля объекта, например изменившийся статус
|
|||
|
|
- replaceSelected
|
|||
|
|
|
|||
|
|
|
|||
|
|
///////////////// setObj
|
|||
|
|
заводим state object, который хранит obj и количество загружаемых элементов.
|
|||
|
|
В конце setObj проверяем счетчик - если > 0 блокируем форму.
|
|||
|
|
После загрузки успешной или нет - счетчик уменьшаем. Как только стал в 0 - разблокируем форму.
|
|||
|
|
|
|||
|
|
|
|||
|
|
3 кейса для вложенных форм:
|
|||
|
|
1. раскрывается под списком
|
|||
|
|
2. в отдельном диве (например справа). Нужно передать ID блока
|
|||
|
|
3. кастомная реализация. Нужно объявить 2 метода - showForm и hideForm
|
|||
|
|
|
|||
|
|
|
|||
|
|
Разделить createForm для главной и вложенных. Другие кнопки, другие обработчики
|
|||
|
|
|
|||
|
|
|
|||
|
|
DISABLED в createSchemaObjects
|
|||
|
|
|
|||
|
|
|
|||
|
|
Для вложенных форм isEditable вместо editmode
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
|
|||
|
|
const filling = 0
|
|||
|
|
const restoring = 1
|
|||
|
|
const editing = 2
|
|||
|
|
|
|||
|
|
var _labelClass = 'omg-form-label'
|
|||
|
|
|
|||
|
|
function Form(opt) {
|
|||
|
|
//this.formId = opt.form
|
|||
|
|
this._mode = filling
|
|||
|
|
this._loadingCount = 0
|
|||
|
|
this._version = 0
|
|||
|
|
|
|||
|
|
this._enableSubmit = opt.enableSubmit
|
|||
|
|
|
|||
|
|
this._api = opt.api
|
|||
|
|
this._errorClass = opt.errorClass || 'errmsg'
|
|||
|
|
|
|||
|
|
this._key = opt.key
|
|||
|
|
|
|||
|
|
this._context = opt.context
|
|||
|
|
|
|||
|
|
this._btnSubmitText = opt.btnSubmitText || 'Добавить'
|
|||
|
|
this._btnUpdateText = opt.btnUpdateText || 'Обновить'
|
|||
|
|
this._btnRemoveText = opt.btnRemoveText || 'Удалить'
|
|||
|
|
|
|||
|
|
//this._onSubmit = opt.onSubmit
|
|||
|
|
//this._onSubmitSuccess = opt.onSubmitSuccess
|
|||
|
|
//this._onUpdateSuccess = opt.onUpdateSuccess
|
|||
|
|
//this._onRemoveSuccess = opt.onRemoveSuccess
|
|||
|
|
//this._submitFunc = opt.submitFunc
|
|||
|
|
//this._updateFunc = opt.updateFunc || this._submitFunc
|
|||
|
|
//this._removeFunc = opt.removeFunc
|
|||
|
|
|
|||
|
|
this._validate = opt.validate
|
|||
|
|
//this._form = document.getElementById(opt.form)
|
|||
|
|
//if (!this._form) {
|
|||
|
|
// throw new Error(`html form '${opt.form}' not found`)
|
|||
|
|
//}
|
|||
|
|
//this._schema = {}
|
|||
|
|
//this._lists = {}
|
|||
|
|
|
|||
|
|
this.form
|
|||
|
|
|
|||
|
|
// temp
|
|||
|
|
this._fields = {}
|
|||
|
|
|
|||
|
|
if (!opt.formContainer) {
|
|||
|
|
throw new Error(`formContainer option is required`)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (opt.formContainer instanceof Element) {
|
|||
|
|
this._formContainer = opt.formContainer
|
|||
|
|
} else {
|
|||
|
|
let formContainer = document.getElementById(opt.formContainer)
|
|||
|
|
if (!formContainer) {
|
|||
|
|
throw new Error(`form container '${opt.formContainer}' not found`)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this._formContainer = formContainer
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
let form = {
|
|||
|
|
isEditable: opt.isEditable || false,
|
|||
|
|
schema: this._schema,
|
|||
|
|
fields: {}, // для ошибок,
|
|||
|
|
//loadingCount: 0
|
|||
|
|
selected: {},
|
|||
|
|
dirty: {}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//this._schema = this._buildTree(opt.schema, opt.form)
|
|||
|
|
this._schema = this._buildTree(opt.schema, form)
|
|||
|
|
|
|||
|
|
this.form = form
|
|||
|
|
this.form.schema = this._schema
|
|||
|
|
|
|||
|
|
//console.log('SCHEMA:', this._schema)
|
|||
|
|
|
|||
|
|
//this._createSchemaObjects(this._schema)
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
// fix - return ?
|
|||
|
|
//this._createMainForm(this._schema, formContainer, opt.btnSubmitText)
|
|||
|
|
|
|||
|
|
// add submit btn to top form
|
|||
|
|
|
|||
|
|
//console.log(this._schema)
|
|||
|
|
//this._loadDataForCustomElems(this._schema)
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
let str = localStorage.getItem('x')
|
|||
|
|
if (str) {
|
|||
|
|
let s
|
|||
|
|
try {
|
|||
|
|
s = JSON.parse(str)
|
|||
|
|
} catch (e) {
|
|||
|
|
console.log(e)
|
|||
|
|
console.log(str)
|
|||
|
|
}
|
|||
|
|
console.log('localStore:', s.obj)
|
|||
|
|
|
|||
|
|
this._restore(s)
|
|||
|
|
}
|
|||
|
|
*/
|
|||
|
|
//console.log(this)
|
|||
|
|
//this.init()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
Form.prototype = Object.create(Emiter.prototype)
|
|||
|
|
Form.prototype.constructor = Form
|
|||
|
|
|
|||
|
|
Form.EDITABLE = 1 // стандартное поле
|
|||
|
|
Form.UNEDITABLE = 2 // активно при добавлении, отключено при редактировании
|
|||
|
|
Form.READONLY = 3 // всегда отключено, например ID, передается на сервер
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
Form.E = 1 // editable
|
|||
|
|
Form.R = 2 // readonly
|
|||
|
|
Form.D = 3 // disabled
|
|||
|
|
|
|||
|
|
Form.S = 4 // standard
|
|||
|
|
Form.C = 5 // custom
|
|||
|
|
Form.L = 6 // list
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
Form.prototype.init = function() {
|
|||
|
|
let loader = new Loader({
|
|||
|
|
api: this._api,
|
|||
|
|
t: this.form, // пустой dirty
|
|||
|
|
context: this._context,
|
|||
|
|
onError: err => {
|
|||
|
|
//console.log('Loader.err: ', err)
|
|||
|
|
},
|
|||
|
|
onLoaded:() => {
|
|||
|
|
this._createMainForm()
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
loader.load()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Form.prototype.onRemove = function(func) {
|
|||
|
|
this._onRemove = func
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Form.prototype.onSubmit = function(func) {
|
|||
|
|
this._onSubmit = func
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ВАЖНО!
|
|||
|
|
// Вложенные коллекции (в chains или tree) рекурсивно искать сложно, поэтому
|
|||
|
|
// в момент создания объектов нужно сохранить на них ссылки, а в момент reset - удалить.
|
|||
|
|
Form.prototype.showError = function(err) {
|
|||
|
|
this._showError(this.form.fields, err)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/////////////// FIX
|
|||
|
|
// сделать map из форм
|
|||
|
|
Form.prototype._showError = function(fields, err) {
|
|||
|
|
if (this._error) {
|
|||
|
|
this._error.remove()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let path = []
|
|||
|
|
if (Array.isArray(err.path)) {
|
|||
|
|
path = err.path
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
while (true) {
|
|||
|
|
let step = path.shift()
|
|||
|
|
if (!step) {
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//if (!schema) {
|
|||
|
|
// throw new Error(`wrong path step ${JSON.stringify(step)}, not found`)
|
|||
|
|
//}
|
|||
|
|
|
|||
|
|
let p = fields[step.field]
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
schema.forEach(x => {
|
|||
|
|
if (x.name == step.field) {
|
|||
|
|
p = x
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
if (!p || p.type != 'list') {
|
|||
|
|
throw new Error(`wrong path step ${JSON.stringify(step)}`)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (p.arr.length <= step.idx) {
|
|||
|
|
throw new Error(`wrong path step ${JSON.stringify(step)}, idx out of range`)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!p.isFormOpened) {
|
|||
|
|
this._select(p, p.arr[step.idx])
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
fields = p.fields
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let anchor; //, found = false
|
|||
|
|
|
|||
|
|
if (err.field) {
|
|||
|
|
//schema.forEach(p => {
|
|||
|
|
// if (p.name == err.field) {
|
|||
|
|
let p = fields[err.field]
|
|||
|
|
|
|||
|
|
//console.log('ERR FIELD:', p)
|
|||
|
|
|
|||
|
|
if (p) {
|
|||
|
|
switch (p.type) {
|
|||
|
|
case '':
|
|||
|
|
// для скрытых элементов anchor не определяем
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
case 'line':
|
|||
|
|
case 'text':
|
|||
|
|
case 'select':
|
|||
|
|
anchor = p.elem
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
case 'checkbox':
|
|||
|
|
anchor = p.labelElem
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
case 'checkboxes':
|
|||
|
|
case 'radios':
|
|||
|
|
case 'model':
|
|||
|
|
anchor = p.cover
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
case 'list':
|
|||
|
|
anchor = p.btnDiv
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
throw new Error(`field '${err.field}' not found`)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
} else {
|
|||
|
|
// FIX
|
|||
|
|
//let p = schema[err.list]
|
|||
|
|
|
|||
|
|
//anchor = p.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._btnSubmit,
|
|||
|
|
err: err,
|
|||
|
|
class: this._errorClass
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
Form.prototype._buildTree = function(schemaConfig, form) {
|
|||
|
|
let schema = []
|
|||
|
|
|
|||
|
|
if (Array.isArray(schemaConfig)) {
|
|||
|
|
schemaConfig.forEach(v => {
|
|||
|
|
let f;
|
|||
|
|
if (v.type == 'list') {
|
|||
|
|
f = this._buildList(v, form)
|
|||
|
|
} else {
|
|||
|
|
f = this._buildField(v, form)
|
|||
|
|
}
|
|||
|
|
schema.push(f)
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//console.log('returnned tree:', opt, r)
|
|||
|
|
return schema
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Цель build функций - пройтись ко конфигурации схемы на всю глубину, провалидировать
|
|||
|
|
// описание каждого свойства формы, создать внутренние структуры, описывающие свойства
|
|||
|
|
// формы.
|
|||
|
|
// Никаких манипуляций с DOM данные функции не производят. Для fork случаев создание
|
|||
|
|
// schema элементов с нуля после действий пользователя.
|
|||
|
|
//Form.prototype._buildTree = function(schemaConfig, formId) {
|
|||
|
|
Form.prototype._buildField = function(v, form) {
|
|||
|
|
let type = v.type
|
|||
|
|
if (!type) {
|
|||
|
|
type = ''
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let f = {
|
|||
|
|
type: type,
|
|||
|
|
label: v.label,
|
|||
|
|
comment: v.comment,
|
|||
|
|
placeholder: v.placeholder,
|
|||
|
|
dataType: v.dataType || 'str',
|
|||
|
|
name: v.name,
|
|||
|
|
key: v.key,
|
|||
|
|
scalar: v.scalar,
|
|||
|
|
get: v.get,
|
|||
|
|
set: v.set,
|
|||
|
|
data: v.data,
|
|||
|
|
getData: v.getData,
|
|||
|
|
getName: v.getName, // fix
|
|||
|
|
loadFunc: v.loadFunc,
|
|||
|
|
loadFuncContextParam: v.loadFuncContextParam,
|
|||
|
|
chains: [],
|
|||
|
|
virtual: v.virtual, // вирутальное поле, например RadioList, в form.elements не проверять
|
|||
|
|
manage: true,
|
|||
|
|
onChange: v.onChange,
|
|||
|
|
cache: new Map(),
|
|||
|
|
getLoadFuncArg: v.getLoadFuncArg,
|
|||
|
|
//requestModifier: v.requestModifier,
|
|||
|
|
responseModifier: v.responseModifier,
|
|||
|
|
form: form,
|
|||
|
|
modelOptions: v.modelOptions,
|
|||
|
|
modelType: v.modelType,
|
|||
|
|
modelChangeType: v.modelChangeType,
|
|||
|
|
modelSelectType: v.modelSelectType,
|
|||
|
|
contextVar: v.contextVar,
|
|||
|
|
width: v.width,
|
|||
|
|
//height: v.height,
|
|||
|
|
rows: v.rows,
|
|||
|
|
cols: v.cols,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (v.noCache === true) {
|
|||
|
|
f.caching = false
|
|||
|
|
} else {
|
|||
|
|
f.caching = true
|
|||
|
|
}
|
|||
|
|
// FIX
|
|||
|
|
/*
|
|||
|
|
if (v.key) {
|
|||
|
|
f.key = v.key
|
|||
|
|
} else {
|
|||
|
|
f.scalar = true
|
|||
|
|
}
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
//console.log('build:', f)
|
|||
|
|
|
|||
|
|
if (v.getName) {
|
|||
|
|
if (typeof v.getName == 'string') {
|
|||
|
|
f.getName = obj => {
|
|||
|
|
return obj[v.getName]
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
f.getName = v.getName
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
f.getName = obj => obj
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
if (v.container) {
|
|||
|
|
f.containerId = v.container
|
|||
|
|
} else {
|
|||
|
|
f.containerId = formContainerId
|
|||
|
|
}
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
if (v.editmode) {
|
|||
|
|
// Если указан editmode - сперва проверяем значение на валидность
|
|||
|
|
if (v.editmode != Form.UNEDITABLE && v.editmode != Form.EDITABLE && v.editmode != Form.READONLY) {
|
|||
|
|
throw new Error(`Unknown '${v.name}' editmode: ${v.editmode}`)
|
|||
|
|
}
|
|||
|
|
// Если поле readonly - неважно какая форма - поле всегда отключено.
|
|||
|
|
if (v.editmode == Form.READONLY) {
|
|||
|
|
f.editmode = Form.READONLY
|
|||
|
|
} else {
|
|||
|
|
// Если форма редактируемая - поле может быть 2 видов
|
|||
|
|
if (form.isEditable) {
|
|||
|
|
f.editmode = v.editmode // editable / uneditable
|
|||
|
|
} else {
|
|||
|
|
// Если форма нередактируемая - поле только uneditable
|
|||
|
|
f.editmode = Form.UNEDITABLE
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
if (form.isEditable) {
|
|||
|
|
f.editmode = Form.EDITABLE
|
|||
|
|
} else {
|
|||
|
|
f.editmode = Form.UNEDITABLE
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
switch (type) {
|
|||
|
|
case '':
|
|||
|
|
case 'checkboxes':
|
|||
|
|
case 'radios':
|
|||
|
|
case 'select':
|
|||
|
|
case 'line':
|
|||
|
|
case 'text':
|
|||
|
|
case 'checkbox':
|
|||
|
|
//case 'custom-list':
|
|||
|
|
case 'model':
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
default:
|
|||
|
|
throw new Error(`unknown type in '${v.name}' schema property`)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (Array.isArray(v.chains)) {
|
|||
|
|
v.chains.forEach(x => {
|
|||
|
|
//console.log('CHAINS: =====>', JSON.stringify(x))
|
|||
|
|
let field = this._buildField(x, form)
|
|||
|
|
|
|||
|
|
f.chains.push(field)
|
|||
|
|
//let y = this._buildField(x)
|
|||
|
|
//console.log()
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (v.fork) {
|
|||
|
|
f.fork = this._buildFork(v.fork, form)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return f
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// c - config item, k - property name (key)
|
|||
|
|
// form - родительская форма - нужня в первую очередь для проверки isEditable
|
|||
|
|
Form.prototype._buildList = function(c, form) {
|
|||
|
|
//if (!c.btnSubmit) {
|
|||
|
|
// throw new Error(`list elem '${c.name}' must have 'btnSubmit' option`)
|
|||
|
|
//}
|
|||
|
|
let f = {
|
|||
|
|
type: 'list',
|
|||
|
|
label: c.label,
|
|||
|
|
name: c.name,
|
|||
|
|
isEditable: c.isEditable || false,
|
|||
|
|
//containerId: c.container,
|
|||
|
|
//btnSubmitId: c.btnSubmit,
|
|||
|
|
//btnHideId: c.btnHide,
|
|||
|
|
//formOpts: c.form,
|
|||
|
|
//templateOpts: c.template,
|
|||
|
|
//view: c.view,
|
|||
|
|
activeClass: c.activeClass || 'selected',
|
|||
|
|
//sort: c.sort,
|
|||
|
|
validate: c.validate,
|
|||
|
|
//parentFormId: formId,
|
|||
|
|
//formId: c.form,
|
|||
|
|
showForm: c.showForm,
|
|||
|
|
hideForm: c.hideForm,
|
|||
|
|
formContainerClass: c.formContainerClass,
|
|||
|
|
//btnAddText: c.btnAddText,
|
|||
|
|
//btnUpdateText: c.btnUpdateText,
|
|||
|
|
//formContainer: document.getElementById(c.formContainer),
|
|||
|
|
emptyListMsg: c.emptyListMsg || '',
|
|||
|
|
form: form,
|
|||
|
|
fields: {},
|
|||
|
|
loadingCount: 0
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Если родительская форма нередактируемая, то все вложенные тоже
|
|||
|
|
if (!form.isEditable) {
|
|||
|
|
f.isEditable = false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!c.getItemName) {
|
|||
|
|
throw new Error(`getItemName option is required`)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (typeof c.getItemName == 'string') {
|
|||
|
|
f.getItemName = (obj) => {
|
|||
|
|
return obj[c.getItemName]
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
f.getItemName = c.getItemName
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// для всех schema элементов и ниже
|
|||
|
|
// FIX
|
|||
|
|
//if (!c.formContainer) {
|
|||
|
|
// throw new Error(`formContainer option is required for list ${JSON.stringify(c)}`)
|
|||
|
|
//}
|
|||
|
|
f.formContainerId = c.formContainer
|
|||
|
|
|
|||
|
|
f.formContainer = document.getElementById(c.formContainer)
|
|||
|
|
|
|||
|
|
// Для массива
|
|||
|
|
//if (!c.itemContainer) {
|
|||
|
|
// throw new Error(`itemContainer option is required for list ${JSON.stringify(c)}`)
|
|||
|
|
//}
|
|||
|
|
//f.itemContainerId = c.itemContainer
|
|||
|
|
|
|||
|
|
// для самого поля в родительской форме
|
|||
|
|
//if (c.container) {
|
|||
|
|
// f.containerId = c.container
|
|||
|
|
//} else {
|
|||
|
|
// f.containerId = parentFormContainerId
|
|||
|
|
//}
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
if (c.editmode) {
|
|||
|
|
f.editmode = c.editmode
|
|||
|
|
|
|||
|
|
if (f.editmode != Form.UNEDITABLE && f.editmode != Form.EDITABLE && f.editmode != Form.READONLY) {
|
|||
|
|
throw new Error(`Unknown '${c.name}' editmode: ${c.editmode}`)
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
f.editmode = Form.EDITABLE
|
|||
|
|
}
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
if (c.showForm) {
|
|||
|
|
f.showForm = () => {
|
|||
|
|
if (f.selected) {
|
|||
|
|
console.log('select')
|
|||
|
|
this._select(f) // снимаем выделение
|
|||
|
|
}
|
|||
|
|
c.showForm()
|
|||
|
|
f.
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
|
|||
|
|
f.schema = this._buildTree(c.schema, f)
|
|||
|
|
|
|||
|
|
return f
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
Form.prototype._getObjectValue = function(p) {
|
|||
|
|
let clean, dirty
|
|||
|
|
|
|||
|
|
switch (p.type) {
|
|||
|
|
case 'checkboxes':
|
|||
|
|
clean = [] // массив ID
|
|||
|
|
dirty = [] // массив объектов
|
|||
|
|
|
|||
|
|
let key = p.name
|
|||
|
|
if (p.key) {
|
|||
|
|
key = p.key
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
p.elems.forEach((x, idx) => {
|
|||
|
|
if (x.checked) {
|
|||
|
|
let v = p.valueObjects[idx]
|
|||
|
|
dirty.push(v)
|
|||
|
|
|
|||
|
|
if (p.scalar) {
|
|||
|
|
clean.push(v)
|
|||
|
|
} else {
|
|||
|
|
clean.push(v[key])
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
//upd[p.name] = checkedIDs
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
case 'radios':
|
|||
|
|
if (Array.isArray(p.elems)) {
|
|||
|
|
for (let i=0; i<p.elems.length; i++) {
|
|||
|
|
if (p.elems[i].checked) {
|
|||
|
|
dirty = p.valueObjects[i]
|
|||
|
|
if (p.scalar) {
|
|||
|
|
clean = dirty
|
|||
|
|
} else {
|
|||
|
|
clean = dirty[p.name]
|
|||
|
|
}
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//upd[p.name] = dirty
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
case 'select':
|
|||
|
|
// отнимаем пустой option
|
|||
|
|
//console.log('SELECTED INDEX:', p, p.elem.selectedIndex)
|
|||
|
|
|
|||
|
|
// Если элементы еще не загрузились по сети
|
|||
|
|
if (Array.isArray(p.valueObjects) && p.valueObjects.length > 0) {
|
|||
|
|
dirty = p.valueObjects[p.elem.selectedIndex - 1]
|
|||
|
|
}
|
|||
|
|
if (dirty !== undefined) {
|
|||
|
|
if (p.scalar) {
|
|||
|
|
clean = dirty
|
|||
|
|
} else {
|
|||
|
|
clean = dirty[p.name]
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
case 'checkbox':
|
|||
|
|
dirty = p.elem.checked
|
|||
|
|
// fix - объекты вместо скаляра?
|
|||
|
|
//upd[p.name] = dirty
|
|||
|
|
clean = dirty
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
case '':
|
|||
|
|
//if (p.contextVar) {
|
|||
|
|
// dirty = this._context[p.contextVar]
|
|||
|
|
//clean = this._context[p.contextVar]
|
|||
|
|
//} else {
|
|||
|
|
dirty = p.value
|
|||
|
|
clean = p.value
|
|||
|
|
//}
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
case 'line':
|
|||
|
|
case 'text':
|
|||
|
|
dirty = p.elem.value
|
|||
|
|
|
|||
|
|
// Обрезаем пробелы
|
|||
|
|
if (typeof dirty == 'string') {
|
|||
|
|
dirty = dirty.trim()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let v;
|
|||
|
|
|
|||
|
|
// Важно для расчета signature. Ибо до того, как напечатали в поле ввода символ
|
|||
|
|
// - в signature нет такого property, а когда символ напечатали и удалили, - в
|
|||
|
|
// signature появится property c пустой строкой или нулем
|
|||
|
|
if (dirty == '') {
|
|||
|
|
v = undefined
|
|||
|
|
} else {
|
|||
|
|
switch (p.dataType) {
|
|||
|
|
case 'int':
|
|||
|
|
v = parseInt(dirty) || 0
|
|||
|
|
break
|
|||
|
|
case 'float':
|
|||
|
|
v = parseFloat(dirty) || 0
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
case 'str':
|
|||
|
|
v = dirty || ''
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
case 'hex':
|
|||
|
|
try {
|
|||
|
|
v = hexToBytes(dirty)
|
|||
|
|
} catch (e) {
|
|||
|
|
v = ''
|
|||
|
|
}
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
default:
|
|||
|
|
throw new Error(`Unknown type: ${p.dataType}`)
|
|||
|
|
}
|
|||
|
|
if (p.get) {
|
|||
|
|
v = p.get(v)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
dirty = v
|
|||
|
|
clean = v
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
//case 'custom-list':
|
|||
|
|
//return p.custom.getValues()
|
|||
|
|
|
|||
|
|
case 'model':
|
|||
|
|
if (p.modelChangeType == 'radio') {
|
|||
|
|
dirty = p.model.getSelected(p.modelSelectType)
|
|||
|
|
} else {
|
|||
|
|
dirty = p.model.listChecked(p.modelSelectType)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//console.log('DIRTY model object:', dirty)
|
|||
|
|
|
|||
|
|
if (dirty !== undefined) {
|
|||
|
|
if (p.scalar) {
|
|||
|
|
clean = dirty
|
|||
|
|
} else {
|
|||
|
|
clean = dirty[p.name]
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
default:
|
|||
|
|
throw new Error(`bug: unknown property type '${p.type}'`)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
dirty: dirty,
|
|||
|
|
clean: clean
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Form.prototype._disableField = function(p) {
|
|||
|
|
//console.log('disablefield')
|
|||
|
|
if (p.type == 'list') {
|
|||
|
|
p.arr.forEach(item => {
|
|||
|
|
item.checkbox.disabled = true
|
|||
|
|
})
|
|||
|
|
p.btnShow.hidden = true
|
|||
|
|
} else if (p.type == 'model') {
|
|||
|
|
p.model.disable()
|
|||
|
|
} else if (p.elem) {
|
|||
|
|
p.elem.disabled = true
|
|||
|
|
} else if (p.elems) {
|
|||
|
|
p.elems.forEach(elem => {
|
|||
|
|
elem.disabled = true
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
Form.prototype._createField = function(p, container) {
|
|||
|
|
//if (p.type == '') {
|
|||
|
|
// return
|
|||
|
|
//}
|
|||
|
|
//console.log('_createField...', p.name, p.type)
|
|||
|
|
let control
|
|||
|
|
let contextValue
|
|||
|
|
|
|||
|
|
let labelText = p.name
|
|||
|
|
if (p.label) {
|
|||
|
|
labelText = p.label
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//console.log('_createField: data=', p.data)
|
|||
|
|
|
|||
|
|
let div = document.createElement('div')
|
|||
|
|
div.setAttribute('class', 'form-group')
|
|||
|
|
|
|||
|
|
p.cover = div
|
|||
|
|
|
|||
|
|
let label = document.createElement('label')
|
|||
|
|
label.textContent = labelText
|
|||
|
|
label.classList.add(_labelClass)
|
|||
|
|
|
|||
|
|
switch (p.type) {
|
|||
|
|
case '':
|
|||
|
|
//p.cover = document.createElement('div')
|
|||
|
|
|
|||
|
|
container.appendChild(p.cover)
|
|||
|
|
|
|||
|
|
|
|||
|
|
// FIX - генерация события?
|
|||
|
|
if (p.contextVar) {
|
|||
|
|
contextValue = this._context[p.contextVar]
|
|||
|
|
|
|||
|
|
if (contextValue !== undefined) {
|
|||
|
|
p.value = contextValue
|
|||
|
|
|
|||
|
|
this.form.selected[p.name] = contextValue
|
|||
|
|
} else {
|
|||
|
|
delete this.form.selected[p.name]
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
case 'checkboxes':
|
|||
|
|
div.style.display = 'flex'
|
|||
|
|
div.style.flexDirection = 'column'
|
|||
|
|
|
|||
|
|
div.appendChild(label)
|
|||
|
|
|
|||
|
|
p.optionContainer = div
|
|||
|
|
p.elems = []
|
|||
|
|
|
|||
|
|
// FIX - генерация события?
|
|||
|
|
if (p.contextVar) {
|
|||
|
|
contextValue = this._context[p.contextVar]
|
|||
|
|
this._fillOptionsField(p, contextValue)
|
|||
|
|
} else if (p.data) {
|
|||
|
|
this._fillOptionsField(p, p.data)
|
|||
|
|
} else if (p.getData) {
|
|||
|
|
this._fillOptionsField(p, p.getData())
|
|||
|
|
} else if (p.caching) {
|
|||
|
|
let cached = p.cache.get()
|
|||
|
|
if (cached) {
|
|||
|
|
this._fillOptionsField(p, cached)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
container.appendChild(div)
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
case 'radios':
|
|||
|
|
div.style.display = 'flex'
|
|||
|
|
div.style.flexDirection = 'column'
|
|||
|
|
|
|||
|
|
div.appendChild(label)
|
|||
|
|
|
|||
|
|
p.optionContainer = div
|
|||
|
|
p.elems = []
|
|||
|
|
|
|||
|
|
// FIX - генерация события?
|
|||
|
|
if (p.contextVar) {
|
|||
|
|
contextValue = this._context[p.contextVar]
|
|||
|
|
this._fillOptionsField(p, contextValue)
|
|||
|
|
} else if (p.data) {
|
|||
|
|
this._fillOptionsField(p, p.data)
|
|||
|
|
} else if (p.getData) {
|
|||
|
|
this._fillOptionsField(p, p.getData())
|
|||
|
|
} else if (p.caching) {
|
|||
|
|
let cached = p.cache.get()
|
|||
|
|
if (cached) {
|
|||
|
|
this._fillOptionsField(p, cached)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//console.log('RADIOs CREATED', p.elems)
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
container.appendChild(div)
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
case 'select':
|
|||
|
|
control = document.createElement('select')
|
|||
|
|
control.setAttribute('class', 'form-control')
|
|||
|
|
|
|||
|
|
if (p.width) {
|
|||
|
|
control.style.width = p.width
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (p.className) {
|
|||
|
|
control.classList.add(p.className)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
p.elem = control
|
|||
|
|
|
|||
|
|
div.appendChild(label)
|
|||
|
|
div.appendChild(control)
|
|||
|
|
|
|||
|
|
if (p.comment) {
|
|||
|
|
// FIX <small class="form-text text-muted">от 15 секунд и выше</small>
|
|||
|
|
let small = document.createElement('small')
|
|||
|
|
small.classList.add('form-text', 'text-muted')
|
|||
|
|
small.textContent = p.comment
|
|||
|
|
|
|||
|
|
div.appendChild(small)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// FIX - генерация события?
|
|||
|
|
if (p.contextVar) {
|
|||
|
|
contextValue = this._context[p.contextVar]
|
|||
|
|
this._fillOptionsField(p, contextValue)
|
|||
|
|
} else if (p.data) {
|
|||
|
|
this._fillOptionsField(p, p.data)
|
|||
|
|
} else if (p.getData) {
|
|||
|
|
this._fillOptionsField(p, p.getData())
|
|||
|
|
} else if (p.caching) {
|
|||
|
|
let cached;
|
|||
|
|
//console.log(p.cache, p.loadFuncContextParam)
|
|||
|
|
if (p.getLoadFuncArg) {
|
|||
|
|
let arg = p.getLoadFuncArg(p.form.selected)
|
|||
|
|
cached = p.cache.get(arg)
|
|||
|
|
} else {
|
|||
|
|
cached = p.cache.get()
|
|||
|
|
}
|
|||
|
|
//console.log(cached)
|
|||
|
|
if (cached) {
|
|||
|
|
this._fillOptionsField(p, cached)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
p.elem.addEventListener('change', () => {
|
|||
|
|
this._onElemEvent(p)
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
container.appendChild(div)
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
case 'line':
|
|||
|
|
control = document.createElement('input')
|
|||
|
|
control.setAttribute('type', 'text')
|
|||
|
|
control.setAttribute('class', 'form-control')
|
|||
|
|
|
|||
|
|
if (p.width) {
|
|||
|
|
control.style.width = p.width
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (p.className) {
|
|||
|
|
control.classList.add(p.className)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (p.placeholder) {
|
|||
|
|
control.setAttribute('placeholder', p.placeholder)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
control.addEventListener('input', () => {
|
|||
|
|
this._onElemEvent(p)
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
div.appendChild(label)
|
|||
|
|
div.appendChild(control)
|
|||
|
|
|
|||
|
|
if (p.comment) {
|
|||
|
|
// FIX <small class="form-text text-muted">от 15 секунд и выше</small>
|
|||
|
|
let small = document.createElement('small')
|
|||
|
|
small.classList.add('form-text', 'text-muted')
|
|||
|
|
small.textContent = p.comment
|
|||
|
|
|
|||
|
|
div.appendChild(small)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
container.appendChild(div)
|
|||
|
|
|
|||
|
|
p.elem = control
|
|||
|
|
//p.defaultValue = control.value
|
|||
|
|
|
|||
|
|
// FIX - генерация события?
|
|||
|
|
if (p.contextVar) {
|
|||
|
|
contextValue = this._context[p.contextVar]
|
|||
|
|
p.elem.value = contextValue
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
case 'text':
|
|||
|
|
control = document.createElement('textarea')
|
|||
|
|
control.setAttribute('class', 'form-control')
|
|||
|
|
|
|||
|
|
if (p.rows) {
|
|||
|
|
control.rows = p.rows
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (p.cols) {
|
|||
|
|
control.cols = p.cols
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (p.width) {
|
|||
|
|
control.style.width = p.width
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (p.className) {
|
|||
|
|
control.classList.add(p.className)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
control.addEventListener('input', () => {
|
|||
|
|
this._onElemEvent(p)
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
div.appendChild(label)
|
|||
|
|
div.appendChild(control)
|
|||
|
|
|
|||
|
|
if (p.comment) {
|
|||
|
|
// FIX <small class="form-text text-muted">от 15 секунд и выше</small>
|
|||
|
|
let small = document.createElement('small')
|
|||
|
|
small.classList.add('form-text', 'text-muted')
|
|||
|
|
small.textContent = p.comment
|
|||
|
|
|
|||
|
|
div.appendChild(small)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
container.appendChild(div)
|
|||
|
|
|
|||
|
|
p.elem = control
|
|||
|
|
//p.defaultValue = control.value
|
|||
|
|
|
|||
|
|
// FIX - генерация события?
|
|||
|
|
if (p.contextVar) {
|
|||
|
|
contextValue = this._context[p.contextVar]
|
|||
|
|
p.elem.value = contextValue
|
|||
|
|
}
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
case 'checkbox':
|
|||
|
|
control = document.createElement('input')
|
|||
|
|
control.setAttribute('type', 'checkbox')
|
|||
|
|
|
|||
|
|
|
|||
|
|
//console.log('CHECKBOX:', control, labelText)
|
|||
|
|
|
|||
|
|
//label.classList.remove(_labelClass)
|
|||
|
|
label.innerHTML = ''
|
|||
|
|
label.appendChild(control)
|
|||
|
|
label.appendChild(document.createTextNode(` ${labelText}`))
|
|||
|
|
|
|||
|
|
control.addEventListener('change', () => {
|
|||
|
|
this._onElemEvent(p)
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
div.appendChild(label)
|
|||
|
|
|
|||
|
|
container.appendChild(div)
|
|||
|
|
|
|||
|
|
p.elem = control
|
|||
|
|
p.labelElem = label
|
|||
|
|
|
|||
|
|
// FIX - генерация события?
|
|||
|
|
if (p.contextVar) {
|
|||
|
|
contextValue = this._context[p.contextVar]
|
|||
|
|
p.elem.value = contextValue
|
|||
|
|
}
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
//case 'custom-list':
|
|||
|
|
// p.custom.create()
|
|||
|
|
//break
|
|||
|
|
|
|||
|
|
case 'model':
|
|||
|
|
//label.style.borderBottom = "1px solid #ddd"
|
|||
|
|
//label.style.paddingBottom = "5px"
|
|||
|
|
//p.cover.appendChild(label)
|
|||
|
|
|
|||
|
|
p.modelOptions.forEach(type => {
|
|||
|
|
type.container = p.cover
|
|||
|
|
})
|
|||
|
|
p.model = new Model(p.modelOptions)
|
|||
|
|
|
|||
|
|
|
|||
|
|
// FIX - генерация события?
|
|||
|
|
if (p.contextVar) {
|
|||
|
|
contextValue = this._context[p.contextVar]
|
|||
|
|
this._fillOptionsField(p, contextValue)
|
|||
|
|
} else if (p.data) {
|
|||
|
|
this._fillOptionsField(p, p.data)
|
|||
|
|
} else if (p.getData) {
|
|||
|
|
this._fillOptionsField(p, p.getData())
|
|||
|
|
} else if (p.caching) {
|
|||
|
|
let cached;
|
|||
|
|
//console.log(p.cache, p.loadFuncContextParam)
|
|||
|
|
if (p.getLoadFuncArg) {
|
|||
|
|
let arg = p.getLoadFuncArg(p.form.selected)
|
|||
|
|
cached = p.cache.get(arg)
|
|||
|
|
} else {
|
|||
|
|
cached = p.cache.get()
|
|||
|
|
}
|
|||
|
|
//console.log(cached)
|
|||
|
|
if (cached) {
|
|||
|
|
this._fillOptionsField(p, cached)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
//if (p.changeType == 'few') {
|
|||
|
|
p.model.onChange = () => {
|
|||
|
|
this._onElemEvent(p)
|
|||
|
|
}
|
|||
|
|
//}
|
|||
|
|
//p.model
|
|||
|
|
|
|||
|
|
container.appendChild(p.cover)
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Отключаем readonly поля при генерации формы (генерация главной формы или
|
|||
|
|
// клик по ссылке "добавить")
|
|||
|
|
if (p.editmode == Form.READONLY && !p.loadFunc) {
|
|||
|
|
this._disableField(p)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
p.form.fields[p.name] = p
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// fix - if initedor not inited
|
|||
|
|
Form.prototype._removeField = function(p) {
|
|||
|
|
//console.log('_removeField:', p.name)
|
|||
|
|
let upd = {}
|
|||
|
|
|
|||
|
|
upd = Object.assign(upd, this._removeDependentFields(p))
|
|||
|
|
|
|||
|
|
if (p.type == 'list') {
|
|||
|
|
this._removeForm(p)
|
|||
|
|
|
|||
|
|
// Удаляем элементы связанные со списком items
|
|||
|
|
p.btnShow = undefined
|
|||
|
|
p.btnDiv = undefined
|
|||
|
|
p.btnRemove = undefined
|
|||
|
|
p.container = undefined
|
|||
|
|
p.arr = []
|
|||
|
|
|
|||
|
|
p.cover.remove()
|
|||
|
|
} else {
|
|||
|
|
if (p.optionContainer) {
|
|||
|
|
p.optionContainer = undefined
|
|||
|
|
}
|
|||
|
|
// checkbox
|
|||
|
|
if (p.labelElem) {
|
|||
|
|
p.labelElem = undefined
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//p.custom.reset()
|
|||
|
|
if (p.model) {
|
|||
|
|
p.model = undefined
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
p.elem = undefined
|
|||
|
|
|
|||
|
|
if (Array.isArray(p.elems)) {
|
|||
|
|
p.elems = []
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (p.cover) {
|
|||
|
|
p.cover.remove()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (p.selected) {
|
|||
|
|
p.selected = undefined
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Срос загружающихся в данный момент
|
|||
|
|
if (p.loading) {
|
|||
|
|
p.loading = false
|
|||
|
|
p.paramSignature = undefined
|
|||
|
|
p.form.loadingCount--
|
|||
|
|
this._loadingCount--
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
delete p.form.fields[p.name]
|
|||
|
|
|
|||
|
|
upd[p.name] = undefined
|
|||
|
|
|
|||
|
|
return upd
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
Form.prototype._buildFork = function(t, form) {
|
|||
|
|
if (!t) {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
let roads = new Map()
|
|||
|
|
|
|||
|
|
//console.log('build FORK:', t)
|
|||
|
|
|
|||
|
|
t.roads.forEach(b => {
|
|||
|
|
let road = {
|
|||
|
|
//templateId: b.templateId,
|
|||
|
|
//templates: b.templates,
|
|||
|
|
value: b.value,
|
|||
|
|
values: b.values,
|
|||
|
|
init: b.init,
|
|||
|
|
cleanup: b.cleanup,
|
|||
|
|
//schema: b.schema,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
road.schema = this._buildTree(b.schema, form)
|
|||
|
|
//branch = Object.assign(branch, tmp)
|
|||
|
|
|
|||
|
|
if (b.value !== undefined) {
|
|||
|
|
roads.set(b.value, road)
|
|||
|
|
} else if (Array.isArray(b.values)) {
|
|||
|
|
b.values.forEach(value => {
|
|||
|
|
roads.set(value, road)
|
|||
|
|
})
|
|||
|
|
} else {
|
|||
|
|
throw new Error(`'value' or 'values' option required in road settings`)
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
getValue: t.getValue,
|
|||
|
|
roads: roads,
|
|||
|
|
//container: t.container,
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
Form.prototype.getObj = function(schema) {
|
|||
|
|
let x = this._getObj(schema)
|
|||
|
|
//console.log('getObj:', x)
|
|||
|
|
return x
|
|||
|
|
}
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
Form.prototype._createMainForm = function() {
|
|||
|
|
//console.log('create main form')
|
|||
|
|
let schema = this._schema
|
|||
|
|
let formContainer = this._formContainer
|
|||
|
|
|
|||
|
|
formContainer.innerHTML = ''
|
|||
|
|
|
|||
|
|
let divFields = document.createElement('div')
|
|||
|
|
divFields.setAttribute('class', 'form-group')
|
|||
|
|
|
|||
|
|
let divButtons = document.createElement('div')
|
|||
|
|
divButtons.style.marginTop = '1.5rem'
|
|||
|
|
//divButtons.setAttribute('class', 'buttons-group')
|
|||
|
|
//divButtons.style.display = 'flex'
|
|||
|
|
//divButtons.style.justifyContent = 'space-between'
|
|||
|
|
|
|||
|
|
let btnSubmit = document.createElement('button')
|
|||
|
|
btnSubmit.setAttribute('class', 'btn btn-outline-success')
|
|||
|
|
btnSubmit.textContent = this._btnSubmitText
|
|||
|
|
//btnSubmit.disabled = true fix
|
|||
|
|
|
|||
|
|
this._btnSubmit = btnSubmit
|
|||
|
|
this._btnSubmit.addEventListener('click', () => {
|
|||
|
|
if (this._error) {
|
|||
|
|
this._error.remove()
|
|||
|
|
}
|
|||
|
|
//lett dirty = this.getObj(this._schema)
|
|||
|
|
|
|||
|
|
//let cleanData = this.cleanObj(dirty)
|
|||
|
|
//console.log('CLEAN FORM DATA:', this.form.selected)
|
|||
|
|
|
|||
|
|
|
|||
|
|
if (this._validate) {
|
|||
|
|
let err = this._validate(this.form.selected)
|
|||
|
|
if (err) {
|
|||
|
|
this.showError(err)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (this._onSubmit) {
|
|||
|
|
//this._onSubmit(this._clean)
|
|||
|
|
this._onSubmit(JSON.parse(JSON.stringify(this.form.selected)))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
if (this._api) {
|
|||
|
|
let func, successFunc
|
|||
|
|
|
|||
|
|
switch (this._mode) {
|
|||
|
|
case filling:
|
|||
|
|
func = this._submitFunc
|
|||
|
|
successFunc = this._onSubmitSuccess
|
|||
|
|
//console.log('filling: func:', func)
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
case editing:
|
|||
|
|
func = this._updateFunc
|
|||
|
|
successFunc = this._onUpdateSuccess
|
|||
|
|
//console.log('editing: func:', func)
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (func) {
|
|||
|
|
let snapshotStr = JSON.stringify({
|
|||
|
|
version: this._version,
|
|||
|
|
clean: this.form.selected,
|
|||
|
|
signature: this._signature,
|
|||
|
|
mode: this._mode,
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
this._api.req({
|
|||
|
|
func: func,
|
|||
|
|
//data: this._clean,
|
|||
|
|
data: this.form.selected,
|
|||
|
|
onError: err => {
|
|||
|
|
this.restore(snapshotStr)
|
|||
|
|
this.showError(err)
|
|||
|
|
},
|
|||
|
|
onSuccess: resp => {
|
|||
|
|
//this.reset()
|
|||
|
|
if (successFunc) {
|
|||
|
|
successFunc(resp)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
*/
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
divButtons.appendChild(btnSubmit)
|
|||
|
|
|
|||
|
|
let btnRemove = document.createElement('button')
|
|||
|
|
// ml-5 отступ
|
|||
|
|
btnRemove.setAttribute('class', 'btn btn-outline-danger ml-5')
|
|||
|
|
btnRemove.textContent = this._btnRemoveText
|
|||
|
|
btnRemove.hidden = true
|
|||
|
|
|
|||
|
|
this._btnRemove = btnRemove
|
|||
|
|
|
|||
|
|
btnRemove.addEventListener('click', () => {
|
|||
|
|
if (this._onRemove) {
|
|||
|
|
this._onRemove()
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
divButtons.appendChild(btnRemove)
|
|||
|
|
|
|||
|
|
this._createSchemaObjects(schema, {append: divFields})
|
|||
|
|
|
|||
|
|
formContainer.appendChild(divFields)
|
|||
|
|
formContainer.appendChild(divButtons)
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
//console.log('MAIN FORM CREATED!')
|
|||
|
|
|
|||
|
|
this._signature = JSON.stringify(this.form.selected)
|
|||
|
|
//this._initSignature = this._signature
|
|||
|
|
this._initSnapshot = JSON.stringify({
|
|||
|
|
version: this._version,
|
|||
|
|
clean: this.form.selected,
|
|||
|
|
signature: this._signature,
|
|||
|
|
mode: this._mode,
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
if (!this._enableSubmit) {
|
|||
|
|
this._btnSubmit.disabled = true
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//console.log("INIT SIGNATURE:", this._signature)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
Form.prototype._onFormDataChanged = function() {
|
|||
|
|
this._version++
|
|||
|
|
// рекурсивно обновляем родительскую коллекцию
|
|||
|
|
// Считаем подпись
|
|||
|
|
//let y = this.getObj(this._schema)
|
|||
|
|
let newSignature = JSON.stringify(this.form.selected)
|
|||
|
|
/////console.log('SIGNATURE:', newSignature)
|
|||
|
|
|
|||
|
|
if (this._signature != newSignature) {
|
|||
|
|
this._btnSubmit.disabled = false
|
|||
|
|
} else {
|
|||
|
|
if (!this._enableSubmit) {
|
|||
|
|
this._btnSubmit.disabled = true
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
//let snapshot =
|
|||
|
|
/*
|
|||
|
|
localStorage.setItem('x', JSON.stringify({
|
|||
|
|
signature: this._signature,
|
|||
|
|
obj: y,
|
|||
|
|
btnSubmit: this._btnSubmit.disabled,
|
|||
|
|
mode: this._mode
|
|||
|
|
}))
|
|||
|
|
*/
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
<div class="form-group">
|
|||
|
|
<label>ain devices</label>
|
|||
|
|
<div id="ainDevices"></div>
|
|||
|
|
|
|||
|
|
// remove + add
|
|||
|
|
<div class="form-group">
|
|||
|
|
<button id="add-ain-device" class="btn btn-link">добавить</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
|
|||
|
|
Form.prototype._createForm = function(schema, formContainer) {
|
|||
|
|
//console.log('CREATE:', formContainer)
|
|||
|
|
formContainer.innerHTML = ''
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
let divFields = document.createElement('div')
|
|||
|
|
divFields.setAttribute('class', 'form-group')
|
|||
|
|
|
|||
|
|
let divButtons = document.createElement('div')
|
|||
|
|
divButtons.setAttribute('class', 'buttons-group')
|
|||
|
|
|
|||
|
|
let btnSubmit = document.createElement('button')
|
|||
|
|
btnSubmit.setAttribute('class', 'btn')
|
|||
|
|
btnSubmit.textContent = btnAddText
|
|||
|
|
|
|||
|
|
let btnClose = document.createElement('button')
|
|||
|
|
btnClose.setAttribute('class', 'btn btn-link')
|
|||
|
|
btnClose.textContent = 'close'
|
|||
|
|
|
|||
|
|
divButtons.appendChild(btnSubmit)
|
|||
|
|
divButtons.appendChild(btnClose)
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
this._createSchemaObjects(schema, {append: formContainer})
|
|||
|
|
|
|||
|
|
//formContainer.appendChild(divFields)
|
|||
|
|
//formContainer.appendChild(divButtons)
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
fieldsContainer: formContainer,
|
|||
|
|
//btnSubmit: btnSubmit,
|
|||
|
|
//btnClose: btnClose
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//Form.prototype._createSchemaObjects = function(formId, schema, formContainer) {
|
|||
|
|
Form.prototype._createSchemaObjects = function(schema, anchors) {
|
|||
|
|
//console.log('_createSchemaObjects...')
|
|||
|
|
|
|||
|
|
let container;
|
|||
|
|
if (anchors.append) {
|
|||
|
|
container = anchors.append
|
|||
|
|
} else {
|
|||
|
|
container = document.createDocumentFragment()
|
|||
|
|
}
|
|||
|
|
//console.log('create objects:', formId) //, schema)
|
|||
|
|
schema.forEach(p => {
|
|||
|
|
//let container = document.getElementById(p.containerId)
|
|||
|
|
//if (!container) {
|
|||
|
|
// throw new Error(`container '${p.containerId}' not found`)
|
|||
|
|
//}
|
|||
|
|
|
|||
|
|
if (p.type == 'list') {
|
|||
|
|
this._createList(p, container)
|
|||
|
|
} else {
|
|||
|
|
this._createField(p, container)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
if (anchors.after) {
|
|||
|
|
anchors.after.parentNode.insertBefore(container, anchors.after.nextSibling)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
//this._onAnyChange()
|
|||
|
|
//this._calcSignature()
|
|||
|
|
|
|||
|
|
|
|||
|
|
//console.log('_createSchemaObjects:', opt)
|
|||
|
|
// Загружаем все AJAX-элементы топ уровня (которые ни от кого не зависят)
|
|||
|
|
// fix
|
|||
|
|
/*
|
|||
|
|
this._chains.forEach(c => {
|
|||
|
|
if (!c.func) {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
this._loadData(item)
|
|||
|
|
})
|
|||
|
|
*/
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Заполняем поле вариантами на выбор
|
|||
|
|
Form.prototype._fillOptionsField = function(p, list) {
|
|||
|
|
switch (p.type) {
|
|||
|
|
case 'select':
|
|||
|
|
p.elem.innerHTML = ''
|
|||
|
|
//console.log('innerHTML')
|
|||
|
|
// пустой option
|
|||
|
|
p.elem.appendChild(document.createElement('option'))
|
|||
|
|
//console.log('append empty option')
|
|||
|
|
|
|||
|
|
if (Array.isArray(list)) {
|
|||
|
|
list.forEach(x => {
|
|||
|
|
let value
|
|||
|
|
|
|||
|
|
if (p.scalar) {
|
|||
|
|
value = x
|
|||
|
|
} else {
|
|||
|
|
value = x[p.name]
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let option = document.createElement('option')
|
|||
|
|
option.innerHTML = p.getName(x)
|
|||
|
|
option.value = value
|
|||
|
|
|
|||
|
|
p.elem.appendChild(option)
|
|||
|
|
|
|||
|
|
//console.log('append value option')
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
case 'checkboxes':
|
|||
|
|
if (Array.isArray(list)) {
|
|||
|
|
list.forEach(x => {
|
|||
|
|
let cb = document.createElement('input')
|
|||
|
|
cb.setAttribute('type', 'checkbox')
|
|||
|
|
|
|||
|
|
let cbLabel = document.createElement('label')
|
|||
|
|
//cbLabel.style.paddingTop = '5px'
|
|||
|
|
//cbLabel.style.paddingBottom = '5px'
|
|||
|
|
|
|||
|
|
cbLabel.appendChild(cb)
|
|||
|
|
cbLabel.appendChild(document.createTextNode(` ${p.getName(x)}`))
|
|||
|
|
|
|||
|
|
p.optionContainer.appendChild(cbLabel)
|
|||
|
|
|
|||
|
|
p.elems.push(cb)
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
p.elems.forEach(checkbox => {
|
|||
|
|
checkbox.addEventListener('change', () => {
|
|||
|
|
this._onElemEvent(p)
|
|||
|
|
})
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
case 'radios':
|
|||
|
|
if (Array.isArray(list)) {
|
|||
|
|
list.forEach(x => {
|
|||
|
|
let cb = document.createElement('input')
|
|||
|
|
cb.setAttribute('type', 'radio')
|
|||
|
|
cb.setAttribute('name', p.name)
|
|||
|
|
|
|||
|
|
//cb.addEventListener('click', () => {
|
|||
|
|
// this._onElemEvent(p, x) // fix
|
|||
|
|
//})
|
|||
|
|
|
|||
|
|
let cbLabel = document.createElement('label')
|
|||
|
|
//cbLabel.style.paddingTop = '5px'
|
|||
|
|
//cbLabel.style.paddingBottom = '5px'
|
|||
|
|
|
|||
|
|
cbLabel.appendChild(cb)
|
|||
|
|
cbLabel.appendChild(document.createTextNode(` ${p.getName(x)}`))
|
|||
|
|
|
|||
|
|
p.optionContainer.appendChild(cbLabel)
|
|||
|
|
|
|||
|
|
p.elems.push(cb)
|
|||
|
|
//p.selected = undefined
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
p.elems.forEach(radio => {
|
|||
|
|
radio.addEventListener('click', () => {
|
|||
|
|
//console.log('RADIO CLICKED', p.selected)
|
|||
|
|
if (p.selected) {
|
|||
|
|
if (p.selected == radio) {
|
|||
|
|
radio.checked = false
|
|||
|
|
p.selected = undefined
|
|||
|
|
} else {
|
|||
|
|
p.selected.checked = false
|
|||
|
|
p.selected = radio
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
radio.checked = true
|
|||
|
|
p.selected = radio
|
|||
|
|
}
|
|||
|
|
//console.log('RADIOS ON ELEM EVENT')
|
|||
|
|
this._onElemEvent(p)
|
|||
|
|
})
|
|||
|
|
})
|
|||
|
|
//console.log('RADIO ELEMS:', p.elems)
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
//case 'custom-list':
|
|||
|
|
// p.custom.setList(list)
|
|||
|
|
// / break
|
|||
|
|
|
|||
|
|
case 'model':
|
|||
|
|
//console.log('FILL OPTION FIELDS', list)
|
|||
|
|
p.model.add(p.modelType, list)
|
|||
|
|
//if (Array.isArray(list)) {
|
|||
|
|
// list.forEach(x => {
|
|||
|
|
|
|||
|
|
//})
|
|||
|
|
//}
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
default:
|
|||
|
|
throw new Error(`bug: unknown property type ${p.type}`)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
p.valueObjects = list
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
Form.prototype._fillCheckboxes = function(p, list) {
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
Form.prototype._fillRadios = function(p, list) {
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
Form.prototype._createList = function(p, fieldsContainer) {
|
|||
|
|
//console.log('create list')
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
let container = document.getElementById(p.containerId)
|
|||
|
|
if (!container) {
|
|||
|
|
throw new Error(`container '${p.containerId}' not found`)
|
|||
|
|
}
|
|||
|
|
*/
|
|||
|
|
let labelText = p.name
|
|||
|
|
if (p.label) {
|
|||
|
|
labelText = p.label
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let div = document.createElement('div')
|
|||
|
|
div.setAttribute('class', 'form-group')
|
|||
|
|
//div.style.border = '1px solid #ddd'
|
|||
|
|
|
|||
|
|
p.cover = div
|
|||
|
|
|
|||
|
|
let label = document.createElement('label')
|
|||
|
|
label.setAttribute('class', 'list-header')
|
|||
|
|
label.textContent = labelText
|
|||
|
|
label.style.borderBottom = '1px solid #ddd'
|
|||
|
|
label.style.paddingBottom = '5px'
|
|||
|
|
|
|||
|
|
label.classList.add(_labelClass)
|
|||
|
|
|
|||
|
|
let arrContainer = document.createElement('div')
|
|||
|
|
//arrContainer.setAttribute('class', 'form-group')
|
|||
|
|
|
|||
|
|
arrContainer.textContent = p.emptyListMsg
|
|||
|
|
|
|||
|
|
p.container = arrContainer
|
|||
|
|
|
|||
|
|
|
|||
|
|
let btnWrapper = document.createElement('div')
|
|||
|
|
btnWrapper.setAttribute('class', 'form-group')
|
|||
|
|
|
|||
|
|
//let btn = document.createElement('button')
|
|||
|
|
//btn.textContent = 'добавить...'
|
|||
|
|
//btn.setAttribute('class', 'btn btn-link')
|
|||
|
|
|
|||
|
|
let btnDiv = document.createElement('div')
|
|||
|
|
btnDiv.setAttribute('class', 'show-link')
|
|||
|
|
btnDiv.style.marginTop = '0.75rem'
|
|||
|
|
|
|||
|
|
let btn = document.createElement('a')
|
|||
|
|
btn.textContent = '+ добавить'
|
|||
|
|
btn.setAttribute('href', '#')
|
|||
|
|
|
|||
|
|
|
|||
|
|
let addBtnDiv = document.createElement('div')
|
|||
|
|
//addBtnDiv.style.marginTop = '0.5rem'
|
|||
|
|
//addBtnDiv.style.marginBottom = '0.5rem'
|
|||
|
|
|
|||
|
|
addBtnDiv.appendChild(btn)
|
|||
|
|
|
|||
|
|
//console.log('btn', btn)
|
|||
|
|
|
|||
|
|
btnDiv.appendChild(addBtnDiv)
|
|||
|
|
|
|||
|
|
p.btnShow = btn
|
|||
|
|
p.btnDiv = btnDiv // для вставки сообщений об ошибке сразу после btnDiv, но внутри form-group
|
|||
|
|
|
|||
|
|
|
|||
|
|
p.btnRemove = document.createElement('button')
|
|||
|
|
p.btnRemove.textContent = 'удалить'
|
|||
|
|
p.btnRemove.style.color = 'white'
|
|||
|
|
p.btnRemove.style.backgroundColor = '#ff6961'
|
|||
|
|
p.btnRemove.setAttribute('class', 'btn')
|
|||
|
|
p.btnRemove.hidden = true
|
|||
|
|
|
|||
|
|
p.btnRemove.addEventListener('click', () => {
|
|||
|
|
this._removeCheckedObjects(p)
|
|||
|
|
p.btnRemove.hidden = true
|
|||
|
|
//p.btnShow.hidden = false
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
//btnWrapper.appendChild(btn)
|
|||
|
|
btnWrapper.appendChild(btnDiv)
|
|||
|
|
btnWrapper.appendChild(p.btnRemove)
|
|||
|
|
|
|||
|
|
div.appendChild(label)
|
|||
|
|
//div.appendChild(document.createElement('hr'))
|
|||
|
|
div.appendChild(arrContainer)
|
|||
|
|
div.appendChild(btnWrapper)
|
|||
|
|
|
|||
|
|
//container.appendChild(div)
|
|||
|
|
|
|||
|
|
fieldsContainer.appendChild(div)
|
|||
|
|
|
|||
|
|
//p.btnSubmit = btn
|
|||
|
|
//p.container = arrContainer
|
|||
|
|
|
|||
|
|
p.arr = []
|
|||
|
|
|
|||
|
|
btn.addEventListener('click', (e) => {
|
|||
|
|
e.preventDefault()
|
|||
|
|
|
|||
|
|
if (p.selected) {
|
|||
|
|
this._select(p, p.selected)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Одновременный select
|
|||
|
|
this._createListItem(p)
|
|||
|
|
|
|||
|
|
|
|||
|
|
// console.log('NEEEEEEEEEEEW ITEEEEEEEEM:', item)
|
|||
|
|
|
|||
|
|
|
|||
|
|
//this._select(p, p.arr[p.arr.length - 1])
|
|||
|
|
//let item = p.arr[p.arr.length - 1]
|
|||
|
|
//item.select.classList.add(p.activeClass)
|
|||
|
|
//p.selected = item
|
|||
|
|
//console.log('PUSHED:', item)
|
|||
|
|
//this._openForm(p)
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
//if (this._inEditMode) {
|
|||
|
|
if (this._mode == editing) {
|
|||
|
|
if (p.isEditable) {
|
|||
|
|
btn.hidden = false
|
|||
|
|
} else {
|
|||
|
|
btn.hidden = true
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
p.form.fields[p.name] = p
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Form.prototype._createNestedForm = function(t, obj) {
|
|||
|
|
let formContainer;
|
|||
|
|
|
|||
|
|
if (t.formContainerId) {
|
|||
|
|
formContainer = document.getElementById(t.formContainerId)
|
|||
|
|
if (!formContainer) {
|
|||
|
|
throw new Error(`form container '${t.formContainerId}' not found`)
|
|||
|
|
}
|
|||
|
|
formContainer.hidden = false
|
|||
|
|
} else {
|
|||
|
|
formContainer = document.createElement('div')
|
|||
|
|
formContainer.setAttribute('class', t.formContainerClass)
|
|||
|
|
|
|||
|
|
//formContainer.style.borderLeft = '4px solid #72bcd4'
|
|||
|
|
//formContainer.style.border = '1px solid #eee'
|
|||
|
|
formContainer.style.padding = '1rem'
|
|||
|
|
formContainer.style.marginLeft = '-1rem'
|
|||
|
|
formContainer.style.marginRight = '-1rem'
|
|||
|
|
formContainer.style.backgroundColor = '#e8f4f8'
|
|||
|
|
//formContainer.hidden = true
|
|||
|
|
t.cover.appendChild(formContainer)
|
|||
|
|
}
|
|||
|
|
// fix add showForm()
|
|||
|
|
|
|||
|
|
t.formContainer = formContainer
|
|||
|
|
|
|||
|
|
|
|||
|
|
t.dirty = {}
|
|||
|
|
|
|||
|
|
// fix - return ?
|
|||
|
|
let r = this._createForm(t.schema, formContainer)
|
|||
|
|
|
|||
|
|
t.isFormOpened = true
|
|||
|
|
|
|||
|
|
t.schema.forEach(p => {
|
|||
|
|
this._setFieldValue(p)
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Form.prototype._openForm = function(t) {
|
|||
|
|
//console.log('LOADER clean:', JSON.stringify(t.selected.clean))
|
|||
|
|
let loader = new Loader({
|
|||
|
|
api: this._api,
|
|||
|
|
t: t,
|
|||
|
|
clean: t.selected.clean,
|
|||
|
|
context: this._context,
|
|||
|
|
onError: err => {
|
|||
|
|
//console.log('Loader.err: ', err)
|
|||
|
|
},
|
|||
|
|
onLoaded:() => {
|
|||
|
|
this._createNestedForm(t)
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
loader.load()
|
|||
|
|
|
|||
|
|
//console.log('OOOOOOOOOOOOOOOOPEN FORM:', p.isFormOpened)
|
|||
|
|
/*
|
|||
|
|
if (t.isFormOpened) {
|
|||
|
|
this._reset(t)
|
|||
|
|
} else {
|
|||
|
|
//console.log('show form')
|
|||
|
|
|
|||
|
|
let formContainer;
|
|||
|
|
|
|||
|
|
if (t.formContainerId) {
|
|||
|
|
formContainer = document.getElementById(t.formContainerId)
|
|||
|
|
if (!formContainer) {
|
|||
|
|
throw new Error(`form container '${t.formContainerId}' not found`)
|
|||
|
|
}
|
|||
|
|
formContainer.hidden = false
|
|||
|
|
} else {
|
|||
|
|
formContainer = document.createElement('div')
|
|||
|
|
formContainer.setAttribute('class', t.formContainerClass)
|
|||
|
|
//formContainer.hidden = true
|
|||
|
|
t.cover.appendChild(formContainer)
|
|||
|
|
}
|
|||
|
|
// fix add showForm()
|
|||
|
|
|
|||
|
|
t.formContainer = formContainer
|
|||
|
|
|
|||
|
|
// fix - return ?
|
|||
|
|
let r = this._createForm(t.schema, formContainer)
|
|||
|
|
|
|||
|
|
|
|||
|
|
this._loadDataForCustomElems(t.schema)
|
|||
|
|
*/
|
|||
|
|
////////////////////////
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
////////////////////////
|
|||
|
|
|
|||
|
|
//if (p.formContainer) {
|
|||
|
|
// p.formContainer.hidden = false
|
|||
|
|
//} else if (p.showForm) {
|
|||
|
|
// p.showForm()
|
|||
|
|
//p.isFormOpened = true
|
|||
|
|
//} else {
|
|||
|
|
// create container
|
|||
|
|
|
|||
|
|
//}
|
|||
|
|
//btn.textContent = 'скрыть'
|
|||
|
|
//p.btnShow.hidden = true
|
|||
|
|
/*
|
|||
|
|
|
|||
|
|
t.isFormOpened = true
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Всегда инициатор, ибо если форма уже была открыта и грузилась - _reset все сбросил
|
|||
|
|
//t.upd = {}
|
|||
|
|
|
|||
|
|
t.schema.forEach(p => {
|
|||
|
|
this._setFieldValue(p)
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
*/
|
|||
|
|
//if (t.loadingCount == 0) {
|
|||
|
|
// this._onFormLoaded(t)
|
|||
|
|
//}
|
|||
|
|
|
|||
|
|
//console.log('EEEEEEND OF OOOOOOOOOOOOOOOOPEN FORM:', p.isFormOpened)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
Form.prototype._closeForm = function(t) {
|
|||
|
|
this._removeForm(t)
|
|||
|
|
t.arr = []
|
|||
|
|
}
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
// FIX разделить на топ и вложенные, топ снимаем выделение со списка, вложенный обнуляем arr
|
|||
|
|
Form.prototype._removeForm = function(t) {
|
|||
|
|
//console.log('_removeForm')
|
|||
|
|
//console.log('CLOOOOOOOOOOOOOOOOSE FORM:', p)
|
|||
|
|
if (!t.isFormOpened) {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
t.schema.forEach(p => {
|
|||
|
|
this._removeField(p)
|
|||
|
|
})
|
|||
|
|
// скрываем если форма открыта
|
|||
|
|
if (t.formContainerId) {
|
|||
|
|
// внешний div, в документе
|
|||
|
|
t.formContainer.hidden = true
|
|||
|
|
t.formContainer.innerHTML = ''
|
|||
|
|
} else if (t.hideForm) {
|
|||
|
|
t.hideForm()
|
|||
|
|
} else {
|
|||
|
|
// inline форма - просто удаляем
|
|||
|
|
t.formContainer.remove()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
t.fields = {}
|
|||
|
|
t.settingObj = undefined
|
|||
|
|
t.upd = undefined
|
|||
|
|
t.selected = undefined
|
|||
|
|
t.dirty = {}
|
|||
|
|
|
|||
|
|
if (t.loadingCount > 0) {
|
|||
|
|
throw new Error(`Bug: form.loadingCount=${t.loadingCount} after remove fields`)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
t.isFormOpened = false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Form.prototype._select = function(t, item) {
|
|||
|
|
let selected = t.selected
|
|||
|
|
if (t.selected) {
|
|||
|
|
t.selected.select.classList.remove(t.activeClass)
|
|||
|
|
}
|
|||
|
|
this._removeForm(t)
|
|||
|
|
|
|||
|
|
if (selected != item) {
|
|||
|
|
item.select.classList.add(t.activeClass)
|
|||
|
|
t.selected = item
|
|||
|
|
|
|||
|
|
this._openForm(t)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Form.prototype._check = function(p) {
|
|||
|
|
if (p.btnRemove) {
|
|||
|
|
let q = 0
|
|||
|
|
p.arr.forEach(item => {
|
|||
|
|
if (item.checkbox && item.checkbox.checked) {
|
|||
|
|
q++
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
//console.log('checked:', q)
|
|||
|
|
|
|||
|
|
if (q > 0) {
|
|||
|
|
//p.btnShow.hidden = true
|
|||
|
|
p.btnRemove.hidden = false
|
|||
|
|
p.btnShow.hidden = true
|
|||
|
|
} else {
|
|||
|
|
p.btnRemove.hidden = true
|
|||
|
|
p.btnShow.hidden = false
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Form.prototype._buildItem = function(p, clean) {
|
|||
|
|
//console.log('buildItem:', p.name, JSON.stringify(clean))
|
|||
|
|
|
|||
|
|
let item = {
|
|||
|
|
clean: clean || {},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (clean === undefined) {
|
|||
|
|
// Создаем пустой item
|
|||
|
|
item.clean.itemName = p.getItemName()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let row = document.createElement('div')
|
|||
|
|
row.style.paddingTop = '3px'
|
|||
|
|
row.style.paddingBottom = '3px'
|
|||
|
|
|
|||
|
|
let checkbox = document.createElement('input')
|
|||
|
|
checkbox.setAttribute('type', 'checkbox')
|
|||
|
|
|
|||
|
|
let select = document.createElement('div')
|
|||
|
|
select.style.display = 'inline'
|
|||
|
|
select.style.padding = '7px'
|
|||
|
|
select.style.marginLeft = '5px'
|
|||
|
|
//select.textContent = p.getItemName(obj)
|
|||
|
|
// dirty - object or undefined
|
|||
|
|
|
|||
|
|
//let itemName = p.getItemName(p.dirty)
|
|||
|
|
//item.clean.itemName = itemName
|
|||
|
|
//select.innerHTML = p.getItemName(dirty)
|
|||
|
|
|
|||
|
|
select.innerHTML = item.clean.itemName
|
|||
|
|
|
|||
|
|
row.appendChild(checkbox)
|
|||
|
|
row.appendChild(select)
|
|||
|
|
|
|||
|
|
item.elem = row
|
|||
|
|
item.select = select
|
|||
|
|
item.checkbox = checkbox
|
|||
|
|
|
|||
|
|
// Область для select кликов
|
|||
|
|
select.style.cursor = 'pointer'
|
|||
|
|
select.addEventListener('click', () => {
|
|||
|
|
//if (this._loadingCount == 0) {
|
|||
|
|
//console.log('SELECT ITEM:', JSON.stringify(item.clean))
|
|||
|
|
this._select(p, item)
|
|||
|
|
//}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// Чекбоксы
|
|||
|
|
checkbox.setAttribute('type', 'checkbox')
|
|||
|
|
checkbox.addEventListener('change', () => this._check(p))
|
|||
|
|
|
|||
|
|
return item
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
Form.prototype._insertItem = function(p, item) {
|
|||
|
|
// Удаляем надпись emptyListMsg
|
|||
|
|
if (p.arr.length == 0) {
|
|||
|
|
p.container.innerHTML = ''
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// вставляем в массив
|
|||
|
|
p.arr.push(item)
|
|||
|
|
|
|||
|
|
if (!p.isEditable) {
|
|||
|
|
item.checkbox.disabled = true
|
|||
|
|
}
|
|||
|
|
// вставляем в DOM
|
|||
|
|
p.container.appendChild(item.elem)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
Form.prototype._up = function(p) {
|
|||
|
|
let cleanList = [];
|
|||
|
|
|
|||
|
|
// цикл по items
|
|||
|
|
p.arr.forEach(x => {
|
|||
|
|
cleanList.push(x.clean)
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
let t = p.form
|
|||
|
|
|
|||
|
|
if (t.type == 'list') {
|
|||
|
|
let item = t.selected
|
|||
|
|
if (item === undefined) {
|
|||
|
|
return // fix bug
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
item.clean[p.name] = cleanList
|
|||
|
|
|
|||
|
|
this._up(t)
|
|||
|
|
} else {
|
|||
|
|
this.form.selected[p.name] = cleanList
|
|||
|
|
|
|||
|
|
// calc signature
|
|||
|
|
this._onFormDataChanged()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Для использования в setFieldValue
|
|||
|
|
// Метод создает item, но не запускает уведомление родителя (ибо уже там есть) и
|
|||
|
|
// не выбирает (select) добавленный элемент
|
|||
|
|
Form.prototype._appendListItem = function(p, clean) {
|
|||
|
|
//console.log('pushObject into:', p.name, JSON.stringify(clean))
|
|||
|
|
|
|||
|
|
let item = this._buildItem(p, clean)
|
|||
|
|
// FIX обновляем имя в списке объектов
|
|||
|
|
//item.select.textContent = p.getItemName(obj)
|
|||
|
|
//item.select.innerHTML = p.getItemName(obj)
|
|||
|
|
|
|||
|
|
// добавляем новый
|
|||
|
|
this._insertItem(p, item)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
Form.prototype._createListItem = function(p) {
|
|||
|
|
//console.log('pushObject into:', p.name)
|
|||
|
|
|
|||
|
|
// вместо clean and dirty - undefined
|
|||
|
|
let item = this._buildItem(p)
|
|||
|
|
// FIX обновляем имя в списке объектов
|
|||
|
|
//item.select.textContent = p.getItemName(obj)
|
|||
|
|
//item.select.innerHTML = p.getItemName(obj)
|
|||
|
|
|
|||
|
|
// добавляем новый
|
|||
|
|
this._insertItem(p, item)
|
|||
|
|
|
|||
|
|
this._select(p, item)
|
|||
|
|
|
|||
|
|
this._up(p)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// recursiveUpdateField
|
|||
|
|
// clean и dirty - объекты вида {name: value}
|
|||
|
|
Form.prototype._updateListItem = function(p, upd) {
|
|||
|
|
if (p.type == 'list') {
|
|||
|
|
//console.log(p)
|
|||
|
|
|
|||
|
|
let item = p.selected
|
|||
|
|
|
|||
|
|
// Обновляем рекурсивно до топ уровня
|
|||
|
|
if (item === undefined) {
|
|||
|
|
throw new Error(`Bug: updateListItem of non selected item`)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for (let k in upd) {
|
|||
|
|
let v = upd[k]
|
|||
|
|
if (v === undefined) {
|
|||
|
|
//console.log('DELETE PROP:', k, ' IN:', obj)
|
|||
|
|
delete item.clean[k]
|
|||
|
|
} else {
|
|||
|
|
item.clean[k] = v
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let itemName = p.getItemName(p.dirty)
|
|||
|
|
|
|||
|
|
item.clean.itemName = itemName
|
|||
|
|
|
|||
|
|
//item.select.textContent = p.getItemName(item.obj)
|
|||
|
|
item.select.innerHTML = itemName
|
|||
|
|
|
|||
|
|
this._up(p)
|
|||
|
|
} else {
|
|||
|
|
for (let k in upd) {
|
|||
|
|
let v = upd[k]
|
|||
|
|
if (v === undefined) {
|
|||
|
|
delete this.form.selected[k]
|
|||
|
|
} else {
|
|||
|
|
this.form.selected[k] = v
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this._onFormDataChanged()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
Form.prototype._removeCheckedObjects = function(p) {
|
|||
|
|
if (Array.isArray(p.arr)) {
|
|||
|
|
let filtered = []
|
|||
|
|
|
|||
|
|
p.arr.forEach(item => {
|
|||
|
|
if (item.checkbox && item.checkbox.checked) {
|
|||
|
|
// Снимаем выделение
|
|||
|
|
if (p.selected == item) {
|
|||
|
|
this._select(p, p.selected)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
p.container.removeChild(item.elem)
|
|||
|
|
} else {
|
|||
|
|
filtered.push(item)
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
p.arr = filtered
|
|||
|
|
|
|||
|
|
if (p.arr.length == 0) {
|
|||
|
|
p.container.textContent = p.emptyListMsg
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
p.btnShow.hidden = false
|
|||
|
|
|
|||
|
|
|
|||
|
|
this._up(p)
|
|||
|
|
/*
|
|||
|
|
if (p.form.type == 'list') {
|
|||
|
|
//this._pushObject(p.form, this.getObj(p.form.schema))
|
|||
|
|
let upd = {}, objectList = [];
|
|||
|
|
|
|||
|
|
p.arr.forEach(item => {
|
|||
|
|
objectList.push(item.obj)
|
|||
|
|
})
|
|||
|
|
upd[p.name] = objectList
|
|||
|
|
|
|||
|
|
this._updateObject(p.form, upd)
|
|||
|
|
} else {
|
|||
|
|
//this._onFormLoaded(this.form)
|
|||
|
|
this._onFormDataChanged()
|
|||
|
|
}
|
|||
|
|
*/
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
Form.prototype._removeDependentFields = function(p) {
|
|||
|
|
//console.log('_removeDependentFields')
|
|||
|
|
|
|||
|
|
let upd = {}
|
|||
|
|
if (Array.isArray(p.chains)) {
|
|||
|
|
// d - depends
|
|||
|
|
p.chains.forEach(d => {
|
|||
|
|
upd = Object.assign(upd, this._removeField(d))
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let fork = p.fork
|
|||
|
|
|
|||
|
|
if (fork) {
|
|||
|
|
//upd = Object.assign(upd, this._resetFork(p.fork))
|
|||
|
|
|
|||
|
|
//console.log('RESET ROAD:', schema)
|
|||
|
|
let cur = fork.current
|
|||
|
|
if (!cur) {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
cur.schema.forEach(d => {
|
|||
|
|
upd = Object.assign(upd, this._removeField(d))
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
if (cur.cleanup) {
|
|||
|
|
cur.cleanup()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
fork.current = undefined
|
|||
|
|
}
|
|||
|
|
return upd
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
// c - custom property
|
|||
|
|
Form.prototype._onElemEvent = function(c) {
|
|||
|
|
let form = c.form;
|
|||
|
|
|
|||
|
|
if (form.reseting) {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Достаем объект и затем ID
|
|||
|
|
let tmp = this._getObjectValue(c)
|
|||
|
|
|
|||
|
|
let cleanValue = tmp.clean
|
|||
|
|
let dirtyValue = tmp.dirty
|
|||
|
|
|
|||
|
|
|
|||
|
|
form.dirty[c.name] = dirtyValue
|
|||
|
|
|
|||
|
|
// ?
|
|||
|
|
|
|||
|
|
//console.log('ON ELEM EVENT:', c.name)
|
|||
|
|
//console.log('cleanValue:', cleanValue)
|
|||
|
|
//console.log('dirtyValue:', dirtyValue)
|
|||
|
|
|
|||
|
|
// bubble update
|
|||
|
|
let upd = {}
|
|||
|
|
upd[c.name] = cleanValue
|
|||
|
|
this._updateListItem(form, upd)
|
|||
|
|
|
|||
|
|
// Очищаем зависимые цепочки и развилки, а сам объект события не трогаем
|
|||
|
|
// FIX обновляем цепочки внутри метода или как сейчас?
|
|||
|
|
//let upd = Object.assign(upd, )
|
|||
|
|
this._removeDependentFields(c)
|
|||
|
|
|
|||
|
|
//console.log('SELECTED OBJECT:', form.selected)
|
|||
|
|
|
|||
|
|
if (dirtyValue !== undefined) {
|
|||
|
|
// Общий контейнер для всех новых полей
|
|||
|
|
let container = document.createDocumentFragment()
|
|||
|
|
|
|||
|
|
c.chains.forEach(d => {
|
|||
|
|
// создаем DOM элементы
|
|||
|
|
this._createField(d, container)
|
|||
|
|
|
|||
|
|
if (d.loadFunc) {
|
|||
|
|
this._loadData(d)
|
|||
|
|
} else if (d.data) {
|
|||
|
|
this._fillOptionsField(d, d.data)
|
|||
|
|
this._setFieldValue(d)
|
|||
|
|
} else {
|
|||
|
|
this._setFieldValue(d) // FIX ????
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
let f = c.fork
|
|||
|
|
if (f) {
|
|||
|
|
let cleanId = cleanValue
|
|||
|
|
if (f.getValue) {
|
|||
|
|
// ВАЖНО!
|
|||
|
|
// getValue на вход получает объект целиком
|
|||
|
|
cleanId = f.getValue(dirtyValue)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//console.log('fork clean param:', cleanId)
|
|||
|
|
|
|||
|
|
// затем устанавливаем новую (если есть)
|
|||
|
|
let road = f.roads.get(cleanId)
|
|||
|
|
if (road) {
|
|||
|
|
//console.log('SET CURRENT ROAD:', JSON.stringify(road))
|
|||
|
|
|
|||
|
|
if (road.init) {
|
|||
|
|
road.init()
|
|||
|
|
}
|
|||
|
|
//console.log('CONTAINER:', c.cover)
|
|||
|
|
this._createSchemaObjects(road.schema, {after: c.cover})
|
|||
|
|
|
|||
|
|
// в цикле для custom элементов загружаем loadFunc
|
|||
|
|
road.schema.forEach(x => {
|
|||
|
|
if (x.loadFunc) {
|
|||
|
|
this._loadData(x)
|
|||
|
|
} else {
|
|||
|
|
this._setFieldValue(x)
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
f.current = road
|
|||
|
|
|
|||
|
|
//road.schema.forEach(x => {
|
|||
|
|
//this._setFieldValue(x)
|
|||
|
|
//})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
// Вставляем все новые поля после поля события
|
|||
|
|
c.cover.parentNode.insertBefore(container, c.cover.nextSibling)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (c.onChange) {
|
|||
|
|
c.onChange(dirtyValue)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
// Загружает данные для построения формы (openForm, init, _select)
|
|||
|
|
// Опциональный объект с уже заполненными полями - clean
|
|||
|
|
function Loader(opt) {
|
|||
|
|
if (!opt.t.selected) {
|
|||
|
|
throw new Error(`Where is selected?`)
|
|||
|
|
}
|
|||
|
|
this._counter = 0
|
|||
|
|
this._api = opt.api
|
|||
|
|
// Для отслеживания - переключил юзер item или нет пока загружались данные
|
|||
|
|
// и для доступа к схеме
|
|||
|
|
this._t = opt.t
|
|||
|
|
// Для отслеживания - переключил юзер item или нет пока загружались данные
|
|||
|
|
this._selected = this._t.selected
|
|||
|
|
this._clean = opt.clean || {}
|
|||
|
|
this._context = opt.context
|
|||
|
|
|
|||
|
|
this._onError = opt.onError
|
|||
|
|
this._onLoaded = opt.onLoaded
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Метод загружает данные и добавляем в кэш элементов. Не занимается отображением данных
|
|||
|
|
Loader.prototype._loadElem = function(p, id) {
|
|||
|
|
// Если значение уже закешировано - запрос не отправляем
|
|||
|
|
if (p.caching) {
|
|||
|
|
let v = p.cache.get(id)
|
|||
|
|
if (v) {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this._counter++
|
|||
|
|
|
|||
|
|
//let arg;
|
|||
|
|
if (p.getLoadFuncArg) {
|
|||
|
|
// параметр функции - это объект-снимок формы со всеми заполненными полями
|
|||
|
|
// на текущий момент. Форма та, которой принадлежит свойство p.
|
|||
|
|
id = p.getLoadFuncArg(this._selected) // FIX whole form object
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log(`_loadElem: ${p.name} = ${JSON.stringify(id)}`)
|
|||
|
|
|
|||
|
|
this._api.req({
|
|||
|
|
func: p.loadFunc,
|
|||
|
|
data: id,
|
|||
|
|
onError: err => {
|
|||
|
|
//console.log(err)
|
|||
|
|
// Прерываем загрузку
|
|||
|
|
this._onError(err)
|
|||
|
|
},
|
|||
|
|
onSuccess: resp => {
|
|||
|
|
//console.log(resp)
|
|||
|
|
if (p.responseModifier) {
|
|||
|
|
resp = p.responseModifier(resp)
|
|||
|
|
}
|
|||
|
|
// Добавляем в кэш
|
|||
|
|
if (p.loadFuncContextParam) {
|
|||
|
|
p.cache.set(undefined, resp)
|
|||
|
|
} else {
|
|||
|
|
p.cache.set(id, resp)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// resp - это список объектов (например список checboxes или options в select)
|
|||
|
|
// this._clean - это заполненные поля (ID-шники)
|
|||
|
|
// нужна функция, которая из списка объектов методом найдет нужный по ID
|
|||
|
|
// (методом перебора).
|
|||
|
|
|
|||
|
|
|
|||
|
|
let cleanValue = this._clean[p.name]
|
|||
|
|
let dirtyValue = this._findSelectedObject(p, resp, cleanValue)
|
|||
|
|
|
|||
|
|
if (dirtyValue !== undefined) {
|
|||
|
|
if (Array.isArray(p.chains)) {
|
|||
|
|
p.chains.forEach(d => {
|
|||
|
|
//
|
|||
|
|
if (d.loadFunc) {
|
|||
|
|
this._loadElem(d)
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let fork = p.fork
|
|||
|
|
if (fork) {
|
|||
|
|
//
|
|||
|
|
let cleanId = cleanValue
|
|||
|
|
if (fork.getValue) {
|
|||
|
|
// ВАЖНО!
|
|||
|
|
// getValue на вход получает объект целиком
|
|||
|
|
cleanId = fork.getValue(dirtyValue)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//console.log('fork clean param:', cleanId)
|
|||
|
|
|
|||
|
|
// затем устанавливаем новую (если есть)
|
|||
|
|
let road = fork.roads.get(cleanId)
|
|||
|
|
if (road) {
|
|||
|
|
road.schema.forEach(d => {
|
|||
|
|
if (d.loadFunc) {
|
|||
|
|
this._loadElem(d)
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this._counter--
|
|||
|
|
|
|||
|
|
if (this._counter == 0) {
|
|||
|
|
if (this._selected == this._t.selected) {
|
|||
|
|
this._onLoaded()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Loader.prototype._findSelectedObject = function(p, resp, id) {
|
|||
|
|
if (!Array.isArray(resp)) {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
if (p.scalar) {
|
|||
|
|
// это список id, а не список объектов
|
|||
|
|
if (p.type == 'checkboxes') {
|
|||
|
|
// fix добавить сюда модель с чекбоксами
|
|||
|
|
if (!Array.isArray(id)) {
|
|||
|
|
return []
|
|||
|
|
}
|
|||
|
|
let result = []
|
|||
|
|
|
|||
|
|
// временный словарь для удобной фильтрации
|
|||
|
|
let checkedIDs = new Map()
|
|||
|
|
|
|||
|
|
id.forEach(checkedID => {
|
|||
|
|
checkedIDs.set(checkedID, true)
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
let key = p.name
|
|||
|
|
if (p.key) {
|
|||
|
|
key = p.key
|
|||
|
|
}
|
|||
|
|
for (let i=0; i<resp.length; i++) {
|
|||
|
|
let v = resp[i]
|
|||
|
|
if (checkedIDs.has(v)) {
|
|||
|
|
result.push(v)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return result
|
|||
|
|
} else {
|
|||
|
|
for (let i=0; i<resp.length; i++) {
|
|||
|
|
if (resp[i] == id) {
|
|||
|
|
return id
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
// это список объектов
|
|||
|
|
if (p.type == 'checkboxes') {
|
|||
|
|
// fix добавить сюда модель с чекбоксами
|
|||
|
|
|
|||
|
|
// id - это список ID, а нам нужно найти список объектов
|
|||
|
|
if (!Array.isArray(id)) {
|
|||
|
|
return []
|
|||
|
|
}
|
|||
|
|
let result = []
|
|||
|
|
|
|||
|
|
// временный словарь для удобной фильтрации
|
|||
|
|
let checkedIDs = new Map()
|
|||
|
|
|
|||
|
|
id.forEach(checkedID => {
|
|||
|
|
checkedIDs.set(checkedID, true)
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
let key = p.name
|
|||
|
|
if (p.key) {
|
|||
|
|
key = p.key
|
|||
|
|
}
|
|||
|
|
for (let i=0; i<resp.length; i++) {
|
|||
|
|
let v = resp[i]
|
|||
|
|
if (checkedIDs.has(v[key])) {
|
|||
|
|
result.push(v)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return result
|
|||
|
|
} else {
|
|||
|
|
for (let i=0; i<resp.length; i++) {
|
|||
|
|
let v = resp[i]
|
|||
|
|
if (v[p.name] == id) {
|
|||
|
|
return v
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Loader.prototype.load = function() {
|
|||
|
|
//console.log('LOADER.loading....')
|
|||
|
|
this._t.schema.forEach(p => {
|
|||
|
|
if (p.loadFunc) {
|
|||
|
|
if (p.loadFuncContextParam) {
|
|||
|
|
let param = this._context[p.loadFuncContextParam]
|
|||
|
|
this._loadElem(p, param)
|
|||
|
|
} else {
|
|||
|
|
this._loadElem(p)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
if (this._counter == 0) {
|
|||
|
|
if (this._selected == this._t.selected) {
|
|||
|
|
this._onLoaded()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
// po - parent object
|
|||
|
|
Form.prototype._loadData = function(p) {
|
|||
|
|
|
|||
|
|
let arg;
|
|||
|
|
if (p.getLoadFuncArg) {
|
|||
|
|
// параметр функции - это объект-снимок формы со всеми заполненными полями
|
|||
|
|
// на текущий момент. Форма та, которой принадлежит свойство p.
|
|||
|
|
arg = p.getLoadFuncArg(p.form.selected, this.form.selected)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log(`_loadData: ${p.name} = ${JSON.stringify(arg)}`)
|
|||
|
|
|
|||
|
|
let paramSignature = JSON.stringify(arg)
|
|||
|
|
|
|||
|
|
//onsole.log('_LOAD DATA ____________________', p.name, p.caching)
|
|||
|
|
|
|||
|
|
if (p.caching) {
|
|||
|
|
let list = p.cache.get(paramSignature)
|
|||
|
|
if (list) {
|
|||
|
|
//console.log('_loadData (from cache):', p, arg)
|
|||
|
|
|
|||
|
|
// Заполняем поля выбора (select, radios, checkboxes) объектами из списка,
|
|||
|
|
// который прислал сервер
|
|||
|
|
this._fillOptionsField(p, list)
|
|||
|
|
|
|||
|
|
// Выбираем элемент(ы), если форма в режиме редактирования
|
|||
|
|
// если режим редактирования этой формы
|
|||
|
|
this._setFieldValue(p)
|
|||
|
|
// Закешированные данные нашли, поэтому выходим
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (p.loading) {
|
|||
|
|
if (p.paramSignature === paramSignature) {
|
|||
|
|
// Запрос уже отправлен, ничего не делаем
|
|||
|
|
// Пример такой ситуации: юзер кликнул по первому объекту, на сервер ушел запрос,
|
|||
|
|
// затем кликнул по второму объекту и снова по первому.
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
// Обновляем только сигнатуру параметра, loadingCount уже увеличен, флаг loading уже выставлен
|
|||
|
|
p.paramSignature = paramSignature
|
|||
|
|
} else {
|
|||
|
|
p.loading = true
|
|||
|
|
p.paramSignature = paramSignature
|
|||
|
|
|
|||
|
|
p.form.loadingCount++
|
|||
|
|
this._loadingCount++
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//console.log('_loadData (loading...):', p, arg)
|
|||
|
|
|
|||
|
|
this._api.req({
|
|||
|
|
func: p.loadFunc,
|
|||
|
|
data: arg,
|
|||
|
|
onError: err => {
|
|||
|
|
if (!p.loading) {
|
|||
|
|
// UI элемент был удален, поэтому сообщение об ошибке не нужно
|
|||
|
|
// loadingCount уже сброшен при удалении элемента
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (p.paramSignature !== paramSignature) {
|
|||
|
|
// Уже отправлен более свежий запрос, поэтому сообщение об ошибке не нужно
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
p.loading = false
|
|||
|
|
p.paramSignature = undefined
|
|||
|
|
|
|||
|
|
p.form.loadingCount--
|
|||
|
|
this._loadingCount--
|
|||
|
|
|
|||
|
|
p.divLoadError = insertError({
|
|||
|
|
//after: item.elem.getAnchor(),
|
|||
|
|
after: p.cover,
|
|||
|
|
err: err,
|
|||
|
|
class: this._errorClass
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
//console.log(`item ${p.name} loaded. Loading count-- (error): ${this._loadingCount}`)
|
|||
|
|
|
|||
|
|
//if (p.form.loadingCount == 0) {
|
|||
|
|
// this._onFormLoaded(p.form)
|
|||
|
|
//}
|
|||
|
|
},
|
|||
|
|
onSuccess: resp => {
|
|||
|
|
if (!p.loading) {
|
|||
|
|
// UI элемент был удален, поэтому загруженные данные больше не нужны
|
|||
|
|
// loadingCount уже сброшен при удалении элемента
|
|||
|
|
//console.log(`_loadData: prop ${p.name} not loading`)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (p.paramSignature !== paramSignature) {
|
|||
|
|
// Уже отправлен более свежий запрос, поэтому загруженные данные больше не нужны
|
|||
|
|
//console.log(`_loadData: prop ${p.name} paramSignature changed (has: '${p.paramSignature}', expect: '${paramSignature}')`)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
p.loading = false
|
|||
|
|
p.paramSignature = undefined
|
|||
|
|
|
|||
|
|
p.form.loadingCount--
|
|||
|
|
this._loadingCount--
|
|||
|
|
|
|||
|
|
if (p.responseModifier) {
|
|||
|
|
resp = p.responseModifier(resp)
|
|||
|
|
}
|
|||
|
|
// Добавляем в кэш
|
|||
|
|
if (p.caching) {
|
|||
|
|
p.cache.set(paramSignature, resp)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//console.log(`item ${p.name} loaded. Loading count--: ${this._loadingCount}`)
|
|||
|
|
|
|||
|
|
// Заполняем поля выбора (select, radios, checkboxes) объектами из списка,
|
|||
|
|
// который прислал сервер
|
|||
|
|
this._fillOptionsField(p, resp)
|
|||
|
|
|
|||
|
|
// Выбираем элемент(ы), если форма в режиме редактирования
|
|||
|
|
// если режим редактирования этой формы
|
|||
|
|
this._setFieldValue(p)
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
// FIX сделать метод локальным для формы?
|
|||
|
|
Form.prototype._onFormLoaded = function(loader) {
|
|||
|
|
//console.log('ON_FORM_LOADED:', form, ' loadingCount:', this._loadingCount)
|
|||
|
|
/*
|
|||
|
|
let t = loader.t
|
|||
|
|
|
|||
|
|
|
|||
|
|
if (t.selected != loader.selected) {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
// create form
|
|||
|
|
this._createSchemaObjects(t.schema)
|
|||
|
|
|
|||
|
|
loader.onFormLoaded()
|
|||
|
|
|
|||
|
|
t.schema.forEach(p => {
|
|||
|
|
this._setFieldValue(p, t.selected.clean, t.selected.dirty)
|
|||
|
|
})
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
//let upd = form.upd
|
|||
|
|
|
|||
|
|
//form.settingObj = undefined
|
|||
|
|
//form.upd = undefined
|
|||
|
|
|
|||
|
|
//this._updateObject(form, upd)
|
|||
|
|
|
|||
|
|
//this.enable()
|
|||
|
|
|
|||
|
|
// editing mode остается до reset формы
|
|||
|
|
// FIXXXXX
|
|||
|
|
/*
|
|||
|
|
if (this._mode == restoring) {
|
|||
|
|
this._mode = this._restoringMode
|
|||
|
|
this._signature = this._restoringSignature
|
|||
|
|
this._btnSubmit.disabled = this._restoringBtnSubmit
|
|||
|
|
|
|||
|
|
this._restoringMode = undefined
|
|||
|
|
this._restoringMode = undefined
|
|||
|
|
this._restoringBtnSubmit = undefined
|
|||
|
|
}
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
//if (this._mode == editing) {
|
|||
|
|
// if (!this.form.isEditable) {
|
|||
|
|
//this._btnSubmit.disabled = true
|
|||
|
|
//this._btnReset.disabled = true
|
|||
|
|
// }
|
|||
|
|
//}
|
|||
|
|
|
|||
|
|
//if (form.type != 'list') {
|
|||
|
|
|
|||
|
|
//}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Отключает все элементы формы во время загрузки данных с сервера
|
|||
|
|
/*
|
|||
|
|
Form.prototype.disable = function() {
|
|||
|
|
this._schema.forEach(p => {
|
|||
|
|
this._disableField2(p)
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
this._btnSubmit.disabled = true
|
|||
|
|
this._btnReset.disabled = true
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Form.prototype.enable = function() {
|
|||
|
|
this._schema.forEach(p => {
|
|||
|
|
this._enableField2(p)
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
this._btnSubmit.disabled = false
|
|||
|
|
this._btnReset.disabled = false
|
|||
|
|
}
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
// Важный метод сбрасывает все поля главной формы и скрывает все открытые формы
|
|||
|
|
Form.prototype.reset = function(opt) {
|
|||
|
|
//console.log('RESET')
|
|||
|
|
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
let snapshot = JSON.parse(this._initSnapshot)
|
|||
|
|
this._signature = JSON.stringify(snapshot.clean)
|
|||
|
|
this.form.selected = snapshot.clean
|
|||
|
|
} catch (e) {
|
|||
|
|
//console.log(this)
|
|||
|
|
throw new Error(`form not initialized:`)
|
|||
|
|
this.form.selected = {}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
this._reset(this.form)
|
|||
|
|
|
|||
|
|
//this._btnSubmit.disabled = true
|
|||
|
|
|
|||
|
|
this._mode = filling
|
|||
|
|
|
|||
|
|
if (this._error) {
|
|||
|
|
this._error.remove()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
//if (opt && opt.ignoreButtons) {
|
|||
|
|
if (!this._enableSubmit) {
|
|||
|
|
this._btnSubmit.disabled = true
|
|||
|
|
}
|
|||
|
|
this._btnSubmit.textContent = this._btnSubmitText
|
|||
|
|
|
|||
|
|
this._btnRemove.hidden = true
|
|||
|
|
//}
|
|||
|
|
|
|||
|
|
//this._onFormDataChanged()
|
|||
|
|
|
|||
|
|
//console.log('reset mode:', this._mode)
|
|||
|
|
//console.log('reset clean:', this.form.selected)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Метод сбрасывает значения всех элементов формы, удаляет зависимые элементы
|
|||
|
|
// и сбрасывает списки, а именно - очищает список, закрывает (удаляет) форму.
|
|||
|
|
// Причем форму закрывает рекурсивно, корректно удаляя все элементы вложенных форм.
|
|||
|
|
// Сброс формы важен чтобы правильно очистить loadingCount, поле loading и т.д.
|
|||
|
|
Form.prototype._reset = function(t) {
|
|||
|
|
//console.log('_RESET', t)
|
|||
|
|
|
|||
|
|
// Флаг, который позволит в методе onElemEvent игнорировать события при
|
|||
|
|
// сбросе формы
|
|||
|
|
t.reseting = true
|
|||
|
|
|
|||
|
|
t.schema.forEach(p => {
|
|||
|
|
if (p.type == 'list') {
|
|||
|
|
//console.log('RESET LIST FIELD:', p)
|
|||
|
|
this._removeForm(p)
|
|||
|
|
|
|||
|
|
// Делаем reset, не удаляя элементы ???
|
|||
|
|
|
|||
|
|
p.arr = []
|
|||
|
|
p.container.innerHTML = ''
|
|||
|
|
p.container.textContent = p.emptyListMsg
|
|||
|
|
p.btnShow.hidden = false
|
|||
|
|
p.btnRemove.hidden = true
|
|||
|
|
} else {
|
|||
|
|
var contextValue
|
|||
|
|
|
|||
|
|
if (p.contextVar) {
|
|||
|
|
contextValue = this._context[p.contextVar]
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
switch (p.type) {
|
|||
|
|
case '':
|
|||
|
|
if (p.contextVar) {
|
|||
|
|
p.value = contextValue
|
|||
|
|
//console.log('"" CONTEXT VALUE:', p.value)
|
|||
|
|
|
|||
|
|
|
|||
|
|
if (contextValue !== undefined) {
|
|||
|
|
this.form.selected[p.name] = contextValue
|
|||
|
|
} else {
|
|||
|
|
delete this.form.selected[p.name]
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
} else {
|
|||
|
|
p.value = undefined
|
|||
|
|
}
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
case 'line':
|
|||
|
|
case 'text':
|
|||
|
|
if (p.contextVar) {
|
|||
|
|
p.elem.value = contextValue
|
|||
|
|
|
|||
|
|
if (contextValue !== undefined) {
|
|||
|
|
this.form.selected[p.name] = contextValue
|
|||
|
|
} else {
|
|||
|
|
delete this.form.selected[p.name]
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
p.elem.value = ''
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (p.editmode != Form.READONLY) {
|
|||
|
|
p.elem.disabled = false
|
|||
|
|
}
|
|||
|
|
// fix dispachevent
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
case 'checkbox':
|
|||
|
|
if (p.contextVar) {
|
|||
|
|
p.elem.value = contextValue
|
|||
|
|
} else {
|
|||
|
|
p.elem.checked = false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (p.editmode != Form.READONLY) {
|
|||
|
|
p.elem.disabled = false
|
|||
|
|
}
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
case 'select':
|
|||
|
|
if (p.contextVar) {
|
|||
|
|
this._fillOptionsField(p, contextValue)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (p.valueObjects) {
|
|||
|
|
if (p.valueObjects.length > 0) {
|
|||
|
|
p.elem.selectedIndex = 0
|
|||
|
|
} else {
|
|||
|
|
p.elem.selectedIndex = -1
|
|||
|
|
}
|
|||
|
|
if (p.editmode != Form.READONLY) {
|
|||
|
|
p.elem.disabled = false
|
|||
|
|
}
|
|||
|
|
p.elem.dispatchEvent(new Event('change'))
|
|||
|
|
}
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
case 'radios':
|
|||
|
|
if (p.contextVar) {
|
|||
|
|
this._fillOptionsField(p, contextValue)
|
|||
|
|
}
|
|||
|
|
// fix context
|
|||
|
|
// fix dispachevent - if checked changed
|
|||
|
|
p.elems.forEach(elem => {
|
|||
|
|
// fix - if checked
|
|||
|
|
if (elem.checked) {
|
|||
|
|
//elem.checked = false
|
|||
|
|
//console.log('RADIOS reset: before click is checked', elem.checked)
|
|||
|
|
elem.dispatchEvent(new Event('click'))
|
|||
|
|
//console.log('RADIOS reset: after click is checked', elem.checked)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (p.editmode != Form.READONLY) {
|
|||
|
|
elem.disabled = false
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
case 'checkboxes':
|
|||
|
|
if (p.contextVar) {
|
|||
|
|
this._fillOptionsField(p, contextValue)
|
|||
|
|
}
|
|||
|
|
// fix context
|
|||
|
|
// fix dispachevent - if checked changed
|
|||
|
|
p.elems.forEach(elem => {
|
|||
|
|
// fix - if checked
|
|||
|
|
if (elem.checked) {
|
|||
|
|
elem.checked = false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (p.editmode != Form.READONLY) {
|
|||
|
|
elem.disabled = false
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
case 'model':
|
|||
|
|
// FIX - just enable?
|
|||
|
|
//p.model.enable()
|
|||
|
|
p.modelOptions.forEach(type => {
|
|||
|
|
type.container = p.cover
|
|||
|
|
})
|
|||
|
|
p.model = new Model(p.modelOptions)
|
|||
|
|
|
|||
|
|
|
|||
|
|
// FIX - генерация события?
|
|||
|
|
/*
|
|||
|
|
if (p.contextVar) {
|
|||
|
|
this._fillOptionsField(p, contextValue)
|
|||
|
|
} else if (p.data) {
|
|||
|
|
this._fillOptionsField(p, p.data)
|
|||
|
|
} else if (p.getData) {
|
|||
|
|
this._fillOptionsField(p, p.getData())
|
|||
|
|
} else {
|
|||
|
|
let cached = p.cache.get()
|
|||
|
|
if (cached) {
|
|||
|
|
this._fillOptionsField(p, cached)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
//if (p.changeType == 'few') {
|
|||
|
|
p.model.onChange = () => {
|
|||
|
|
this._onElemEvent(p)
|
|||
|
|
}
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
//case 'custom-list':
|
|||
|
|
// fix custom reset
|
|||
|
|
// break
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (p.loading) {
|
|||
|
|
p.loading = false
|
|||
|
|
p.paramSignature = undefined
|
|||
|
|
|
|||
|
|
t.loadingCount-- // form
|
|||
|
|
this._loadingCount--
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this._removeDependentFields(p)
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
t.settingObj = undefined
|
|||
|
|
|
|||
|
|
t.reseting = false
|
|||
|
|
//localStorage.removeItem('x')
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
Form.prototype.hideError = function() {
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
Form.prototype.restore = function(s) {
|
|||
|
|
console.log('restore:', s)
|
|||
|
|
|
|||
|
|
//if (!obj) {
|
|||
|
|
// return
|
|||
|
|
//}
|
|||
|
|
|
|||
|
|
if (this._waitingObj) {
|
|||
|
|
throw new Error(`Can't restoreObj while waiting`)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ВАЖНО!
|
|||
|
|
// Обнулять _loadingCount нельзя, ибо в данный момент может грузится форма
|
|||
|
|
this._mode = restoring
|
|||
|
|
this.form.settingObj = s.obj
|
|||
|
|
this._restoringSignature = s.signature
|
|||
|
|
this._restoringMode = s.mode
|
|||
|
|
this._restoringBtnSubmit = s.btnSubmit
|
|||
|
|
|
|||
|
|
this._schema.forEach(p => {
|
|||
|
|
this._setFieldValue(p)
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
if (this._loadingCount > 0) {
|
|||
|
|
//console.log('disable while loading')
|
|||
|
|
//this.disable() // отключаем форму
|
|||
|
|
// Отключать ли submit?
|
|||
|
|
} else {
|
|||
|
|
console.log('restore, no loading elems')
|
|||
|
|
// fix btnsubmit
|
|||
|
|
this._onFormDataChanged()
|
|||
|
|
this._onFormLoaded() // fix
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
|
|||
|
|
// restore - восстанавливает mode, obj, signature и рассчитывает состояние кнопки
|
|||
|
|
Form.prototype.restore = function(snapshotStr) {
|
|||
|
|
let snapshot = JSON.parse(snapshotStr)
|
|||
|
|
if (this._version == snapshot.version) {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let obj = snapshot.dirty
|
|||
|
|
|
|||
|
|
//console.log('restore:', obj)
|
|||
|
|
|
|||
|
|
//if (!obj) {
|
|||
|
|
// return
|
|||
|
|
//}
|
|||
|
|
|
|||
|
|
// Создаем копию объекта
|
|||
|
|
//obj = JSON.parse(JSON.stringify(obj))
|
|||
|
|
|
|||
|
|
this.reset()
|
|||
|
|
|
|||
|
|
this.form.selected = obj // ? fix что делать с дефолтами
|
|||
|
|
|
|||
|
|
//this._cleanDirtyObject(this._schema, obj, this._clean)
|
|||
|
|
//this._cleanDirtyObject(this._schema, obj, this.form.selected.clean)
|
|||
|
|
|
|||
|
|
//console.log('CLEANED:', this.form.selected.clean)
|
|||
|
|
|
|||
|
|
//console.log('restoring')
|
|||
|
|
|
|||
|
|
this._mode = snapshot.mode
|
|||
|
|
|
|||
|
|
let loader = new Loader({
|
|||
|
|
api: this._api,
|
|||
|
|
t: this.form,
|
|||
|
|
clean: this.form.selected,
|
|||
|
|
context: this._context,
|
|||
|
|
onError: err => {
|
|||
|
|
//console.log('Loader.err: ', err)
|
|||
|
|
},
|
|||
|
|
onLoaded:() => {
|
|||
|
|
this._schema.forEach(p => {
|
|||
|
|
this._setFieldValue(p)
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
let newSignature = JSON.stringify(this.form.selected)
|
|||
|
|
|
|||
|
|
this._signature = snapshot.signature
|
|||
|
|
|
|||
|
|
if (this._signature != newSignature) {
|
|||
|
|
this._btnSubmit.disabled = false
|
|||
|
|
} else {
|
|||
|
|
if (!this._enableSubmit) {
|
|||
|
|
this._btnSubmit.disabled = true
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//console.log('restored signature:', this._signature)
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
loader.load()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// edit
|
|||
|
|
Form.prototype.edit = function(obj, isRemoveAllowed) {
|
|||
|
|
|
|||
|
|
//console.log('EDIT:', obj)
|
|||
|
|
|
|||
|
|
if (!obj) {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
obj = JSON.parse(JSON.stringify(obj))
|
|||
|
|
|
|||
|
|
//this.hide()
|
|||
|
|
this.reset({
|
|||
|
|
ignoreButtons: true,
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
this.form.selected = obj // ? fix что делать с дефолтами
|
|||
|
|
|
|||
|
|
|
|||
|
|
//this.form.selected.dirty = obj
|
|||
|
|
//this.form.selected.clean = {}
|
|||
|
|
|
|||
|
|
//this._cleanDirtyObject(this._schema, obj, this._clean)
|
|||
|
|
//this._cleanDirtyObject(this._schema, obj, this.form.selected.clean)
|
|||
|
|
|
|||
|
|
//console.log('CLEANED:', this.form.selected.clean)
|
|||
|
|
|
|||
|
|
//console.log('editing')
|
|||
|
|
|
|||
|
|
this._mode = editing
|
|||
|
|
|
|||
|
|
let loader = new Loader({
|
|||
|
|
api: this._api,
|
|||
|
|
t: this.form,
|
|||
|
|
clean: this.form.selected,
|
|||
|
|
context: this._context,
|
|||
|
|
onError: err => {
|
|||
|
|
//console.log('Loader.err: ', err)
|
|||
|
|
//this.show()
|
|||
|
|
},
|
|||
|
|
onLoaded:() => {
|
|||
|
|
this._schema.forEach(p => {
|
|||
|
|
this._setFieldValue(p)
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
this._signature = JSON.stringify(this.form.selected)
|
|||
|
|
if (!this._enableSubmit) {
|
|||
|
|
this._btnSubmit.disabled = true
|
|||
|
|
}
|
|||
|
|
this._btnSubmit.textContent = this._btnUpdateText
|
|||
|
|
|
|||
|
|
if (isRemoveAllowed) {
|
|||
|
|
this._btnRemove.hidden = false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//console.log('edit.signature:', this._signature)
|
|||
|
|
//console.log('edit.mode:', this._mode)
|
|||
|
|
|
|||
|
|
//this.show()
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
loader.load()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
Form.prototype.fill = function(obj) {
|
|||
|
|
|
|||
|
|
//console.log('fill:', obj)
|
|||
|
|
|
|||
|
|
if (!obj) {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
obj = JSON.parse(JSON.stringify(obj))
|
|||
|
|
|
|||
|
|
this.reset()
|
|||
|
|
|
|||
|
|
this.form.selected = obj // ? fix что делать с дефолтами
|
|||
|
|
|
|||
|
|
//this.form.selected.dirty = obj
|
|||
|
|
//this.form.selected.clean = {}
|
|||
|
|
|
|||
|
|
//this._cleanDirtyObject(this._schema, obj, this._clean)
|
|||
|
|
//this._cleanDirtyObject(this._schema, obj, this.form.selected.clean)
|
|||
|
|
|
|||
|
|
//console.log('CLEANED:', this.form.selected.clean)
|
|||
|
|
|
|||
|
|
this._mode = filling
|
|||
|
|
|
|||
|
|
let loader = new Loader({
|
|||
|
|
api: this._api,
|
|||
|
|
t: this.form,
|
|||
|
|
clean: this.form.selected,
|
|||
|
|
context: this._context,
|
|||
|
|
onError: err => {
|
|||
|
|
/// console.log('Loader.err: ', err)
|
|||
|
|
},
|
|||
|
|
onLoaded:() => {
|
|||
|
|
this._schema.forEach(p => {
|
|||
|
|
this._setFieldValue(p)
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// this._signature = JSON.stringify(this.form.selected.clean)
|
|||
|
|
// this._btnSubmit.disabled = true
|
|||
|
|
|
|||
|
|
// console.log('edit:', this._signature)
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
loader.load()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
Form.prototype._setFieldValue = function(f) {
|
|||
|
|
if (f.loading) {
|
|||
|
|
//console.log(`prop ${f.name} loading`)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let obj
|
|||
|
|
|
|||
|
|
if (this.form == f.form) {
|
|||
|
|
obj = f.form.selected
|
|||
|
|
} else {
|
|||
|
|
obj = f.form.selected.clean
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let v = obj[f.name]
|
|||
|
|
|
|||
|
|
//console.log(`_setFieldValue: ${f.name}=${v}`)
|
|||
|
|
|
|||
|
|
if (f.type == 'list') {
|
|||
|
|
//this._openForm(f)
|
|||
|
|
|
|||
|
|
if (Array.isArray(v)) {
|
|||
|
|
// fix
|
|||
|
|
v.forEach((itemObj, idx) => {
|
|||
|
|
//let cObj = cleanObj[f.name][idx]
|
|||
|
|
//this._appendListItem(f, cObj, dObj)
|
|||
|
|
this._appendListItem(f, itemObj)
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (this._mode == editing) {
|
|||
|
|
if (!f.isEditable) {
|
|||
|
|
this._disableField(f)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
} else {
|
|||
|
|
switch (f.type) {
|
|||
|
|
case 'checkboxes':
|
|||
|
|
// fix - event
|
|||
|
|
let checkedIDs = {}
|
|||
|
|
|
|||
|
|
if (Array.isArray(v)) {
|
|||
|
|
v.forEach(x => {
|
|||
|
|
checkedIDs[x] = true
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
if (f.scalar) {
|
|||
|
|
f.valueObjects.forEach((item, idx) => {
|
|||
|
|
//console.log('CHECK IF:', item, idx, checkedIDs)
|
|||
|
|
if (checkedIDs[item]) {
|
|||
|
|
f.elems[idx].checked = true
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
} else {
|
|||
|
|
let key = f.name
|
|||
|
|
if (f.key) {
|
|||
|
|
key = f.key
|
|||
|
|
}
|
|||
|
|
f.valueObjects.forEach((item, idx) => {
|
|||
|
|
//console.log('CHECK IF:', item, idx, checkedIDs)
|
|||
|
|
if (checkedIDs[item[key]]) {
|
|||
|
|
f.elems[idx].checked = true
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
case 'radios':
|
|||
|
|
//console.log('RADIOS:', f.valueObjects)
|
|||
|
|
if (v && Array.isArray(f.valueObjects)) {
|
|||
|
|
for (let i=0; i<f.valueObjects.length; i++) {
|
|||
|
|
let item = f.valueObjects[i]
|
|||
|
|
|
|||
|
|
if (f.scalar) {
|
|||
|
|
if (item == v) {
|
|||
|
|
let radio = f.elems[i]
|
|||
|
|
//radio.checked = true
|
|||
|
|
// Поле selected важно для снятия отметки при клике
|
|||
|
|
// на уже выделенном radio
|
|||
|
|
//f.selected = radio
|
|||
|
|
//console.log
|
|||
|
|
radio.dispatchEvent(new Event('click'))
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
if (item[f.name] == v) {
|
|||
|
|
let radio = f.elems[i]
|
|||
|
|
//console.log('RADIOS: before click is checked', radio.checked)
|
|||
|
|
radio.dispatchEvent(new Event('click'))
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
case 'select':
|
|||
|
|
f.elem.value = v
|
|||
|
|
f.elem.dispatchEvent(new Event('change'))
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
case '':
|
|||
|
|
f.value = v
|
|||
|
|
this._onElemEvent(f)
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
case 'line':
|
|||
|
|
case 'text':
|
|||
|
|
if (v !== undefined) {
|
|||
|
|
if (f.dataType == 'hex') {
|
|||
|
|
if (v) {
|
|||
|
|
v = base64ToHex(v)
|
|||
|
|
}
|
|||
|
|
} else if (f.dataType == 'int' && v === 0) {
|
|||
|
|
v = ''
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (f.set) {
|
|||
|
|
f.elem.value = f.set(v)
|
|||
|
|
} else {
|
|||
|
|
f.elem.value = v
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
//f.elem.dispatchEvent(new Event('input'))
|
|||
|
|
this._onElemEvent(f)
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
case 'checkbox':
|
|||
|
|
f.elem.checked = v
|
|||
|
|
f.elem.dispatchEvent(new Event('change'))
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
case 'model':
|
|||
|
|
//console.log('modelSelectType:', f.modelSelectType)
|
|||
|
|
//console.log('modelChangeType:', f.modelChangeType)
|
|||
|
|
|
|||
|
|
|
|||
|
|
if (f.modelChangeType == 'radio') {
|
|||
|
|
f.model.setSelected(f.modelSelectType, v)
|
|||
|
|
} else {
|
|||
|
|
f.model.checkList(f.modelSelectType, v)
|
|||
|
|
}
|
|||
|
|
this._onElemEvent(f)
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Отключать поля можно только если форма в режиме редактирования (именно топ форма)
|
|||
|
|
if (this._mode == editing) {
|
|||
|
|
if (f.editmode == Form.UNEDITABLE) {
|
|||
|
|
this._disableField(f)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
function insertError(opt) {
|
|||
|
|
let div = document.createElement('div')
|
|||
|
|
if (opt.class) {
|
|||
|
|
div.className = opt.class
|
|||
|
|
}
|
|||
|
|
let msg;
|
|||
|
|
if (typeof opt.err == 'string') {
|
|||
|
|
msg = opt.err
|
|||
|
|
} else {
|
|||
|
|
msg = opt.err.message
|
|||
|
|
}
|
|||
|
|
div.textContent = msg
|
|||
|
|
|
|||
|
|
if (opt.after) {
|
|||
|
|
opt.after.parentNode.insertBefore(div, opt.after.nextSibling)
|
|||
|
|
} else if (opt.before) {
|
|||
|
|
// например перед submit кнопкой
|
|||
|
|
opt.before.parentNode.insertBefore(div, opt.before)
|
|||
|
|
}
|
|||
|
|
return div
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
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
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
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;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// fix - hide открытые вложенные формы
|
|||
|
|
Form.prototype.hide = function() {
|
|||
|
|
this._formContainer.hidden = true
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// fix - show открытые вложенные формы
|
|||
|
|
Form.prototype.show = function() {
|
|||
|
|
this._formContainer.hidden = false
|
|||
|
|
}
|