/** * @version $Id$ * @requires jQuery * @requires _ Underscore.js */ var qs = { createObject: function() { return function() { this.initialize.apply(this, arguments); } }, openPopupByLocation: function(location, target, width, height) { if (typeof width != 'number') { if (width == 'MAX') { width = screen.width; } else { width = screen.width/2; } } if (typeof height != 'number') { if (height == 'MAX') { height = screen.height; } else { height = screen.height - screen.height/3; } } var top = screen.height/2 - height/2; var left = screen.width/2 - width/2; var params = ['toolbar=0', 'location=0', 'menubar=0', 'resizable=1', 'status=0', 'scrollbars=yes', 'screenX=' + left, 'screenY=' + top, 'top=' + top, 'left=' + left, 'width=' + width, 'height=' + height].join(','); var wnd = window.open(location, target, params); if (wnd && 'object' === typeof wnd) { wnd.opener = self; wnd.focus(); } else { alert('Popup blocked by your browser.'); } }, /** * Load JSON data using POST * * @param {String} url * @param {Object|String} request * @param {Object} [customOptions] * @return {jQuery.Deferred} */ ajax: function (url, request, customOptions) { var options = $.extend({ url: url, data: request, type: 'post', dataType: 'json', qsShowLoading: true, qsDefaultCallbacks: true }, customOptions); if (options.qsShowLoading) { qs.showLoading(); } var deferred = $.ajax(options); if (options.qsDefaultCallbacks) { deferred.done(function (response) { if (options.qsShowLoading) { qs.hideLoading(); } if (!response || response.isError) { var errorCallback = (response && response.errorCallback) ? response.errorCallback : {fn: qs.errorCallback, args: [response]}; qs.call(errorCallback); return this; } if (response && response.callbacks) { qs.call(response.callbacks); } return this; }); deferred.fail(function (xhr, textStatus, errorThrown) { if (options.qsShowLoading) { qs.hideLoading(); } qs.debug.debug('qs.ajax: Ajax request failed: ', textStatus, xhr); var message = (qs.constant('DEBUG')) ? xhr.responseText : textStatus + ' ' + errorThrown; alert(message); return this; }); } return deferred; }, errorCallback: function (response) { var message = (response && response.message) ? response.message : 'Ajax response is not valid'; qs.debug.debug('qs.ajax: Ajax response is not valid: ', response); alert(message); return this; }, showLoading: function () { qs.hideLoading(); $(document.createElement('div')) .append(document.createElement('div')) .attr('id', 'loading-box') .appendTo('body'); return this; }, hideLoading: function () { $("#loading-box").remove(); return this; } }; /* qs.call */ (function () { /** * Executes callback and returns its result. * If callback name is complex string - callback will be executed in specified context. * @example * qs.call(console.log); // context == window * qs.call('console.log'); // context == console * qs.call({fn: Function|String, args: Array|undefined, context: Object|undefined}) * @param {Function|String|Object|Array} callback Single or list of callbacks * @return {*|Array|undefined} Result(s) returned by callback(s) or undefined */ qs.call = function (callback) { var result = [], i, length, type = _.getType(callback); if ('Array' === type) { for (i = 0, length = callback.length; i < length; ++i) { result.push(_call(callback[i])); } return result; } else { result = _call(callback); } return result; }; var _call = function (callback) { var result, info, type = $.type(callback); if ('function' === type) { info = { context: window, fn: callback, args: undefined }; } else if ('string' === type) { info = _getFnInfo(callback); } else if ('object' === type && callback.fn) { info = _getFnInfo(callback.fn, callback.args, callback.context); } else { qs.debug.warn('Unknown callback format: ', callback); return result; } if ('function' === typeof info.fn && info.fn.apply && info.context) { if (typeof info.args == 'undefined' || info.args == null) { result = info.fn.apply(info.context); } else { result = info.fn.apply(info.context, info.args); } } else { qs.debug.warn('Invalid callback: ', callback, info); } return result; }; var _normalizeName = function (name) { name = '' + name; return ('window.' === name.substring(0, 7)) ? name.substring(7) : name; }; /** * @param {String|Function} fn * @param {Array|*} [args] * @param {String|Object} [context] * @return {Object} {context: Object, fn: Function, args: Array|undefined} * @private */ var _getFnInfo = function (fn, args, context) { var i, object, method, fnType = _.getType(fn), contextType = _.getType(context); if ('String' === fnType) { fn = _normalizeName(fn); /* split fn in to object and method */ if (-1 !== (i = fn.lastIndexOf('.'))) { object = fn.substring(0, i); method = fn.substring(i + 1); } else { object = null; method = fn; } object = (object) ? _.obj.get(window, object) : window; fn = _.obj.get(object, method); } if ('String' === contextType) { context = _.obj.get(window, _normalizeName(context)); } else { context = (context) ? context : (object ? object : window); } args = (undefined === args || _.isArray(args) ? args : [args]); return {context: context, fn: fn, args: args}; }; })(); /** * Returns value of global variable * @param {String} name * @return {*} */ qs.variable = function (name) { name = '' + name; return _.obj.get(window, ('window.' === name.substring(0, 7) ? name.substring(7) : name)); }; /* qs.constant */ (function () { /** * Returns constant value specified by **name**, or sets its value if **value** is passed * @param {String} name * @param {*} [value] * @return {*} */ qs.constant = function (name, value) { name = '' + name; var result, defined = _constants.hasOwnProperty(name); if ('undefined' === typeof value) { if (defined) { result = _constants[name]; } else { qs.debug && qs.debug.warn('Unknown constant "' + name + '"'); } } else { if (!defined) { _constants[name] = value; } else { qs.debug && qs.debug.warn('Constant "' + name + '" already defined'); } } return result; }; /** * Constants * @private */ var _constants = {}; })(); qs.debug = (function () { var _dump = function () { var result = '', separator = '', args = Array.prototype.slice.call(arguments); for (var i = 0, len = args.length; i < len; ++i) { result += separator + _dump.stringify(args[i]); separator = '\n'; } return result; }; _dump.options = { indent: ' ', levels: 5, types: true }; _dump.indent = function (level) { level = parseInt(level || 0); var str = ''; for (var i = 0; i < level; ++i) { str += _dump.options.indent; } return str; }; _dump.stringify = function(mixed, level, noFirstIndent) { level = level || 0; noFirstIndent = noFirstIndent || false; var type = _.getType(mixed), result = (_dump.converters[type]) ? _dump.converters[type](mixed, level) : _dump.direct(mixed, level); return (noFirstIndent ? '' : _dump.indent(level)) + (_dump.options.types ? '{' + type + '} ': '') + result; }; _dump.direct = function(value, level) { return '' + value; }; _dump.converters = { 'String': function (str, level) { return '"' + str.replace(/"/g, '\\"') + '"'; }, 'Object': function (obj, level) { var result = '', comma = ''; if (level < _dump.options.levels - 1) { for (var el in obj) { if (obj.hasOwnProperty(el)) { result += comma + _dump.indent(level + 1) + _dump.direct(el, level + 1) + ': ' + _dump.stringify(obj[el], level + 1, true); comma = ', \n'; } } } else { result += _dump.indent(level + 1) + '...'; } return '{\n' + result + '\n' + _dump.indent(level) + '}'; }, 'Array': function(arr, level){ var result = '', comma = ''; if (level < _dump.options.levels - 1) { for(var i = 0, c = arr.length; i < c; i++) { result += comma + _dump.indent(level + 1) + _dump.stringify(arr[i], level + 1, true); comma = ', \n'; } } else { result += _dump.indent(level + 1) + '...'; } return '[\n' + result + '\n' + _dump.indent(level) + ']'; }, 'Error': _dump.direct, 'RegExp': _dump.direct, 'Date': _dump.direct, 'Boolean': _dump.direct, 'Number': _dump.direct, 'Function': _dump.direct, 'undefined': _dump.direct, 'null': _dump.direct }; var fnAlert = function (args) { qs.constant('DEBUG') && alert(_dump.apply(null, arguments)); }, fnNoop = function () {}, methods = {error: fnAlert, warn: fnAlert, info: fnNoop, debug: fnNoop, log: fnNoop}; var consoleSupported = (function () { var supported = false; if (window.console) { supported = true; try { var C = function () {}; C.prototype = window.console; var c = new C(); c.log(); } catch (e) { supported = false; } } return supported; })(); var Debug = function () {}; Debug.prototype = (consoleSupported) ? window.console : Object; var debug = new Debug(); debug.dump = _dump; debug.vdie = function () { alert(_dump.apply(null, arguments)); }; for (var name in methods) { if (methods.hasOwnProperty(name) && 'function' !== typeof debug[name]) { debug[name] = methods[name]; } } return debug; })(); qs.http = { /** * Redirect user using "document.location" method * @param {String} url */ redirect: function (url) { document.location = '' + url; } }; qs.dom = { getParentTag: function (obj, tag) { var tmp = obj; while (tmp = tmp.parentNode) { if (tmp.nodeName == tag) { return tmp; } } return null; }, getPreviousTag: function(obj, tag) { var tmp = obj; while (tmp = tmp.previousSibling) { if (tmp.nodeName == tag) { return tmp; } } return null; }, getNextTag: function(obj, tag) { var tmp = obj; while (tmp = tmp.nextSibling) { if (tmp.nodeName == tag) { return tmp; } } return null; } }; /* qs.resource */ (function () { qs.resource = { STATE_FAILED: 'failed', STATE_LOADING: 'loading', STATE_COMPLETED: 'completed', STATE_NONE: 'none', loadTimeout: 10000, /** * Loads specified scripts and stylesheets * @param {String|Array} urls * @return {jQuery.Deferred} */ load: function (urls) { urls = _.isArray(urls) ? urls : [urls]; var deferredObjects = [], info; for (var i = 0, length = urls.length; i < length; ++i) { info = qs.resource.getInfo(urls[i]); if (info.type in _state) { deferredObjects.push(_loadResource(info)); } else { qs.debug.warn('qs.resource.load: Unknown resource type ', info.type); } } return $.when.apply($, deferredObjects); }, /** * Checks in given resource is loaded * @param {String} url * @param {String} type script|stylesheet * @return {String} */ getState: function (url, type) { var state = _getStateInfo(url, type); return (undefined === state) ? qs.resource.STATE_NONE : state.state; }, /** * * @param {String} url * @param {String} type * @param {Object} [options] * @constructor */ Info: function (url, type, options) { options && _.extend(this, options); this.url = '' + url; this.type = '' + type; return this; }, /** * @param {String} url * @return {qs.resource.Info} */ getInfo: function (url) { url = ('' + url).replace(/\/([^\/]*)\/\.\.\//g, '/'); var resourceType, ext, index, type, options, baseInfo = _getBaseInfo(), urlInfo = new _.Uri(url, baseInfo.source); /* external url check */ if (urlInfo.relativePath) { /* add revision suffix */ var relativePath = urlInfo.relativePath; index = relativePath.indexOf('/', 1); resourceType = relativePath.substring(1, index).match(/^(js|css)(?:|-\d+)?$/); resourceType = (resourceType) ? resourceType[1] : resourceType; if (resourceType && ('css' === resourceType || 'js' === resourceType)) { relativePath = '/' + resourceType + baseInfo.revSuffix + relativePath.substring(index); urlInfo.path = urlInfo.basePath + relativePath; } url = urlInfo.toString(); } index = urlInfo.parts.file.lastIndexOf('.'); ext = (0 < index) ? urlInfo.parts.file.substring(index + 1).toLowerCase() : ''; type = {js: 'script', css: 'stylesheet'}[ext]; return new qs.resource.Info(url, type); } }; var _state = { /** @type {Object} url: {state: String, deferred: jQuery.Deferred} */ script: null, stylesheet: null }, /** * @param {String} url * @param {String} type script|stylesheet * @param {Boolean} [createInfo] * @return {Object|undefined} */ _getStateInfo = function (url, type, createInfo) { url = '' + url; type = '' + type; createInfo = (undefined === createInfo) ? false : createInfo; if (type in _state) { if (null == _state[type] && type in _parsers) { _parsers[type](); } if (undefined === _state[type][url] && createInfo) { _state[type][url] = { state: qs.resource.STATE_NONE, deferred: $.Deferred() }; } return _state[type][url]; } else { qs.debug.warn('_getStateInfo: unknown resource type'); return undefined; } }, _setStateInfo = function (url, type, options) { url = '' + url; type = '' + type; if (type in _state) { if (null == _state[type]) { _state[type] = {}; } if (null == _state[type][url]) { _state[type][url] = { state: qs.resource.STATE_NONE, deferred: $.Deferred() }; } options && _.extend(_state[type][url], options); return _state[type][url]; } else { qs.debug.warn('_setStateInfo: unknown resource type'); return undefined; } }, _firstScript = document.getElementsByTagName('script')[0], /** * @param {qs.resource.Info} urlInfo * @return {jQuery.Deferred} */ _loadResource = function (urlInfo) { var userDeferred = $.Deferred(), _insertMethod = {script: _insertScript, stylesheet: _insertStylesheet}[urlInfo.type]; if (undefined === _insertMethod) { qs.debug.warn('Unknown resource type: ', urlInfo); userDeferred.reject(urlInfo); return userDeferred; } var stateInfo = _getStateInfo(urlInfo.url, urlInfo.type, true); switch (stateInfo.state) { case qs.resource.STATE_NONE: stateInfo.deferred.done(_doneCallback); stateInfo.deferred.fail(_failCallback); stateInfo.deferred.done(function () { userDeferred.resolve.apply(userDeferred, arguments); }); stateInfo.deferred.fail(function () { userDeferred.reject.apply(userDeferred, arguments); }); _insertMethod(stateInfo, urlInfo); break; case qs.resource.STATE_LOADING: stateInfo.deferred.done(function () { userDeferred.resolve.apply(userDeferred, arguments); }); stateInfo.deferred.fail(function () { userDeferred.reject.apply(userDeferred, arguments); }); break; case qs.resource.STATE_COMPLETED: userDeferred.resolve(urlInfo); break; case qs.resource.STATE_FAILED: userDeferred.reject(urlInfo); break; default: qs.debug.warn('Unknown resource state: ', stateInfo.state); userDeferred.reject(urlInfo); } return userDeferred; }, _doneCallback = function (urlInfo) { if (urlInfo instanceof qs.resource.Info && urlInfo.type in _state) { _state[urlInfo.type][urlInfo.url].state = qs.resource.STATE_COMPLETED; } else { qs.debug.warn('Unknown resource type', urlInfo); } }, _failCallback = function (urlInfo) { if (urlInfo instanceof qs.resource.Info && urlInfo.type in _state) { _state[urlInfo.type][urlInfo.url].state = qs.resource.STATE_FAILED; } else { qs.debug.warn('Unknown resource type', urlInfo); } }, _isReady = function (readyState) { return (!readyState || 'loaded' == readyState || 'complete' == readyState || 'uninitialized' == readyState); }, _insertScript = function (stateInfo, urlInfo) { var loaded = false, script = document.createElement('script'); script.type = 'text/javascript'; script.src = urlInfo.url; script.onreadystatechange = script.onload = function () { if (!loaded && _isReady(script.readyState)) { loaded = true; script.onload = script.onreadystatechange = null; stateInfo.deferred.resolve(urlInfo); } }; setTimeout(function () { if (!loaded) { loaded = true; script.onload = script.onreadystatechange = null; stateInfo.deferred.reject(urlInfo); } }, qs.resource.loadTimeout); _firstScript.parentNode.insertBefore(script, _firstScript); }, _insertStylesheet = function (stateInfo, urlInfo) { var link = document.createElement('link'); link.rel = "stylesheet"; link.type = "text/css"; link.href = urlInfo.url; _firstScript.parentNode.insertBefore(link, _firstScript); stateInfo.deferred.resolve(urlInfo); }, /* * Methods for searching loaded resources in page */ _parsers = { script: function () { var count = 0, src, nodes = document.getElementsByTagName('script'); for (var i = 0, length = nodes.length; i < length; ++i) { if (nodes[i].hasAttribute('src') && (src = _.str.trim(nodes[i].getAttribute('src')))) { ++count; src = qs.resource.getInfo(src).url; _setStateInfo(src, 'script', {state: qs.resource.STATE_COMPLETED}); } } return count; }, stylesheet: function () { var count = 0, href, nodes = $('link[rel="stylesheet"]').get(); for (var i = 0, length = nodes.length; i < length; ++i) { if (nodes[i].hasAttribute('href') && (href = _.str.trim(nodes[i].getAttribute('href')))) { ++count; href = qs.resource.getInfo(href).url; _setStateInfo(href, 'stylesheet', {state: qs.resource.STATE_COMPLETED}); } } return count; } }, _getBaseInfo = function () { if (undefined === _getBaseInfo.info) { _getBaseInfo.info = new _.Uri(qs.constant('BASE_URL')); _getBaseInfo.info.revSuffix = qs.constant('SITE_REVISION_SUFFIX'); } return _getBaseInfo.info; }; })(); /* qs.Message */ qs.Message = qs.createObject(); qs.Message.prototype = { initialize: function (messages) { this.messages = messages; }, get: function (name, language) { if (typeof language == 'undefined') { language = qs.constant('CURRENT_LANGUAGE'); } if (typeof this.messages[language] == 'undefined') { return ''; } if (typeof this.messages[language][name] == 'string') { return this.messages[language][name]; } if (typeof this.messages[qs.constant('DEFAULT_LANGUAGE')][name] == 'string') { return this.messages[qs.constant('DEFAULT_LANGUAGE')][name]; } return ''; } }; qs.paginator = { setIpp: function (value) { var _value = encodeURIComponent('' + value), cookieName = 'paginator-ipp-' + qs.constant('CURRENT_PAGE'), expires = new Date(); expires.setDate(expires.getDate() + 90); document.cookie = [ cookieName + '=' + _value, 'expires=' + expires.toUTCString(), 'path=' + window.location.pathname ].join('; '); var search = '' + window.location.search; if (search) { if (-1 === search.indexOf('ipp=')) { search += '&ipp=' + _value; } else { search = search.replace(/ipp=[^=&]*/i, 'ipp=' + _value); } } else { search = '?ipp=' + _value; } window.location.search = search; return this; } }; qs.imageFs = { thumbnailPath: 'thumbnails', notAvailableImage: 'images/image-not-available.png', /** * Returns path relative to basePath * @param {String} fileUrl * @param {String} [basePath] * @return {String} */ normalize: function (fileUrl, basePath) { fileUrl = _.str.trim(fileUrl || ''); if ('' === fileUrl) { return qs.imageFs.notAvailableImage; } basePath = '' + (basePath || '/'); if ('/' !== basePath.substr(0, 1)) { basePath = '/' + basePath; } if ('/' !== basePath.substr(basePath.length - 1)) { basePath += '/'; } var baseUrl = qs.constant('BASE_URL') + basePath, fileUri = new _.Uri(fileUrl), baseUri = new _.Uri(baseUrl), result = qs.imageFs.notAvailableImage; if ('' === fileUri.host || fileUri.host === baseUri.host) { if ('/' === fileUri.path.substr(0, 1)) { if (baseUri.path === fileUri.path.substr(0, baseUri.path.length)) { result = fileUri.path.substr(baseUri.path.length); } } else { if ('/' === basePath) { result = fileUri.path; } else if (basePath === fileUri.path.substr(0, basePath.length)) { result = fileUri.path.substr(basePath.length); } } } return result; }, /** * Returns relative url to resized thumbnail * This does not registers image size (@see Qs_ImageFs::registerSize()) * @param {String} fileUrl * @param {Number} [width] * @param {Number} [height] * @param {String} [methodAlias] Short method alias * @return {String} */ thumbnail: function (fileUrl, width, height, methodAlias) { var ext = '', pos; fileUrl = qs.imageFs.normalize(fileUrl || ''); fileUrl = qs.imageFs.thumbnailPath + '/' + fileUrl; width = +width || 0; height = +height || 0; methodAlias = '' + (methodAlias || ''); if (width || height) { if (-1 !== (pos = fileUrl.lastIndexOf('.')) && pos > fileUrl.lastIndexOf('/')) { ext = fileUrl.substr(pos); fileUrl = fileUrl.substr(0, pos); } fileUrl += '_' + width + 'x' + height + methodAlias + ext; } return fileUrl; } }; qs.form = {}; qs.form.select = { /** * Sets options for HTMLSelectElement * @param {jQuery|HTMLSelectElement} element * @param {Array} options Format: [{value:, title:,}, ...] * @param {String|Boolean} [emptyTitle] */ setOptions: function (element, options, emptyTitle) { var firstOption, option, type; element = (element instanceof jQuery) ? element : $(element); if (element.size() > 1 || 'HTMLSelectElement' !== _.getType(element.get(0))) { qs.debug.warn('Wrong param element passed to setOptions', element); return false; } if ('Array' !== (type = _.getType(options))) { qs.debug.warn('Wrong param options passed to setOptions: "' + type + '"'); return false; } if (emptyTitle) { if ('String' === _.getType(emptyTitle)) { option = document.createElement('option'); option.text = emptyTitle; option.value = ''; } else if (firstOption = element.get(0).options.item(0)) { option = document.createElement('option'); option.text = firstOption.text; option.value = firstOption.value; } } element.empty(); if (option) { element.append(option); } for (var i = 0, length = options.length; i < length; ++i) { if ('Object' === _.getType(options[i])) { option = document.createElement('option'); option.value = options[i].value; option.text = options[i].title; element.append(option); } } return this; } }; /** * Appends ".pack" and site revision suffix according to site config * @param {String} name * @returns {String} */ qs.getResourceName = function (name) { "use strict"; name = String(name || ''); var type = name.substr(name.lastIndexOf('.') + 1).toLowerCase(), revision = qs.constant('SITE_REVISION'), packSuffix = (qs.constant('USE_PACKED_RESOURCES')) ? '.pack' : '', resourceDir; if ('js' !== type && 'css' !== type) { return name; } if (revision) { resourceDir = type + '/'; if (0 === name.indexOf(resourceDir)) { name = type + '-' + revision + '/' + name.substr(resourceDir.length); } else if ((resourceDir = qs.constant('BASE_URL') + '/' + type + '/') && 0 === name.indexOf(resourceDir)) { name = qs.constant('BASE_URL') + '/' + type + '-' + revision + '/' + name.substr(resourceDir.length); } } if (packSuffix) { name = name.substring(0, name.lastIndexOf('.')) + packSuffix + '.' + type; } return name; }; /** * Scrolls window to specified element * @param {jQuery|HTMLElement|String} target DOM node or selector * @param {Number} [delay] */ qs.scrollToNode = function (target, delay) { "use strict"; target = (target instanceof jQuery) ? target : $(target); delay = parseInt(delay); setTimeout(function () { var wnd = $(window), viewTop, viewBottom, targetTop, targetBottom, inView = true, pos; if (target.length) { viewTop = wnd.scrollTop(); viewBottom = viewTop + (window.innerHeight) ? window.innerHeight : wnd.height(); targetTop = target.offset().top; targetBottom = targetTop + target.height(); inView = ((targetBottom <= viewBottom) && (targetTop >= viewTop)); pos = targetTop - (window.innerHeight / 2 ); inView || window.scrollTo(0, pos); } }, delay); return this; };