/** * @version $Id$ * @requires jQuery * @requires _ Underscore.js */ var Qs_ViewController_List = qs.createObject(); Qs_ViewController_List.defaultOptions = { /** @type {Array} Primary key fields */ primary: undefined, idItem: undefined, listId: undefined, url: undefined, /** * Structure: * {String|Function} constructor * {Object} options * {Object} instance Handler instance will be stored here */ handlers: { rowPreview: {}, checkboxColumn: {} } }; Qs_ViewController_List.prototype = { initialize: function (options) { this.options = $.extend({}, Qs_ViewController_List.defaultOptions, options); if (false === this.checkRequiredOptions()) { return this; } this.element = {}; this.element.list = $('#' + this.options.listId); if (!this.options.listId || 1 != this.element.list.size()) { qs.debug.warn('Qs_ViewController_List: List node not found'); return false; } this.initHandlers(); return this; }, initHandlers: function () { var handler, constructor, options; for (var name in this.options.handlers) { if (this.options.handlers.hasOwnProperty(name)) { handler = this.options.handlers[name]; if (handler.constructor) { constructor = ('function' === typeof handler.constructor) ? handler.constructor : qs.variable(handler.constructor); if ('function' === typeof constructor) { options = handler.options || {}; options.list = this; handler.instance = new constructor(options); } else { qs.debug.warn('Qs_ViewController_List: Incorrect handler constructor ', handler.constructor); } } handler = options = null; } } return this; }, /** * Returns handler instance * @param {String} name Handler name * @return {Object|undefined} * @see Qs_ViewController_List.defaultOptions.handlers */ getHandler: function (name) { return _.obj.get(this, 'options.handlers.' + name + '.instance'); }, checkRequiredOptions: function () { var requiredOptions = ['listId', 'primary', 'url'], missingOptions = []; for (var i = 0, length = requiredOptions.length; i < length; ++i) { if (undefined === this.options[requiredOptions[i]]) { missingOptions.push(requiredOptions[i]); } } if (missingOptions.length) { qs.debug.warn('Qs_ViewController_List: Required options missing: ', missingOptions); return false; } return true; }, /** * @param {jQuery|HTMLElement} itemNode Item container with data attribute that contains primary key * @return {Object} */ getPrimaryKey: function (itemNode) { itemNode = (itemNode instanceof jQuery) ? itemNode : $(itemNode); var primaryKey = {}, primary = this.options.primary; for (var i = 0, length = primary.length; i < length; ++i) { primaryKey[primary[i]] = itemNode.data(primary[i]); } if (0 === _.size(primaryKey)) { qs.debug.warn('Qs_ViewController_List.getPrimaryKey: Primary key not found.'); } return primaryKey; }, /** * @param {jQuery|HTMLElement} childNode * @return {jQuery} */ getItemNode: function (childNode) { childNode = (childNode instanceof jQuery) ? childNode : $(childNode); var itemNode = childNode.parents('tr:first'); if (1 !== itemNode.size()) { qs.debug.warn('Qs_ViewController_List.getItemNode: Item node can not be found.'); } return itemNode; } }; var Qs_ViewController_List_RowPreview = qs.createObject(); Qs_ViewController_List_RowPreview.defaultOptions = { /** @type {Qs_ViewController_List} */ list: undefined, controllerAction: undefined, handlerClass: undefined, caching: true, /** @type {Object} Additional data that will be passed with primary key */ requestData: undefined, /* showSpeed: undefined, showEasing: undefined, hideSpeed: undefined, hideEasing: undefined, */ itemExpandedClass: 'expanded', linkExpanded: { title: '↑', 'class': 'expanded' }, linkCollapsed: { title: '↓', 'class': 'collapsed' } }; Qs_ViewController_List_RowPreview.prototype = { initialize: function (options) { this.options = $.extend({}, Qs_ViewController_List_RowPreview.defaultOptions, options); if (!this.options.list || !this.options.list instanceof Qs_ViewController_List) { qs.debug.warn('Qs_ViewController_List_RowPreview: Incorrect option list ', this.options.list); return this; } this.element = {}; /** @type {Qs_ViewController_List} */ this.list = this.options.list; /** @type {jQuery} */ this.element.list = this.list.element.list; this.busy = false; this.cache = {}; this.lastCacheId = 0; this.expandLink = undefined; this.itemNode = undefined; this.previewNode = undefined; this.previewHandler = undefined; this.element.list.find('.row_expand_link').bind('click.list', _.bind(this.onRowExpandClick, this)); return this; }, onRowExpandClick: function (e) { e.preventDefault(); if (false == this.busy) { this.initItemPreview(e.currentTarget); } return true; }, initItemPreview: function (expandLink) { expandLink = (expandLink instanceof jQuery) ? expandLink : $(expandLink); var that = this, request, itemNode = this.list.getItemNode(expandLink), previewNode = this.getPreviewNode(itemNode); this.busy = true; /* hide current preview */ if (this.previewNode && this.previewNode.is(':visible') && previewNode.get(0) === this.previewNode.get(0)) { this.hideItemPreview(function () { that.busy = false; }); } else { var primary = this.list.getPrimaryKey(itemNode), cacheId = this.getCacheId(itemNode); /* load preview data or show it */ if (this.options.caching && this.cache[cacheId]) { this.showItemPreview(itemNode, previewNode, expandLink, this.cache[cacheId], function () { that.busy = false; }); } else { primary.action = this.options.controllerAction; primary.__idItem = this.list.options.idItem; request = (this.options.requestData) ? _.defaults(primary, this.options.requestData) : primary; qs.ajax(this.list.options.url, request, {cache: this.options.caching}) .done(function (response) { if (response && !response.isError) { if (that.options.caching) { that.cache[cacheId] = response; } that.showItemPreview(itemNode, previewNode, expandLink, response, function () { that.busy = false; }); } }); } } return this; }, getCacheId: function (itemNode) { var cacheId = itemNode.data('cacheId'); if (undefined === cacheId) { cacheId = this.lastCacheId++; itemNode.data('cacheId', cacheId); } return cacheId; }, hideItemPreview: function (completeCallback) { var that = this; /* destroy old preview */ if (this.previewHandler && this.previewHandler.destroy) { this.previewHandler.destroy(); this.previewHandler = undefined; } if (this.expandLink) { this.updateExpandLink(this.expandLink, false); } if (this.previewNode) { this.hidePreviewNode(this.previewNode, function () { that.itemNode.removeClass(that.options.itemExpandedClass); that.itemNode = undefined; that.previewNode.empty().removeClass(that.options.itemExpandedClass); that.previewNode = undefined; completeCallback && completeCallback(); }); } else if (completeCallback) { completeCallback(); } return this; }, /** * * @param {jQuery} itemNode * @param {jQuery} previewNode * @param {jQuery} expandLink * @param {Object} responseData Response data * @param {Function} completeCallback */ showItemPreview: function (itemNode, previewNode, expandLink, responseData, completeCallback) { var that = this, handlerClass; this.hideItemPreview(function () { previewNode.html(responseData.html); that.expandLink = expandLink; that.previewNode = previewNode; that.itemNode = itemNode; if (that.options.handlerClass) { if (null == (handlerClass = qs.variable(that.options.handlerClass))) { qs.debug.warn('Preview handler class can not be found'); return; } that.previewHandler = new handlerClass({ itemNode: itemNode, previewNode: previewNode, expandLink: expandLink, responseData: responseData }); } itemNode.addClass(that.options.itemExpandedClass); previewNode.addClass(that.options.itemExpandedClass); that.updateExpandLink(expandLink, true); that.showPreviewNode(previewNode, completeCallback); }); return this; }, /** * @param {jQuery} expandLink * @param {Boolean} state Expanded/Collapsed */ updateExpandLink: function (expandLink, state) { var state2name = ['linkCollapsed', 'linkExpanded'], oldOptions = this.options[state2name[+!state]], newOptions = this.options[state2name[+state]]; expandLink .removeClass(oldOptions['class']) /* oldOptions.class - такого yuicompressor не розуміє */ .addClass(newOptions['class']) /* newOptions.class - такого yuicompressor не розуміє */ .html(newOptions.title); return this; }, getPreviewNode: function (itemNode) { var previewNode = itemNode.next('tr.row_preview_content').children('td'); if (1 !== previewNode.size()) { qs.debug.warn('Qs_ViewController_List.getItemNode: Item preview node can not be found.'); } return previewNode; }, hidePreviewNode: function (previewNode, completeCallback) { /* previewNode.parent().slideUp(this.options.hideSpeed, this.options.hideEasing, completeCallback); */ var tr = previewNode.parent(); tr.data('oldDisplay', tr.css('display')); tr.css('display', 'none'); completeCallback && completeCallback(); return this; }, showPreviewNode: function (previewNode, completeCallback) { /* previewNode.parent().slideDown(this.options.showSpeed, this.options.showEasing, completeCallback); */ var tr = previewNode.parent(); tr.css('display', tr.data('oldDisplay') || ''); completeCallback && completeCallback(); return this; } }; var Qs_ViewController_List_ActionToolbar = qs.createObject(); Qs_ViewController_List_ActionToolbar.defaultOptions = { /** @type {Qs_ViewController_List} */ list: undefined, controllerAction: undefined, actions: undefined, cellClasses: undefined, attribs: { id: undefined, 'class': undefined } }; Qs_ViewController_List_ActionToolbar.prototype = { initialize: function (options) { this.options = $.extend({}, Qs_ViewController_List_RowPreview.defaultOptions, options); if (!this.options.list || !this.options.list instanceof Qs_ViewController_List) { qs.debug.warn('Qs_ViewController_List_ActionToolbar: Incorrect option list ', this.options.list); return this; } this.busy = false; this.element = {}; /** @type {Qs_ViewController_List} */ this.list = this.options.list; /** @type {jQuery} */ this.element.list = this.list.element.list; this.initElement(); this.refreshActionElement(); return this; }, initElement: function () { var cellSelector = this.classList2Selector(this.options.cellClasses), switcher = this.element.list.find('th' + cellSelector + ' :checkbox'), checkboxes = this.element.list.find('td' + cellSelector + ' :checkbox'); this.element.toolbar = $('#' + this.options.attribs.id); this.element.actions = this.element.toolbar.find('.multi_actions a.action'); this.element.checkboxes = checkboxes; this.element.switcher = switcher; if (1 !== this.element.toolbar.size()) { qs.debug.log('Qs_ViewController_List_ActionToolbar: Can not find action toolbar'); return this; } if (1 !== switcher.size()) { qs.debug.log('Qs_ViewController_List_ActionToolbar: Can not find switcher checkbox'); return this; } this.element.actions.bind('click.qsListToolbar', _.bind(this.onAction, this)); this.element.switcher.bind('change.qsListToolbar', _.bind(this.onSwitcherChange, this)); this.element.checkboxes.bind('change.qsListToolbar', _.bind(this.onCheckboxChange, this)); return this; }, classList2Selector: function (classList) { var selector = ''; for (var i = 0, length = classList.length; i < length; ++i) { selector += '.' + classList[i]; } return selector; }, setBusy: function (busy) { this.busy = !!busy; this.refreshActionElement(); return this; }, refreshActionElement: function () { var disabled = !!this.element.actions.hasClass('disabled'), doDisable = (this.busy || 0 == this.element.checkboxes.filter(':checked').size()); if (doDisable && !disabled) { this.element.actions.addClass('disabled'); } else if (disabled && !doDisable) { this.element.actions.removeClass('disabled'); } return this; }, onAction: function (e) { var target = $(e.currentTarget), multiAction = target.data('multiAction'); if (!target.hasClass('disabled') && !this.busy) { this.fireMultiAction(multiAction); } return true; }, confirmAction: function (multiAction) { var actionOptions, confirmation, result = true; actionOptions = this.options.actions[multiAction]; if (actionOptions && _.isObject(actionOptions)) { if ((confirmation = actionOptions['confirmation'])) { result = confirm(confirmation); } if ((confirmation = actionOptions['confirmationCallback'])) { result = qs.call(confirmation); } } return result; }, fireMultiAction: function (multiAction) { var that = this, selectedPrimary = this.getSelectedPrimary(); if (multiAction && selectedPrimary.length && this.confirmAction(multiAction)) { var request = { action: this.options.controllerAction, __idItem: this.list.options.idItem, multiAction: multiAction, selectedPrimary: selectedPrimary }; this.setBusy(true); qs.ajax(this.list.options.url + '?action=' + request.action, request).done(function () { that.setBusy(false); location.reload(); }); } return this; }, onSwitcherChange: function (e) { if (this.element.switcher.is(':checked')) { this.element.checkboxes.filter(':enabled').prop('checked', true); } else { this.element.checkboxes.prop('checked', false); } this.refreshActionElement(); return true; }, onCheckboxChange: function (e) { var target = $(e.currentTarget); if (!target.is(':checked') && this.element.switcher.is(':checked')) { this.element.switcher.prop('checked', false); } this.refreshActionElement(); return true; }, /** * Returns array of primary keys for selected items * @return {Array} Array of objects */ getSelectedPrimary: function () { var that = this, itemKeys, primaryKeys = []; this.element.checkboxes.filter(':checked').each(function () { itemKeys = that.list.getPrimaryKey(that.list.getItemNode(this)); primaryKeys.push(itemKeys); }); return primaryKeys; } };