UNPKG

sbis3-node-ws

Version:

Модуль позволяет использовать ядро интерфейсного фреймворка sbis3-ws(`core.js`) и модуль работы с данными (`Source.js`) в приложении на nodejs.

1,336 lines (1,263 loc) 355 kB
/** * @fileoverview * @author elifantievon */ var global = (function(){ return this || (0,eval)('this'); }()); (function() { if ($ws.single.Storage.isReady('Source')) { return; } var EVENTS = ['onNewRecord', 'onParseData', 'onSettingsParsed', 'onComplete', 'onWayExists', 'onResults']; /** * Асинхронная загрузка XML-документа с шаблоном. * Для каждого файла непосредственно загрузка будет выполняться только один раз * * @param {String} templateName имя шаблона или путь (если начинается с . или /) * @param {Boolean} [fast] * @returns {$ws.proto.Deferred} */ $ws.core.loadTemplateFile = function(templateName, fast){ /** * Функция, запрашивающая шаблон. * @param {String} file конкретное абсолютное имя файла шаблона * @return {$ws.proto.Deferred} */ function requestTemplateFile(file){ //проверяем загружался ли хоть 1 из пакетов содержащий нужный шаблон, если нет - берем последний if (!fast && file instanceof Array){ for (var i = 0, l = file.length; i < l; i++){ if ($ws.single.Storage.isStored('resfile://' + file[i]) || i == l - 1){ file = file[i]; break; } } } var rid = $ && $.cookie && $.cookie('rightshash'); return $ws.single.Storage.store('resfile://' + file, function(dResult) { dResult.dependOn($ws.single.ioc.resolve('ITransport', { dataType: fast ? 'text' : 'xml', url: (function() { // При запросе XML допишем к версии идентификатор прав (передаваемый в cookie) var res = $ws.core.urlWithHost(file + (fast ? '.fast.xml' : '.xml')); if (rid) { res = res.replace(/(\.fast)?(\.v[0-9a-f]+)?(\.xml)$/, '$1.r' + rid + '$2$3'); } return res; })() }).execute(null)); }); } // Если ресурс пакетирован - грузим пакет и ищем там if($ws._const.xmlPackages[templateName] && !fast){ return new $ws.proto.Deferred().dependOn(requestTemplateFile($ws._const.xmlPackages[templateName])) .addCallback(function(res){ var root = res.documentElement; for(var j = root.childNodes.length - 1; j >= 0; --j){ if(root.childNodes[j].nodeType != $ws._const.nodeType.ELEMENT_NODE) { continue; } if(root.childNodes[j].getAttribute('id') == templateName) { return root.childNodes[j]; } } return new Error(templateName + ' is not found in the package ' + $ws._const.xmlPackages[templateName]); }); } // Иначе работаем по старой схеме var firstChar = templateName.charAt(0), base = ''; // Если первый символ . или / - значит это путь. Грузим его непосредственно. В противном случае // это или ресурс движка или ресурс по оглавлению if(firstChar !== '/' && firstChar !== '.') { // Проверим есть ли в оглавлении if(templateName in $ws._const.xmlContents) { templateName = $ws._const.xmlContents[templateName]; firstChar = templateName.charAt(0); if (firstChar !== '/'){ // Если у нас не абсолютный путь base = $ws._const.resourceRoot; } } else { // если нет - грузим из ресурсов движка base = $ws._const.wsRoot + 'res/xml/'; } } return requestTemplateFile(base + templateName); }; /** * Асинхронная загрузка инстанса класса $ws.proto.Template * Для каждого не первого вызова будет отдаваться ранее созданный инстанс * * @param {String} templateName имя шаблона * @param {Object} [options] * @param {Boolean} [options.fast=false] * @param {String} [options.html=''] * @returns {$ws.proto.Deferred} */ $ws.core.attachTemplate = function(templateName, options){ var res, block = BOOMR.plugins.WS.startBlock('$ws.core.attachTemplate:' + templateName), fast = false, html = ''; if (options && typeof options == 'object') { fast = options.fast || false; html = options.html || ''; } else { fast = arguments[1]; html = arguments[2]; } block.openSyncBlock(); res = $ws.single.Storage.store('res://' + templateName, function(dResult){ function createTpl (type, cont, tplName){ if (typeof $ws.proto[type] == 'function') { var templateInstance = new $ws.proto[type]({ templateXML : cont, template: cont, templateName: tplName }); templateInstance.getRenderResult().addCallback(function() { dResult.callback(templateInstance); }).addErrback(function(e) { dResult.errback(new Error('$ws.core.attachTemplate : ' + e.message)); }); } else { dResult.errback(new Error('$ws.core.attachTemplate : $ws.proto.' + type + ' is not implemented')); } } if (/js!/.test(templateName)){ require([templateName], function(constructor){ createTpl('CompoundControlTemplate', constructor, templateName); }, function(e){ dResult.errback(e); }); } else if (fast && html) { createTpl('FastTemplate', html, templateName); } else if (templateName) { $ws.core.loadTemplateFile(templateName, fast).addCallbacks(function(content){ createTpl(fast ? 'FastTemplate' : 'XMLTemplate', content, templateName); $ws.helpers.addXmlLog(templateName); return content; }, function(error){ dResult.errback(error); return error; }); } else { createTpl('EmptyTemplate', '', ''); } }).addBoth(function(res){ block.close(); return res; }); block.closeSyncBlock(); return res; }; $ws._const.templateParseConfig = { '#text': true, 'control': 'div', 'option' : false, 'style' : false, 'configuration' : false }; $ws._const.allowAttribute = { 'config': false }; $ws._const.nodeType = { 'ELEMENT_NODE': 1, 'ATTRIBUTE_NODE': 2, 'TEXT_NODE': 3, 'CDATA_SECTION_NODE': 4, 'ENTITY_REFERENCE_NODE': 5, 'ENTITY_NODE': 6, 'PROCESSING_INSTRUCTION_NODE': 7, 'COMMENT_NODE': 8, 'DOCUMENT_NODE': 9, 'DOCUMENT_TYPE_NODE': 10, 'DOCUMENT_FRAGMENT_NODE': 11, 'NOTATION_NODE': 12 }; /** * @class $ws.proto.Template * @public */ $ws.proto.Template = $ws.core.extend({}, /** @lends $ws.proto.Template.prototype */{ /** * @cfg {String} Название шаблона, оно же путь к нему * @name $ws.proto.Template#templateName */ $protected: { _templateName: '', _dReady: null, _loadedHandlers: {}, _configuration:{ autoWidth:false, autoHeight:false, isRelativeTemplate:false, minWidth:0 } }, $constructor: function(cfg){ this._templateName = cfg.templateName; this._dReady = new $ws.proto.ParallelDeferred(); this._dReady.getResult(). addCallback(this._attachResources.bind(this)). addCallback(this._loadDependencies.bind(this)); }, isPage: function() { throw new Error("Template::isPage - method is not implemented!"); }, _collectAllControlsToPreload: function(source) { throw new Error("Template::_collectAllControlsToPreload - method is not implemented!"); }, _loadDependencies: function() { var controlConfigs = this._collectAllControlsToPreload(this.getControls(undefined)), configsPaths = $ws.helpers.map(controlConfigs, function(cfg) { return [cfg.type, cfg]; }, []); return $ws.core.loadControlsDependencies(configsPaths, true); }, /** * Возвращает результат загрузки всех функций используемых в шаблоне * @return {$ws.proto.Deferred} результат загрузки */ getRenderResult : function(){ return this._dReady.getResult(); }, /** * Возвращает стиль окна, заданный при проектировании */ getStyle: function() { throw new Error("Template::getStyle - method is not implemented!"); }, /** * @returns {Object} Объект с параметрами width и height */ getDimensions: function(){ throw new Error("Template::getDimensions - method is not implemented!"); }, /** * @returns {Object} объект { h: String, w: String } с параметрами выравнивания ws-area */ getAlignment: function(){ throw new Error("Template::getAlignment - method is not implemented!"); }, /** * @returns {String} заголовок окна */ getTitle : function(){ throw new Error("Template::getTitle - method is not implemented!"); }, /** * @return {object} конфиг окна прописанный в шаблоне */ getConfig : function(node){ throw new Error("Template::getConfig - method is not implemented!"); }, /** * @returns {Object} Хэш-мэп событий, и подписантов на них. Подписанты передаются в виде массива */ getDeclaredHandlers: function() { return this._loadedHandlers; }, createMarkup: function(container) { throw new Error("Template::createMarkup - method is not implemented!"); }, /** * @param {String} parentId * @param {jQuery} templateRoot корневой элемент, в который встроен текущий шаблон * @returns {Object} параметры и типы контролов, присутствующих в шаблоне */ getControls: function(parentId, templateRoot){ throw new Error("Template::getControls - method is not implemented!"); }, getName : function(){ return this._templateName; }, _getIncludeDescriptorNodes: function() { throw new Error("Template::_getIncludeDescriptorNodes - method is not implemented!"); }, _attachResources: function() { var include = this._getIncludeDescriptorNodes(), pdResult = new $ws.proto.ParallelDeferred(); if (include.length > 0) { // Не может быть несколько инклюдов... Всегда берем первый var spec = include[0].getAttribute('spec'); if(spec) { try { spec = JSON.parse(spec); } catch(e) { spec = false; } if(spec) { if (spec.js) { pdResult.push($ws.core.attachSequentally(spec.js)); } if (spec.css) { pdResult.push($ws.core.attachSequentally(spec.css)); } } } } return pdResult.done().getResult(); }, _mergeAttrToConfig: function(config, attrName, attrValue) { if (attrName !== 'title' && attrName !== 'width' && attrName !== 'height'){ config[attrName] = attrValue === 'false' ? false : (attrValue === 'true' ? true : attrValue); } }, needSetZindexByOrder: function() { return false; } }); /** * @class $ws.proto.EmptyTemplate Класс - заглушка, представляющий собой пустой шаблон. * Его может быть удобно подставить вместо настоящего шаблона. * @extends $ws.proto.Template * @public */ $ws.proto.EmptyTemplate = $ws.proto.Template.extend({ $constructor: function() { this._dReady.done(); }, _collectAllControlsToPreload: function() { return []; }, isPage: function() { return true; }, getConfig: function() { return {}; }, getStyle: function() { return ''; }, getAlignment: function() { return { horizontalAlignment: 'Stretch', verticalAlignment: 'Stretch' }; }, getTitle: function() { return ''; }, createMarkup: function(container) { container.get(0).innerHTML = ''; }, getDimensions: function() { return { width: '', height: ''}; }, getControls: function() { return []; }, _getIncludeDescriptorNodes: function() { return []; } }); /** * @class $ws.proto.XMLTemplate * @extends $ws.proto.Template * @public */ $ws.proto.XMLTemplate = $ws.proto.Template.extend(/** @lends $ws.proto.Template.prototype */{ /** * @cfg {Document} XML-документ шаблона * @name $ws.proto.XMLTemplate#templateXML * @see templateName */ /** * @cfg {String} Название шаблона, оно же путь к нему * @name $ws.proto.XMLTemplate#templateName * @see templateXML */ $protected: { _xml : null, _toText : [], _html: '', _dReady: null, _innerTemplates: [], _templateName: '', _document : undefined, _controlTagCache: '' }, $constructor: function(cfg){ var self = this; this._xml = cfg.templateXML; this._document = this._xml.documentElement ? this._xml.documentElement : this._xml; this._innerTemplates[this._templateName = cfg.templateName] = true; this._includeInnerTemplate(new $ws.proto.Deferred()).addCallbacks(function(){ self._assignId(); self._loadEventHandlers(); }, function(e){ $ws.single.ioc.resolve('ILogger').log("Template", "Building failed. Reason: " + e.message); return e; }); var tag = this._xml.getElementsByTagName('style')[0]; if(tag){ var style = this._findCDATA(tag, true); if(style){ var cssReady = $ws.helpers.insertCss(style, true, this.getName()); if (cssReady) { this._dReady.push(cssReady); } } } }, _getIncludeDescriptorNodes: function() { var collection; if (this._xml.nodeType == $ws._const.nodeType.DOCUMENT_NODE) { collection = this._xml.firstChild.childNodes; } else { collection = this._xml.childNodes; } return $ws.helpers.filter(collection, function(child){ return child.nodeName.toUpperCase() == 'I'; }); }, isPage: function() { return this._document.getAttribute('isApplication') === 'false'; }, _collectAllControlsToPreload: function(source) { return this._getControlsByFilter(function() { return true; }); }, _extractControlConfiguration: function(controlNode) { var configObject, cfg = controlNode.getAttribute('config'); if(cfg) { try { configObject = eval("("+cfg+")"); } catch(e) { configObject = {}; } } else configObject = this._parseConfiguration(controlNode.getElementsByTagName('configuration')[0]); return configObject; }, /** * @param node где нужно искать * @param needValue что нужно вернуть - значение или узел * @return {String} styleData значение тега style */ _findCDATA: function(node, needValue){ var childNode, styleData; for(var i = 0, l = node.childNodes.length; i < l; i++){ childNode = node.childNodes[i]; if(childNode.nodeType === $ws._const.nodeType['CDATA_SECTION_NODE']){ styleData = needValue ? childNode.nodeValue : childNode; break; } } return styleData; }, /** * Ищет все теги include и инстанцирует их в нужные места * @param {$ws.proto.Deferred} dResult * @returns {$ws.proto.Deferred} */ _includeInnerTemplate : function(dResult){ var self = this, includeNodes = this._xml.getElementsByTagName('include'), dP = new $ws.proto.ParallelDeferred(), includeNode, parent, includeName, innerTemplates = [], styles = []; if(includeNodes.length === 0) dResult.callback(); else { for (var i = 0, l = includeNodes.length; i < l; i++){ includeNode = includeNodes[i]; parent = includeNode.parentNode; if((includeName = includeNodes[i].getAttribute('name')) !== null){ if(this._innerTemplates[includeName]) return dResult.errback("Cyclic dependency detected"); else{ innerTemplates.push(includeName); styles[i] = ""; (function(parent, includeNode, i) { dP.push($ws.core.loadTemplateFile(includeName).addCallback(function(xmlDoc){ var templateChildren = (xmlDoc.documentElement ? xmlDoc.documentElement.childNodes : xmlDoc.childNodes), tag = xmlDoc.getElementsByTagName('style')[0], newNode; if(tag){ styles[i] = self._findCDATA(tag, true); (xmlDoc.documentElement ? xmlDoc.documentElement : xmlDoc).removeChild(tag); } for(var j = 0, cnt = templateChildren.length; j < cnt; j++){ newNode = templateChildren[0]; if(self._xml.adoptNode) newNode = self._xml.adoptNode(newNode); parent.insertBefore(newNode, includeNode); } parent.removeChild(includeNode); return xmlDoc; })); })(parent, includeNode, i); } } else{ $ws.helpers.forEach(includeNode.childNodes, function(val){ if(val.getAttribute){ var source = val.getAttribute('source'); if(source) dP.push($ws.core.attach(source)); } }); } } dP.done().getResult().addCallback(function(){ var styleNode = self._xml.getElementsByTagName('style')[0]; for(var i = 0, l = innerTemplates.length; i< l; i++) self._innerTemplates[innerTemplates[i]] = true; if(styleNode) self._findCDATA(styleNode, false).nodeValue += styles.join(''); else{ styleNode = self._xml.createElement('style'); styleNode.appendChild(self._xml.createCDATASection(styles.join(''))); self._document.appendChild(styleNode); } self._includeInnerTemplate(dResult); }); } return dResult; }, /** * Проставляет id контролам у которых его нету. */ _assignId : function(){ var controls = this._getControlTags(this._xml); for (var i = 0, l = controls.length; i < l; i++){ var control = controls[i]; if (!control.getAttribute('id')){ control.setAttribute('id', $ws.helpers.randomId()); } } }, _getControlTags: function(where) { var result; if(this._controlTagCache) return this._controlTagCache; if(where.querySelectorAll) { var qsa = where.querySelectorAll('div[wsControl="true"]'); if(qsa) result = qsa; } if(!result) { result = []; var collection = where.getElementsByTagName('div'); for(var i = 0, l = collection.length; i < l; i++) { var item = collection[i]; if(item.getAttribute('wsControl')) result.push(item); } } return (this._controlTagCache = result); }, /** * Это оптимизация. * Используем более быстрый querySelectorAll если возможно * @returns {Array} */ _getFunctionOptions: function(){ var res; if(this._xml.querySelectorAll) { res = this._xml.querySelectorAll('option[type="function"]'); if(res) return res; } var functions = this._xml.getElementsByTagName('option'); res = []; for (var i = 0; i < functions.length; i++){ if (functions[i].getAttribute('type') === 'function') res.push(functions[i]); } return res; }, /** * Инициирует загрузку всех функций используемых в шаблоне */ _loadEventHandlers : function(){ var functions = this._getFunctionOptions(); for (var i = 0; i < functions.length; i++){ var name = functions[i].getAttribute('name'), value = functions[i].getAttribute('value'); if (value !== null && value.length > 0) this._dReady.push($ws.core.getHandler(value)); } this._dReady.push(this._processTemplateHandlers()).done(this); }, /** * Ставит в очередь на загрузку все хэндлеры диалогов, найденные в содержании * @returns {$ws.proto.Deferred} */ _processTemplateHandlers: function() { var dMainHandlers = new $ws.proto.ParallelDeferred(), self = this; var attrs = this._document.attributes, events = {}; for(var i = 0, l = attrs.length; i < l; i++) { var anAttr = attrs[i], attrName = anAttr.nodeName; if(attrName.substring(0, 2) == 'on' && anAttr.nodeValue !== '') { // it is event handler events[attrName] = anAttr.nodeValue.split('|'); } } if(!Object.isEmpty(events)) { /** * Здесь мы начинаем загрузку всех хандлеров, определенных на самом окне. * Здесь, а не ранее, потому что они будут иметь более высокий приоритет, * но не должны нарушить ранее заданный порядок. * * В колбэке основной цепочки стартуем ParallelDeferred, * зависящий от загрузки всех хандлеров на окне */ for(var eventName in events) { if(events.hasOwnProperty(eventName)) { (function(eN, hSpec) { self._loadedHandlers[eN] = []; for(var i = 0; i < hSpec.length; ++i) { (function(i) { dMainHandlers.push($ws.core.getHandler(hSpec[i]).addCallbacks(function(f){ self._loadedHandlers[eN][i] = f; return f; }, function(e){ $ws.single.ioc.resolve('ILogger').error( "Template", "Error while loading handler " + eN + ": " + e.message, e); return e; })); }(i)); } })(eventName, events[eventName]); } } } return dMainHandlers.done().getResult(); }, /** * Возвращает стиль окна, заданный при проектировании */ getStyle: function() { return this._document.getAttribute('windowStyle') || ""; }, /** * @returns {Object} Объект с параметрами width и height */ getDimensions: function(){ var width = this._document.getAttribute('width'), height = this._document.getAttribute('height'); return { width: !width? '': width.toLowerCase() === 'auto' ? 'auto' : width.indexOf('%') >= 0 ? width : parseInt(width, 10) + 'px', height: !height? '': height.toLowerCase() === 'auto' ? 'auto' : height.indexOf('%') >= 0 ? height : parseInt(height, 10) + 'px' }; }, /** * @returns {Object} объект { h: String, w: String } с параметрами выравнивания ws-area */ getAlignment: function(){ var h = this._document.getAttribute('HorizontalAlignment'), v = this._document.getAttribute('VerticalAlignment'); return { horizontalAlignment: !h ? 'Stretch' : h, verticalAlignment: !v ? 'Stretch' : v }; }, /** * @returns {String} заголовок окна */ getTitle : function(){ return this._document.getAttribute('title'); }, /** * @return {object} конфиг окна прописанный в шаблоне */ getConfig : function(node){ var cfg = this._configuration, tag = node || this._document; if (tag.attributes !== null){ for (var i = 0, l = tag.attributes.length; i < l; i++){ var att = tag.attributes[i]; this._mergeAttrToConfig(cfg, att.nodeName, att.nodeValue); } } return cfg; }, /** * @returns {Object} Хэш-мэп событий, и подписантов на них. Подписанты передаются в виде массива */ getDeclaredHandlers: function() { return this._loadedHandlers; }, createMarkup: function(container) { var block = BOOMR.plugins.WS.startBlock("mkup:" + this.getName()); var markup; if(this._html === '') { // TODO зачем здесь так? var markupNode = (this._xml.nodeName == 'ws-template' ? this._xml : this._xml.getElementsByTagName('ws-template')[0]); var ownerDoc = container.get(0).ownerDocument; var fragment = ownerDoc.createElement('div'); var col = markupNode ? markupNode.childNodes : []; var item, i = 0, l; while(col.length && i < col.length) { item = col.item(i); if(item.nodeType == $ws._const.nodeType.ELEMENT_NODE && item.nodeName != 'style') { item = $ws.proto.Template._importNode(ownerDoc, item, true); i++; fragment.appendChild(item); } else i++; } var configs = fragment.getElementsByTagName('configuration'); for(i = 0, l = configs.length; i < l; i++) { configs[0].parentNode.removeChild(configs[0]); } this._html = markup = fragment.innerHTML.replace(/<\/ ?br>/ig,"").replace(/&amp;/ig, "&"); } else markup = this._html; container.html(markup); block.close(); }, _getControlsByFilter: function(filter) { var controls = this._getControlTags(this._xml); var result = []; for(var i = 0, l = controls.length; i < l; i++) { var control = controls[i]; if(filter(control)) { var cfg = this._extractControlConfiguration(control); cfg.type = control.getAttribute('type'); cfg.id = control.getAttribute('id'); result.push(cfg); } } return result; }, /** * @param {String} parentId * @returns {Array} параметры и типы контролов, присутствующих в шаблоне */ getControls: function(parentId){ var block = BOOMR.plugins.WS.startBlock('ctrls:' + this.getName()), result = this._getControlsByFilter(function(control) { return parentId == control.getAttribute('parentId'); }); block.close(); return result; }, /** * Обрабатывает конфигурацию элементов управления * * @param {Node} configRoot Текщий узел разобра * @param {Boolean} [makeArray] обрабатывается ли в настоящий момент массив? * @returns {Object} Конфигурация контрола */ _parseConfiguration: function(configRoot, makeArray){ var name, value, type, // Это место переписано так не случайно. От старого вариант почему-то ВНЕЗАПНО ломался каверидж retvalFnc = function(){ var self = this; self.mass = makeArray ? [] : {}; self.push = function(name, value){ if (makeArray) self.mass.push(value); else if (name !== null) self.mass[name] = value; } }, retval = new retvalFnc(); if (configRoot && configRoot.childNodes){ var children = configRoot.childNodes; for (var i = 0, l = children.length; i < l; i++){ var child = children[i]; if (child.nodeName && child.nodeName == 'option'){ name = child.getAttribute('name'); type = child.getAttribute('type'); value = child.getAttribute('value'); //if (type === 'array' || name === null || value === null){ if (type === 'array' || (value === null && type != 'cdata')){ //Если не в листе дерева, то разбираем дальше рекурсивно if (value === null) value = this._parseConfiguration(child, type === 'array'); retval.push(name, value); } //добрались до листа дерева else //if (!makeArray){ switch (type){ case 'cdata': retval.push(name, this._findCDATA(child, true)); break; case 'boolean': retval.push(name, value === "true"); break; case 'function': if (typeof(value) === 'string' && value.length > 0){ var hdl = $ws.core.getHandler(value); if(hdl.isSuccessful()) { (function(name){ hdl.addCallback(function(handler){ if(typeof(handler) == 'function') { retval.push(name, handler); return handler; } else throw new Error("Integrity error! Some serious problems in $ws.core.getHandler()!"); // XXX: Potentially uncaught error condition }); })(name); } else throw new Error(value + " function is not ready or don't exist"); } break; case 'dialog': if (typeof(value) === 'string' && value.length > 0) { (function(value){ retval.push(name, function() { $ws.core.attachInstance("Control/Area:Dialog", { template: value, opener: this, context: (new $ws.proto.Context().setPrevious(this.getLinkedContext())) }) }) })(value); } break; case 'floatArea': if (typeof(value) === 'string' && value.length > 0) { (function(value){ retval.push(name, function() { var topParentContainer = this.getTopParent().getContainer(); this.setEnabled(false);//Отключаем кнопку, чтобы юзер 100500 раз панельку не вызывал $ws.core.attachInstance("Control/Area:FloatArea", { id:this.getId(), opener: this, name: this.getName()+'-floatArea', template: value, target: topParentContainer,//$('body'), side : 'right', autHide : true, showDelay : 300, animationLength : 300, offset: { x: 0, y: (window.scrollY > topParentContainer.offset().top) ? window.scrollY : 0 }, handlers: { 'onAfterClose' : function(){ this.getOpener().setEnabled(true);//включаем обратно } } }); }); })(value); } break; case 'command': if (typeof(value) === 'string' && value.length > 0) { (function(value){ retval.push(name, function(event) { event.setResult($ws.single.CommandDispatcher.sendCommand.apply($ws.single.CommandDispatcher, [this, value].concat(Array.prototype.slice.call(arguments, 1)))); }); })(value); } break; case 'page': case 'newpage': if(typeof(value) === 'string' && value.length > 0) { (function(value, type){ retval.push(name, function() { var link = typeof this.getOpenLink === 'function' ? this.getOpenLink() || value : value; if(type == 'page') window.location = link; else window.open(link); }); })(value, type); } break; case 'menu': if(typeof(value) === 'string' && value.length > 0) { (function(value){ retval.push(name, function() { if($ws.helpers.instanceOfModule(this, 'SBIS3.CORE.Button')){ this._options.menu = value; this.unsubscribe('onActivated', arguments.callee); this._initMenu(); this._notify('onActivated'); } }); })(value); } break; case null: default : if(value === "null"){ value = null; } if(value === "Infinity"){ value = Infinity; } retval.push(name, value); break; } //} } } } return retval.mass; }, needSetZindexByOrder: function() { return true; } }); $ws.proto.FastTemplate = $ws.proto.Template.extend({ $protected: { _options: { template: '' }, _dom: null, _rootConfig: {}, _controlsConfig: [] }, $constructor: function() { var self = this, preloadIndex = {}; var block = BOOMR.plugins.WS.startBlock("FastTemplate:ctor"); var block2 = BOOMR.plugins.WS.startBlock("FastTemplate:ctor:configuration:"+this.getName()); this._dom = document.createElement('div'); this._dom.innerHTML = this._options.template; if(!this._dom.firstChild) throw new Error("Wrong template (" + this.getName() + ") came from server"); var firstChild = this._dom.firstChild; this._rootConfig = JSON.parse(firstChild.getAttribute('templateConfig')); this._controlsConfig = JSON.parse(firstChild.getAttribute('config')); firstChild.removeAttribute('templateConfig'); firstChild.removeAttribute('config'); block2.close(); var block3 = BOOMR.plugins.WS.startBlock("FastTemplate:ctor:func_preload:"+this.getName()); $ws.helpers.forEach(this._controlsConfig.functions, function(val){ var path = '["' + val.split('/').join('"]["') + '"]'; var acessor = new Function("s", "val", "return arguments.length == 1 ? s" + path + " : (s" + path + " = val);"); var configRoot = self._controlsConfig; var fSpec = acessor(configRoot).split('#'); switch(fSpec[0]) { case 'function': self._dReady.push($ws.core.getHandler(fSpec[1]).addCallback(function(f){ acessor(configRoot, f); })); break; case 'moduleFunc': var m = fSpec[1].split("/"), fName = m.length > 1 ? m.pop() : '', mName = m.join('/'); self._dReady.push($ws.require("js!" + mName).addCallback(function(mod){ var fn = mod[0][fName]; fn.wsHandlerPath = 'js!' + mName + (fName ? ':' + fName : ''); acessor(configRoot, fn); })); break; case 'floatArea': acessor(configRoot, function(event) { var topParentContainer = this.getTopParent().getContainer(); this.setEnabled(false);//Отключаем кнопку, чтобы юзер 100500 раз панельку не вызывал $ws.core.attachInstance("Control/Area:FloatArea", { opener: this, template: fSpec[1], target: topParentContainer,//$('body'), side : 'right', autHide : true, animationLength : 300, offset: { x: 0, y: (window.scrollY > topParentContainer.offset().top) ? window.scrollY : 0 }, handlers: { 'onAfterClose' : function(){ this.getOpener().setEnabled(true);//включаем обратно } } }); }); break; case 'dialog': acessor(configRoot, function(){ $ws.core.attachInstance("Control/Area:Dialog", { template: fSpec[1], opener: this, context: (new $ws.proto.Context().setPrevious(this.getLinkedContext())) }) }); break; case 'command': var fn = function(event) { event.setResult($ws.single.CommandDispatcher.sendCommand.apply($ws.single.CommandDispatcher, [this, fSpec[1]].concat(Array.prototype.slice.call(arguments, 1)))); }; fn.isCommand = true; acessor(configRoot, fn); break; case 'newpage': case 'page': acessor(configRoot, function() { var link = typeof this.getOpenLink === 'function' ? this.getOpenLink() || fSpec[1] : fSpec[1]; if(fSpec[0] == 'page') window.location = link; else window.open(link); }); break; case 'menu': acessor(configRoot, function(){ if($ws.helpers.instanceOfModule(this, 'SBIS3.CORE.Button')){ this._options.menuName = fSpec[1]; this.unsubscribe('onActivated', arguments.callee); this._initMenu(); this._notify('onActivated'); } }); break; } }); block3.close(); var block4 = BOOMR.plugins.WS.startBlock("FastTemplate:ctor:handlers_preload:"+this.getName()); $ws.helpers.forEach(this._rootConfig, function(val, key){ if(key.substr(0, 2) == 'on') { var handlers = val.split('|'); $ws.helpers.forEach(handlers, function(val, idx){ self._dReady.push($ws.core.getHandler(val).addCallback(function(f){ self._loadedHandlers[key] = self._loadedHandlers[key] || []; self._loadedHandlers[key][idx] = f; return f; })); }); } }); block4.close(); if(this._rootConfig.style) { var cssReady = $ws.helpers.insertCss(this._rootConfig.style, true, this.getName()); if (cssReady) { this._dReady.push(cssReady); } } var include = $ws.helpers.filter(this._dom.firstChild.childNodes, function(child){ return child.nodeName == 'I'; }); if (include.length > 0) { // Не может быть несколько инклюдов... Всегда берем первый var spec = include[0].getAttribute('spec'); if(spec) { try { spec = JSON.parse(spec); } catch(e) { spec = false; } if(spec) { spec.js && self._dReady.push($ws.core.attachSequentally(spec.js)); spec.css && self._dReady.push($ws.core.attachSequentally(spec.css)); } } } this._dReady.done(); block.close(); }, _getIncludeDescriptorNodes: function() { return $ws.helpers.filter(this._dom.firstChild.childNodes, function(child){ return child.nodeName == 'I'; }); }, _collectAllControlsToPreload: function(source) { return $ws.helpers.reduce(source, function(res, item){ if (item.children && item.children.length) { res = res.concat(this._collectAllControlsToPreload(item.children)); } res.push(item); return res; }, [], this); }, isPage: function() { return !this._rootConfig.isApplication; }, getConfig: function() { $ws.helpers.forEach(this._rootConfig, function(attrValue, attr) { this._mergeAttrToConfig(this._configuration, attr, attrValue); }, this); return this._configuration; }, getStyle: function() { return this._rootConfig.windowStyle || ""; }, getAlignment: function() { var h = this._rootConfig.HorizontalAlignment, v = this._rootConfig.VerticalAlignment; return { horizontalAlignment: !h ? 'Stretch' : h, verticalAlignment: !v ? 'Stretch' : v }; }, getTitle: function() { return this._rootConfig.title; }, createMarkup: function(container) { var block = BOOMR.plugins.WS.startBlock("mkup:" + this.getName()); // Шилов Д.А. вернул, так выполняются скрипты container.html(this._dom.firstChild.innerHTML); block.close(); }, getDimensions: function() { var width = this._rootConfig.width + "", height = this._rootConfig.height + ""; return { width: !width? '': width.toLowerCase() === 'auto' ? 'auto' : width.indexOf('%') >= 0 ? width : parseInt(width, 10) + 'px', height: !height? '': height.toLowerCase() === 'auto' ? 'auto' : height.indexOf('%') >= 0 ? height : parseInt(height, 10) + 'px' }; }, getControls: function(parent) { if(parent) return []; return this._controlsConfig.children; }, needSetZindexByOrder: function() { return true; } }); $ws.proto.CompoundControlTemplate = $ws.proto.Template.extend({ $protected: { _options: { template: null }, _elementId: null, _docFragment: null, _instance: null }, $constructor: function(){ this._elementId = $ws.helpers.randomId(); this._loadedHandlers = this._options.template && this._options.template.handlers || {}; this._dReady.done(); }, _collectAllControlsToPreload: function(source) { return source[0] && [ source[0] ] || []; }, getConfig: function(){ var templateClass = this._options.template, proto = templateClass.prototype, result = {}, dimensions = templateClass.dimensions || {}; if (proto && proto._initializer) { proto._initializer.call(result); result = result._options || {}; //По умолчанию у xhtml-шаблонов нужно устанавливать isRelativeTemplate, чтоб ресайзера не было у //области по шаблону, которая загружает его if (result.isRelativeTemplate === undefined) { result.isRelativeTemplate = true; } } $ws.core.merge(result, dimensions); return result; }, getStyle: function() { return ''; }, getDimensions: function(){ var config = this.getConfig(); return $ws.helpers.reduce('width height'.split(' '), function(result, opt) { if (opt in config) { result[opt] = config[opt]; } return result; }, {}); }, isPage: function(){ return true; }, getTitle: function(){ var config = this.getConfig(); return (config && config.title) || this._options.template.title || ''; }, createMarkup: function(container, config){ var element = document.createElement('div'); element.setAttribute('id', this._elementId); if (config) { element._componentConfig = config; } container.append(element); }, getControls: function(parentId, templateRoot){ if (parentId && this._elementId != parentId) { return []; } var elem = templateRoot && templateRoot.find('#' + this._elementId).get(0); return [$ws.core.merge({ id: this._elementId, type: this._templateName }, (elem && elem._componentConfig) || {})]; }, _getIncludeDescriptorNodes: function() { // У CompoundControl все зависимости и подключения - в requirejs return []; } }); /** * Загрузчик внешних данных из хранимой процедуры * * @class $ws.proto.JSONLoader * @public */ $ws.proto.JSONLoader = $ws.core.extend({}, /** @lends $ws.proto.JSONLoader.prototype */{ $protected: { _options: { url: '/data.php', updateURL : '', destroyURL : '', tableName : '' } }, /** * Инициирует загрузку данных * @param {Object} arguments * @param {String} action выполняемая операция: чтение, редактирование, удаление * @returns {$ws.proto.Deferred} */ load: function(args, action){ var url = this._options.url; if(action === "update" && this._options.updateURL) url = this._options.updateURL; else if(action === "destroy" && this._options.destroyURL) url = this._options.destroyURL; if(this._options.tableName !== '') args["tableName"] = this._options.tableName; return $ws.single.ioc.resolve('ITransport', { method: 'POST', dataType: 'json', url: url + (/\?/.test(url) ? "&" : "?") + "$=1" + ($ws.core.debug ? "&" + Math.random() : "") }).execute(args); } }); /** * Класс "Набор данных" * * @class $ws.proto.RecordSet * @extends $ws.proto.Abstract * @public */ $ws.proto.RecordSet = $ws.proto.Abstract.extend(/** @lends $ws.proto.RecordSet.prototype */{ /** * @event onBeforeLoad Возникает в момент начала загрузки данных через Reader * @param {Object} eventObject описание в классе $ws.proto.Abstract * @param {Object}filter Значение фильтров, с которыми началась загрузка * @param {Object} options Опции, которые могли быть переданы при запросе */ /** * @event onAfterLoad Возникает после окончания загрузки данных (RecordSet готов к использованию) * @param {Object} eventObject описание в классе $ws.proto.Abstract * @param {$ws.proto.RecordSet} recordSet Экземпляр рекорд-сета * @param {Boolean} isSuccess успешна ли загрузка * @param {Error} error произошедшая ошибка (если загрузка неуспешная) * @param {Object} options Опции, которые могли быть переданы при запросе */ /** * @event onRecordUpdated Возникает после обновлении записи в текущем рекордсете * @param {Object} eventObject описание в классе $ws.proto.Abstract * @param {$ws.proto.Record} record измененная запись */ /** * @event onRecordDeleted Возникает при удалении обновлении записи в текущем рекордсете * @param {Object} eventObject описание в классе $ws.proto.Abstract * @param {String} primaryKey Первичный ключ удаленной записи * @param {$ws.proto.Record} record Сама удалённая запись */ /** * @event onAbort возникает при прерывании загрузки * @param {Object} eventObject описание в классе $ws.proto.Abstract */ /** * @event onPageChange при смене страницы рекордсета * @param {Object} eventObject описание в классе $ws.proto.Abstract */ /** * @event onShadowLoad При загрузке, если указать в методе loadNode параметр shadowed * @param {Object} eventObject описание в классе $ws.proto.Abstract * @param