- /**
- * Глобальные переменные и общие методы фреймворка __metadata.js__ <i>Oknosoft data engine</i>
- *
- * © Evgeniy Malyarov http://www.oknosoft.ru 2014-2016
- *
- * @module common
- * @submodule common.ui
- */
-
-
- /**
- * Описание структуры колонки формы списка
- * @class Col_struct
- * @param id
- * @param width
- * @param type
- * @param align
- * @param sort
- * @param caption
- * @constructor
- */
- function Col_struct(id,width,type,align,sort,caption){
- this.id = id;
- this.width = width;
- this.type = type;
- this.align = align;
- this.sort = sort;
- this.caption = caption;
- }
-
- /**
- * ### Объекты интерфейса пользователя
- * @class InterfaceObjs
- * @static
- * @menuorder 40
- * @tooltip Контекст UI
- */
- function InterfaceObjs(){
-
- var iface = this;
-
- /**
- * Очищает область (например, удаляет из div все дочерние элементы)
- * @method clear_svgs
- * @param area {HTMLElement|String}
- */
- this.clear_svgs = function(area){
- if(typeof area === "string")
- area = document.getElementById(area);
- while (area.firstChild)
- area.removeChild(area.firstChild);
- };
-
- /**
- * Возвращает координату левого верхнего угла элемента относительно документа
- * @method get_offset
- * @param elm {HTMLElement} - элемент, координату которого, необходимо определить
- * @return {Object} - {left: number, top: number}
- */
- this.get_offset = function(elm) {
- var offset = {left: 0, top:0};
- if (elm.offsetParent) {
- do {
- offset.left += elm.offsetLeft;
- offset.top += elm.offsetTop;
- } while (elm = elm.offsetParent);
- }
- return offset;
- };
-
- /**
- * Заменяет в строке критичные для xml символы
- * @method normalize_xml
- * @param str {string} - исходная строка, в которой надо замаскировать символы
- * @return {XML|string}
- */
- this.normalize_xml = function(str){
- if(!str) return "";
- var entities = { '&': '&', '"': '"', "'": ''', '<': '<', '>': '>'};
- return str.replace( /[&"'<>]/g, function (s) {return entities[s];});
- };
-
- /**
- * Масштабирует svg
- * @method scale_svg
- * @param svg_current {String} - исходная строка svg
- * @param size {Number|Object} - требуемый размер картинки
- * @param padding {Number} - отступ от границы viewBox
- * @return {String} - отмасштабированная строка svg
- */
- this.scale_svg = function(svg_current, size, padding){
- var j, k, svg_head, svg_body, head_ind, vb_ind, svg_head_str, vb_str, viewBox, svg_j = {};
-
- var height = typeof size == "number" ? size : size.height,
- width = typeof size == "number" ? (size * 1.5).round(0) : size.width,
- max_zoom = typeof size == "number" ? Infinity : (size.zoom || Infinity);
-
- head_ind = svg_current.indexOf(">");
- svg_head_str = svg_current.substring(5, head_ind);
- svg_head = svg_head_str.split(' ');
- svg_body = svg_current.substr(head_ind+1);
- svg_body = svg_body.substr(0, svg_body.length - 6);
-
- // получаем w, h и формируем viewBox="0 0 400 100"
- for(j in svg_head){
- svg_current = svg_head[j].split("=");
- if("width,height,x,y".indexOf(svg_current[0]) != -1){
- svg_current[1] = Number(svg_current[1].replace(/"/g, ""));
- svg_j[svg_current[0]] = svg_current[1];
- }
- }
-
- if((vb_ind = svg_head_str.indexOf("viewBox="))!=-1){
- vb_str = svg_head_str.substring(vb_ind+9);
- viewBox = 'viewBox="' + vb_str.substring(0, vb_str.indexOf('"')) + '"';
- }else{
- viewBox = 'viewBox="' + (svg_j.x || 0) + ' ' + (svg_j.y || 0) + ' ' + (svg_j.width - padding) + ' ' + (svg_j.height - padding) + '"';
- }
-
- var init_height = svg_j.height,
- init_width = svg_j.width;
-
- k = (height - padding) / init_height;
- svg_j.height = height;
- svg_j.width = (init_width * k).round(0);
-
- if(svg_j.width > width){
- k = (width - padding) / init_width;
- svg_j.height = (init_height * k).round(0);
- svg_j.width = width;
- }
-
- if(k > max_zoom){
- k = max_zoom;
- svg_j.height = (init_height * k).round(0);
- svg_j.width = (init_width * k).round(0);
- }
-
- svg_j.x = (svg_j.x * k).round(0);
- svg_j.y = (svg_j.y * k).round(0);
-
- return '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" ' +
- 'width="' + svg_j.width + '" ' +
- 'height="' + svg_j.height + '" ' +
- 'x="' + svg_j.x + '" ' +
- 'y="' + svg_j.y + '" ' +
- 'xml:space="preserve" ' + viewBox + '>' + svg_body + '</svg>';
- };
-
- /**
- * Добавляет в форму функциональность вызова справки
- * @method bind_help
- * @param wnd {dhtmlXWindowsCell}
- * @param [path] {String} - url справки
- */
- this.bind_help = function (wnd, path) {
-
- function frm_help(win){
- if(!win.help_path){
- $p.msg.show_msg({
- title: "Справка",
- type: "alert-info",
- text: $p.msg.not_implemented
- });
- return;
- }
- }
-
- if(wnd instanceof dhtmlXCellObject) {
- // TODO реализовать кнопку справки для приклеенной формы
- }else{
- if(!wnd.help_path && path)
- wnd.help_path = path;
-
- wnd.button('help').show();
- wnd.button('help').enable();
- wnd.attachEvent("onHelp", frm_help);
- }
-
- };
-
- /**
- * Устанавливает hash url для сохранения истории и последующей навигации
- * @method set_hash
- * @param [obj] {String|Object} - имя класса или объект со свойствами к установке в хеш адреса
- * @param [ref] {String} - ссылка объекта
- * @param [frm] {String} - имя формы объекта
- * @param [view] {String} - имя представления главной формы
- */
- this.set_hash = function (obj, ref, frm, view ) {
-
- var ext = {},
- hprm = $p.job_prm.parse_url();
-
- if(arguments.length == 1 && typeof obj == "object"){
- ext = obj;
- if(ext.hasOwnProperty("obj")){
- obj = ext.obj;
- delete ext.obj;
- }
- if(ext.hasOwnProperty("ref")){
- ref = ext.ref;
- delete ext.ref;
- }
- if(ext.hasOwnProperty("frm")){
- frm = ext.frm;
- delete ext.frm;
- }
- if(ext.hasOwnProperty("view")){
- view = ext.view;
- delete ext.view;
- }
- }
-
- if(obj === undefined)
- obj = hprm.obj || "";
- if(ref === undefined)
- ref = hprm.ref || "";
- if(frm === undefined)
- frm = hprm.frm || "";
- if(view === undefined)
- view = hprm.view || "";
-
- var hash = "obj=" + obj + "&ref=" + ref + "&frm=" + frm + "&view=" + view;
- for(var key in ext){
- hash += "&" + key + "=" + ext[key];
- }
-
- if(location.hash.substr(1) == hash)
- iface.hash_route();
- else
- location.hash = hash;
- };
-
- /**
- * Выполняет навигацию при изменении хеша url
- * @method hash_route
- * @param event {HashChangeEvent}
- * @return {Boolean}
- */
- this.hash_route = function (event) {
-
- var hprm = $p.job_prm.parse_url(),
- res = $p.eve.callEvent("hash_route", [hprm]),
- mgr;
-
- if((res !== false) && (!iface.before_route || iface.before_route(event) !== false)){
-
- if($p.ajax.authorized){
-
- if(hprm.ref && typeof _md != "undefined"){
- // если задана ссылка, открываем форму объекта
- mgr = _md.mgr_by_class_name(hprm.obj);
- if(mgr)
- mgr[hprm.frm || "form_obj"](iface.docs, hprm.ref)
-
- }else if(hprm.view && iface.swith_view){
- // если задано имя представления, переключаем главную форму
- iface.swith_view(hprm.view);
-
- }
-
- }
- }
-
- if(event)
- return iface.cancel_bubble(event);
- };
-
- /**
- * Запрещает всплывание события
- * @param e {MouseEvent|KeyboardEvent}
- * @returns {Boolean}
- */
- this.cancel_bubble = function(e) {
- var evt = (e || event);
- if (evt && evt.stopPropagation)
- evt.stopPropagation();
- if (evt && !evt.cancelBubble)
- evt.cancelBubble = true;
- return false
- };
-
- /**
- * Конструктор описания колонки динамического списка
- * @type {Col_struct}
- * @constructor
- */
- this.Col_struct = Col_struct;
-
- /**
- *
- * @param items {Array} - закладки сайдбара
- * @param buttons {Array} - кнопки дополнительной навигации
- * @param [icons_path] {String} - путь к иконам сайдбара
- */
- this.init_sidebar = function (items, buttons, icons_path) {
-
- // наблюдатель за событиями авторизации и синхронизации
- iface.btn_auth_sync = new iface.OBtnAuthSync();
-
- // кнопки навигации справа сверху
- iface.btns_nav = function (wrapper) {
- return iface.btn_auth_sync.bind(new iface.OTooolBar({
- wrapper: wrapper,
- class_name: 'md_otbnav',
- width: '260px', height: '28px', top: '3px', right: '3px', name: 'right',
- buttons: buttons,
- onclick: function (name) {
- iface.main.cells(name).setActive(true);
- return false;
- }
- }))
- };
-
- // основной сайдбар
- iface.main = new dhtmlXSideBar({
- parent: document.body,
- icons_path: icons_path || "dist/imgs/",
- width: 180,
- header: true,
- template: "tiles",
- autohide: true,
- items: items,
- offsets: {
- top: 0,
- right: 0,
- bottom: 0,
- left: 0
- }
- });
-
- // подписываемся на событие навигации по сайдбару
- iface.main.attachEvent("onSelect", function(id){
-
- var hprm = $p.job_prm.parse_url();
- if(hprm.view != id)
- iface.set_hash(hprm.obj, hprm.ref, hprm.frm, id);
-
- iface["view_" + id](iface.main.cells(id));
-
- });
-
- // включаем индикатор загрузки
- iface.main.progressOn();
-
- // активируем страницу
- var hprm = $p.job_prm.parse_url();
- if(!hprm.view || iface.main.getAllItems().indexOf(hprm.view) == -1){
- iface.set_hash(hprm.obj, hprm.ref, hprm.frm, "doc");
- } else
- setTimeout(iface.hash_route);
- };
-
- /**
- * ### Страница "Все объекты"
- * похожа на подменю "все функции" тонкого клиента 1С
- *
- * @class All_meta_objs
- * @param cont {dhtmlXCellObject} - контейнер для размещения страницы
- * @param [classes] {Array} - список классов. Если параметр пропущен, будут показаны все классы метаданных
- * @param [frm_attr] {Object} - дополнительные настройки, которые будут переданы создаваемым формам списка
- * @constructor
- */
- function All_meta_objs(cont, classes, frm_attr) {
-
- this.layout = cont.attachLayout({
- pattern: "2U",
- cells: [{
- id: "a",
- text: "Разделы",
- collapsed_text: "Разделы",
- width: 220
- }, {
- id: "b",
- text: "Раздел",
- header: false
- }],
- offsets: { top: 0, right: 0, bottom: 0, left: 0}
- });
-
- // дерево используемых метаданных
- this.tree = this.layout.cells("a").attachTreeView();
- this.tree.attachEvent("onSelect", function (name, mode) {
- if(!mode)
- return;
- var mgr = $p.md.mgr_by_class_name(name);
- if(mgr instanceof DataProcessorsManager){
- // для отчетов и обработок используем форму отчета
- mgr.form_rep(this.layout.cells("b"), frm_attr || {hide_header: true});
-
- }else if(mgr){
- // для остальных объектов показываем форму списка
- mgr.form_list(this.layout.cells("b"), frm_attr || {hide_header: true});
- }
-
- }.bind(this));
-
-
- if(!classes){
- var md_classes = $p.md.get_classes();
- classes = [];
- for(var cl in md_classes){
- if(md_classes[cl].length)
- classes.push(cl);
- }
- }
-
- // если тип объектов только один, скрываем иерархию
- if(classes.length == 1){
- $p.md.get_classes()[classes[0]].forEach(function (name) {
- var key = classes[0]+"."+name,
- meta = $p.md.get(key);
- if(!meta.hide){
- this.tree.addItem(key, meta.list_presentation || meta.synonym);
- this.tree.setItemIcons(key, {file: "icon_1c_"+classes[0]});
- }
- }.bind(this));
-
- }else{
- classes.forEach(function (id) {
- this.tree.addItem(id, $p.msg["meta_"+id]);
- this.tree.setItemIcons(id, {file: "icon_1c_"+id, folder_opened: "icon_1c_"+id, folder_closed: "icon_1c_"+id});
- $p.md.get_classes()[id].forEach(function (name) {
- var key = id+"."+name,
- meta = $p.md.get(key);
- if(!meta.hide){
- this.tree.addItem(key, meta.list_presentation || meta.synonym, id);
- this.tree.setItemIcons(key, {file: "icon_1c_"+id});
- }
- }.bind(this));
- }.bind(this));
- }
- }
-
- /**
- * ### Страница "Все объекты"
- * @property All_meta_objs
- * @type {All_meta_objs}
- */
- this.All_meta_objs = All_meta_objs;
-
- /**
- * ### Страница общих настроек
- * @param cont {dhtmlXCellObject} - контейнер для размещения страницы
- * @constructor
- */
- function Setting2col(cont) {
-
- // закладка основных настроек
- cont.attachHTMLString($p.injected_data['view_settings.html']);
- this.cont = cont.cell.querySelector(".dhx_cell_cont_tabbar");
- this.cont.style.overflow = "auto";
-
- // первая колонка настроек
- this.form2 = (function (cont) {
-
- var form = new dhtmlXForm(cont, [
-
- { type:"settings", labelWidth:80, position:"label-left" },
-
- {type: "label", labelWidth:320, label: "Адрес CouchDB", className: "label_options"},
- {type:"input" , inputWidth: 220, name:"couch_path", label:"Путь:", validate:"NotEmpty"},
- {type:"template", label:"",value:"",
- note: {text: "Можно указать как относительный, так и абсолютный URL публикации CouchDB", width: 320}},
-
- {type: "label", labelWidth:320, label: "Адрес http сервиса 1С", className: "label_options"},
- {type:"input" , inputWidth: 220, name:"rest_path", label:"Путь", validate:"NotEmpty"},
- {type:"template", label:"",value:"",
- note: {text: "Можно указать как относительный, так и абсолютный URL публикации 1С OData", width: 320}},
-
- {type: "label", labelWidth:320, label: "Значение разделителя данных", className: "label_options"},
- {type:"input" , inputWidth: 220, name:"zone", label:"Зона:", numberFormat: ["0", "", ""], validate:"NotEmpty,ValidInteger"},
- {type:"template", label:"",value:"", note: {text: "Для неразделенной публикации, зона = 0", width: 320}},
-
- {type: "label", labelWidth:320, label: "Суффикс базы пользователя", className: "label_options"},
- {type:"input" , inputWidth: 220, name:"couch_suffix", label:"Суффикс:"},
- {type:"template", label:"",value:"",
- note: {text: "Назначается абоненту при регистрации", width: 320}},
-
- {type:"block", blockOffset: 0, name:"block_buttons", list:[
- {type: "button", name: "save", value: "<i class='fa fa-floppy-o fa-lg'></i>", tooltip: "Применить настройки и перезагрузить программу"},
- {type:"newcolumn"},
- {type: "button", offsetLeft: 20, name: "reset", value: "<i class='fa fa-refresh fa-lg'></i>", tooltip: "Стереть справочники и перезаполнить данными сервера"}
- ]
- }
- ]);
-
- form.cont.style.fontSize = "100%";
-
- // инициализация свойств
- ["zone", "couch_path", "couch_suffix", "rest_path"].forEach(function (prm) {
- if(prm == "zone")
- form.setItemValue(prm, $p.wsql.get_user_param(prm));
- else
- form.setItemValue(prm, $p.wsql.get_user_param(prm) || $p.job_prm[prm]);
- });
-
- form.attachEvent("onChange", function (name, value, state){
- $p.wsql.set_user_param(name, name == "enable_save_pwd" ? state || "" : value);
- });
-
- form.disableItem("couch_suffix");
-
- if(!$p.job_prm.rest_path)
- form.disableItem("rest_path");
-
- form.attachEvent("onButtonClick", function(name){
-
- if(name == "save"){
-
- // завершаем синхронизацию
- $p.wsql.pouch.log_out();
-
- // перезагружаем страницу
- setTimeout(function () {
- $p.eve.redirect = true;
- location.reload(true);
- }, 1000);
-
- } else if(name == "reset"){
-
- dhtmlx.confirm({
- title: "Сброс данных",
- text: "Стереть справочники и перезаполнить данными сервера?",
- cancel: $p.msg.cancel,
- callback: function(btn) {
- if(btn)
- $p.wsql.pouch.reset_local_data();
- }
- });
- }
- });
-
- return form;
-
- })(this.cont.querySelector("[name=form2]").firstChild);
-
- // вторая колонка настроек
- this.form1 = (function (cont) {
-
- var form = new dhtmlXForm(cont, [
- { type:"settings", labelWidth:320, position:"label-left" },
-
- {type: "label", label: "Тип устройства", className: "label_options"},
- { type:"block", blockOffset: 0, name:"block_device_type", list:[
- { type:"settings", labelAlign:"left", position:"label-right" },
- { type:"radio" , name:"device_type", labelWidth:120, label:'<i class="fa fa-desktop"></i> Компьютер', value:"desktop"},
- { type:"newcolumn" },
- { type:"radio" , name:"device_type", labelWidth:150, label:'<i class="fa fa-mobile fa-lg"></i> Телефон, планшет', value:"phone"}
- ] },
- {type:"template", label:"",value:"", note: {text: "Класс устройства определяется автоматически, но пользователь может задать его явно", width: 320}},
-
- {type: "label", label: "Сохранять пароль пользователя", className: "label_options"},
- {type:"checkbox", name:"enable_save_pwd", label:"Разрешить:", labelWidth:90, checked: $p.wsql.get_user_param("enable_save_pwd", "boolean")},
- {type:"template", label:"",value:"", note: {text: "Не рекомендуется, если к компьютеру имеют доступ посторонние лица", width: 320}},
- {type:"template", label:"",value:"", note: {text: "", width: 320}},
-
- {type: "label", label: "Подключаемые модули", className: "label_options"},
- {type:"input" , position:"label-top", inputWidth: 320, name:"modifiers", label:"Модификаторы:", value: $p.wsql.get_user_param("modifiers"), rows: 3, style:"height:80px;"},
- {type:"template", label:"",value:"", note: {text: "Список дополнительных модулей", width: 320}}
-
- ]);
-
- form.cont.style.fontSize = "100%";
-
- // инициализация свойств
- form.checkItem("device_type", $p.job_prm.device_type);
-
- // подключаем обработчик изменения значений в форме
- form.attachEvent("onChange", function (name, value, state){
- $p.wsql.set_user_param(name, name == "enable_save_pwd" ? state || "" : value);
-
- });
-
- form.disableItem("modifiers");
- form.getInput("modifiers").onchange = function () {
- $p.wsql.set_user_param("modifiers", this.value);
- };
-
- return form;
-
- })(this.cont.querySelector("[name=form1]").firstChild);
-
- }
-
- /**
- * ### Страница общих настроек
- * @property Setting2col
- * @type {Setting2col}
- */
- this.Setting2col = Setting2col;
- }
-
- $p.__define({
-
- /**
- * Объекты интерфейса пользователя
- * @property iface
- * @for MetaEngine
- * @type InterfaceObjs
- * @static
- */
- iface: {
- value: new InterfaceObjs(),
- writable: false
- },
-
- /**
- * ### Текущий пользователь
- * Свойство определено после загрузки метаданных и входа впрограмму
- * @property current_user
- * @type CatUsers
- * @final
- */
- current_user: {
- get: function () {
- return $p.cat && $p.cat.users ?
- $p.cat.users.by_id($p.wsql.get_user_param("user_name")) :
- $p.utils.blank.guid;
- }
- },
-
- /**
- * ### Права доступа текущего пользователя.
- * Свойство определено после загрузки метаданных и входа впрограмму
- * @property current_acl
- * @type CcatUsers_acl
- * @final
- */
- current_acl: {
- get: function () {
- var res = {};
- if($p.cat && $p.cat.users_acl){
- $p.cat.users_acl.find_rows({owner: $p.current_user}, function (o) {
- res = o;
- return false;
- })
- }
- return res;
- }
- },
-
- /**
- * Загружает скрипты и стили синхронно и асинхронно
- * @method load_script
- * @for MetaEngine
- * @param src {String} - url ресурса
- * @param type {String} - "link" или "script"
- * @param [callback] {Function} - функция обратного вызова после загрузки скрипта
- * @async
- */
- load_script: {
- value: function (src, type, callback) {
-
- return new Promise(function(resolve, reject){
-
- var s = document.createElement(type);
- if (type == "script") {
- s.type = "text/javascript";
- s.src = src;
- s.async = true;
- s.addEventListener('load', callback ? function () {
- callback();
- resolve();
- } : resolve, false);
-
- } else {
- s.type = "text/css";
- s.rel = "stylesheet";
- s.href = src;
- }
- document.head.appendChild(s);
-
- if(type != "script")
- resolve()
-
- });
- }
- }
-
- });
-