Проблемы с созданием своего кастомного UI компонента

Есть задача - сделать модальный редактор (ui.window как база вполне подходит). Пробовал сделать сам, но постоянно какие-то баги - в общем я уже порядком устал дебажить исходники библиотеки, а статей на эту тему недостаточно. Да, что-то простое сделать - не вопрос, но когда что-то сложнее кнопки, то уже магия какая-то. Вот код компонента на текущий момент:

`
require(["locale"], function(t) {
	webix.protoUI({
		name: "modal-editor",
		defaults: {
			position: "center",
			modal: true,
			move: true,
			save: null,
			head: {
				view: "toolbar",
				cols: [
					{ view: "label" },
					{ view: "icon", icon: "times-circle", click: function () { this.getTopParentView().close(); }}
				]
			},
			body: {
				view: "form",
				autoheight: true,
				autowidth: true,
				scroll: false
			},
			buttons: [
				{ view: "button", type:"form", value: t("app.button.save", {_: "Save"}), width: 100, align: "center", click: function() { this.getTopParentView().save(); }}
			]
		},
		title_setter: function(value) {
			//this.$ready.push(function(){
			//	webix.message();
			//	console.log('ready, title setter=', value);
			//});
			//
			//console.log('direct title setter=', value);
			//console.log('title setter');
			//console.log(this.getHead());
			return value;
		},
		$init: function (config) {
			//this.$ready.push(this._init_popup);
			//if(webix.isUndefined(config._initialized)) {
			//	this.defaults = webix.extend({}, this.defaults);

				//var body = {};
				//webix.extend(body, webix.copy(this.defaults.body));
				//webix.extend(body, webix.copy(config.body || {}), true);

				//var buttons =  webix.copy(config.buttons || this.defaults.buttons);
				//body.elements = body.elements ? body.elements.concat(buttons) : buttons;
				//config.body = body;
				//config._initialized = true;
			//}

			//console.log(config);
			//var self = this;
			//
			//console.log(this._init_once);

			//if(webix.isUndefined(config._init_once)) {
			//	this.defaults = webix.extend({}, this.defaults);
//
//				var body = {};
//				webix.extend(body, webix.copy(this.defaults.body));
//				webix.extend(body, webix.copy(config.body || {}), true);
//				var buttons = webix.copy(config.buttons || this.defaults.buttons);
//				body.elements = body.elements ? body.elements.concat(buttons) : buttons;
//				config.body = body;
//				config.head = webix.copy(config.head || this.defaults.head);
				//config._init_once = true;
//			}
			this._init_once(config);
		},
		_init_once: function(config) {
			//console.log('popup=', config);
			//var obj = this._settings;
			//console.log(obj);
			//	this.defaults = webix.extend({}, this.defaults);

			var body = {};
			webix.extend(body, webix.copy(this.defaults.body));
			webix.extend(body, webix.copy(config.body || {}), true);

			var buttons =  webix.copy(config.buttons || this.defaults.buttons);
			body.elements = body.elements ? body.elements.concat(buttons) : buttons;
			config.body = body;
			config.head = webix.copy(config.head || this.defaults.head);

			this._init_once = function(){};
		},
		save: function() {
			var callback = (typeof this._settings.save == "function" ? this._settings.save : this._save_default);
			callback.apply(this);
		},
		_save_default: function() {
			var form = this.getBody();

			//console.log('-----');
			//console.log('this', this);
			//console.log('editor', editor);
			//console.log('form', form);
			//console.log('obj', form._viewobj);
			//console.log('-----');
			webix.extend(form, webix.ProgressBar);
			//var self = this;
			//console.log(this.id);
			var self = this;

			var blockEditor = function(start, close) {
				//editor.getHead().disabled = start;
				//editor.getBody().disabled = start;
				console.log('form view = ', form._viewobj);
				if(start) {
					form.showProgress();
				} else {
					form.hideProgress();
				}

				if(close) {
					self.close();
				}
			};

			var promise = webix.promise.defer().then(function() {
				blockEditor(false, true);
			}, function(error) {
				webix.message(error.message);
				blockEditor(false, error.close);
			});

			if(!form._bind_source) {
				promise.reject({ message: t("app.error.form-unbound", {_:"Form is not bound and can't be saved."}), close: true });
			} else {
				if(form.validate()) {
					var dp = webix.dp(form._bind_source);

					dp.attachEvent("onBeforeDataSend", function() {
						blockEditor(true, false);
					});

					dp.attachEvent("onAfterSave", function() {
						promise.resolve();
					});

					dp.attachEvent("onBeforeSaveError", function(id, status, obj, details) {
						var message = details.data.json().errorMessage || t("app.error.server-save", {_: "Unknown error during saving data at server."});
						promise.reject({ message: message, close: false });
					});

					form.save();
				}
			}
		}
	}, webix.ui.window);
});
`

Создаю по такому типу:

`
						var grid = webix.$$(node);
						var editor = webix.ui(webix.copy(update));
						editor.getBody().bind(grid);
						grid.select(id);
						editor.show();

`

Много кода закомментировано, так как пробовал разные подходы, но либо плодятся кнопки, либо не срабатывает ‘title_setter’, либо ругаются на не уникальные id.

p.s.: одновременно на экране может быть несколько таких окон, но активным будет самое последнее.

Текущая версия срабатывает ровно один раз, при повторной попытке сохранить данные (по-идее окно физически другое, но не тут-то было судя по ошибкам) начинает ругаться на то, что viewobj отсутствует. В общем нужна помощь, потому, что уже вложено куча времени на переделывание на проекта этой библиотеке и это было мое решение выбрать webix, а не extjs, но сроки горят, времени тратиться много не по фронту работ, а на изучение внутренностей библиотеки и уже начинают появляться сомнения в выборе :frowning: Очень много не очевидных вещей и чтобы понять логику надо лезть в код, документации и примеров реальных приложений и подход очень не хватает :frowning:

да, подключается все так:

`
define([
	"locale",
	"views/users/create-form",
	"views/users/update-form"
],function(t, create, update){
//.....
 var grid = webix.$$(node);
                        var editor = webix.ui(webix.copy(update));
                        editor.getBody().bind(grid);
                        grid.select(id);
                        editor.show();
//
});
`

Не думаю, что для Вашей задачи стоит создавать новый компонент. Вот простейший пример редактора:

http://webix.com/snippet/d829c79d

Использовать же внутренние свойства не рекомендуем ( _settings, _bind_source )

Пример эдитора как компоненты

http://webix.com/snippet/5859a554

Спасибо, попробую переделать - пока правда решил не использовать close, а hide, т.к. нужно уже результаты показать, а с hide хоть как-то, но работает.

p.s.: Вам бы одну большую статью с примерами и комментариями про создание приложения с нуля с каким-нибудь бэкэндом (стандартный вариант - “создаем свой блог”). Чтобы были какие-то простенькие компоненты созданные, например, как эти вот редакторы во внешнем окне, а не инлайновые и на этом же экране. И тогда это очень сильно сократило бы вход. Просто сейчас вся информация разнородная и не систематизированная - просто база знаний, но нет “best practices”.

А свой компонент в моем случае нужен, т.к. будет повторяемость кода.

P.S.:я так и не понял как проверить есть ли биндинг у формы без использования внутренних свойств.

я так и не понял как проверить есть ли биндинг у формы без использования внутренних свойств.

Вам не понадобится эта проверка, если эдитор будет открываться, когда он “привязан” к конкретному view. Ваш код использует _bind_source, чтобы установить обработчики событий. И эти обработчики устанавливаются каждый раз при вызове save. А обработчик события должен устанавливаться только один раз. Переместите данный код в $oninit для view (если речь идет о webix jet), к которому привязана форма.

понял насчет повторных событий, форма закрывалась через close() и про повторный save как-то упустил.