//Author : @arboshiki /** * Generates random string of n length. * String contains only letters and numbers * * @param {int} n * @returns {String} */ Math.randomString = function (n) { var text = ""; var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; for (var i = 0; i < n; i++) text += possible.charAt(Math.floor(Math.random() * possible.length)); return text; }; /** * This function is for HTML element style attribute. * It converts the style attribute to css object * * @returns {object} */ String.prototype.getCss = function () { var css = {}; var style = this.valueOf().split(';'); for (var i = 0; i < style.length; i++) { style[i] = $.trim(style[i]); if (style[i]) { var s = style[i].split(':'); css[$.trim(s[0])] = $.trim(s[1]); } } return css; }; String.prototype.trim = function () { return this.replace(/^\s+|\s+$/g, ""); }; String.prototype.toCamel = function () { return this.replace(/(\-[a-z])/g, function ($1) { return $1.toUpperCase().replace('-', ''); }); }; String.prototype.toDash = function () { return this.replace(/([A-Z])/g, function ($1) { return "-" + $1.toLowerCase(); }); }; String.prototype.toUnderscore = function () { return this.replace(/([A-Z])/g, function ($1) { return "_" + $1.toLowerCase(); }); }; /** * Checks if number is between two numbers * * @param {number} num1 * @param {number} num2 * @param {boolean} including "include these numbers in comparison or not" default false * @returns boolean */ Number.prototype.isBetween = function (num1, num2, including) { if (!including) { if (this.valueOf() < num2 && this.valueOf() > num1) { return true; } } else { if (this.valueOf() <= num2 && this.valueOf() >= num1) { return true; } } return false; }; /** * Inserts element at specific index in given elements children * * @param {number} i * @param {string} selector * @returns {undefined} */ $.fn.insertAt = function (i, selector) { var object = selector; if (typeof selector === 'string') { object = $(selector); } i = Math.min(object.children().length, i); if (i == 0) { object.prepend(this); return this; } var oldIndex = this.data('index'); this.attr('data-index', i); object.find(">*:nth-child(" + i + ")").after(this); object.children().each(function (index, el) { var $el = $(el); if (oldIndex < i && index > oldIndex && index <= i) { $el.attr('data-index', parseInt($el.data('data-index'), 10) - 1); } else if (oldIndex >= i && index > i && index <= oldIndex) { $el.attr('data-index', parseInt($el.attr('data-index'), 10) + 1); } }); return this; }; $.fn.disableSelection = function () { return this .attr('unselectable', 'on') .css('user-select', 'none') .on('selectstart', false); }; $.fn.enableSelection = function () { return this .removeAttr('unselectable') .css('user-select', 'initial') .off('selectstart'); }; $(function () { var STORAGE_PREFIX = 'lobipanel_'; var LobiPanel = function ($el, options) { var me = this; this.hasRandomId = false; this.storage = null; this.$el = $el; if (!me.$el.data('inner-id')) { me.hasRandomId = true; me.$el.attr('data-inner-id', Math.randomString(10)); } this.innerId = me.$el.data('inner-id'); this.$options = me._processInput(options); me.$heading = this.$el.find('>.card-header'); me.$body = this.$el.find('>.card-body'); me._init(); me.$el.css('display', 'none'); me._applyState(me.$options.state, me.$options.stateParams); me.$el.css('display', 'block'); me._applyIndex(me.$options.initialIndex); }; LobiPanel.prototype = { _processInput: function (options) { var me = this; if (!options) { options = {}; } if (!me.hasRandomId) { me.storage = localStorage.getItem(STORAGE_PREFIX + me.innerId); me.storage = JSON.parse(me.storage) || {}; } var opts = me._getOptionsFromAttributes(); // window.console.log(opts); options = $.extend({}, $.fn.lobiPanel.DEFAULTS, me.storage, options, opts); var objects = ['unpin', 'reload', 'expand', 'minimize', 'close', 'editTitle']; for (var i = 0; i < objects.length; i++) { var prop = objects[i]; if (typeof options[prop] === 'object') { options[prop] = $.extend({}, $.fn.lobiPanel.DEFAULTS[prop], options[prop], opts[prop]); } } return options; }, _init: function () { var me = this; me.$el.addClass('lobipanel'); me.$heading.append(me._generateControls()); //------------------------------------------------------------------------------ var parent = me.$el.parent(); me._appendInnerIdToParent(parent, me.innerId); me._enableSorting(); me._onToggleIconsBtnClick(); me._enableResponsiveness(); me._setBodyHeight(); if (me.$options.autoload) { me.load(); } var maxWidth = 'calc(100% - ' + me.$heading.find('.dropdown-menu').children().length * me.$heading.find('.dropdown-item').first().outerWidth() + "px)"; me.$heading.find('.card-title').css('max-width', maxWidth); me._triggerEvent("init"); }, /** * Checks if panel is initialized. Panel is initialized if it has * lobipanel class and data-inner-id="" attribute * * @returns {boolean} */ isPanelInit: function () { var me = this; return me.$el.hasClass('lobipanel') && me.$el.data('inner-id'); }, /** * Checks if panel is pinned or unpinned * * @returns {Boolean} */ isPinned: function () { var me = this; return !me.$el.hasClass('panel-unpin'); }, /** * Pin the panel * * @returns {LobiPanel} */ pin: function () { var me = this; //disable resize functionality me.disableResize(); me.disableDrag(); me._enableSorting(); //remove on panel click event (which brings the panel into front) me._offPanelClick(); //remove panel-unpin class me.$el.removeClass('panel-unpin') //save current position, z-index and size to use it for later unpin .attr('old-style', me.$el.attr('style')) .removeAttr('style') .css('position', 'relative'); me.$body.css({ width: '', height: '' }); me._setBodyHeight(); me._insertInParent(); return me; }, /** * Unpin the panel * * @returns {LobiPanel} */ unpin: function () { var me = this; if (me.$el.hasClass("panel-collapsed")) { return me; } me._disableSorting(); if (me.$el.attr('old-style')) { me.$el.attr('style', me.$el.attr('old-style')); } else { var width = me.$el.width(); var height = me.$el.height(); var left = Math.max(0, (($(window).width() - me.$el.outerWidth()) / 2)); var top = Math.max(0, ($(document).scrollTop() + ($(window).height() - me.$el.outerHeight()) / 2)); me.$el.css({ left: left, top: top, width: width, height: height }); } var res = me._getMaxZIndex(); me.$el.css('z-index', res['z-index'] + 1); me._onPanelClick(); me.$el.addClass('panel-unpin'); $('body').append(me.$el); var panelWidth = me._getAvailableWidth(me.$el.width()); var panelHeight = me._getAvailableHeight(me.$el.height()); me.$el.css({ position: 'absolute', width: panelWidth, height: panelHeight }); //we give .card-block to width and height in order .card-block to start scroling var bHeight = me._calculateBodyHeight(panelHeight); var bWidth = me._calculateBodyWidth(panelWidth); me.$body.css({ width: bWidth, height: bHeight }); if (me.$options.draggable) { me.enableDrag(); } if (me.$options.resize !== 'none') { me.enableResize(); } return me; }, /** * Toggles (pin or unpin) the panel * * @returns {LobiPanel} */ togglePin: function () { var me = this; if (this.isPinned()) { this.unpin(); } else { this.pin(); } return me; }, /** * Checks if panel is minimized or not. It does not matter if panel is pinned or not * * @returns {Boolean} */ isMinimized: function () { var me = this; return me.$el.hasClass('panel-minimized') || me.$el.hasClass('panel-collapsed'); }, /** * Minimize the panel. If panel is pinned it is minimized on its place * if panel is unpinned it is minimized at the bottom of the page * * @returns {LobiPanel} */ minimize: function () { var me = this; me._triggerEvent("beforeMinimize"); if (me.isMinimized()) { return me; } if (me.isPinned()) { me.$body.slideUp(); me.$el.find('.card-footer').slideUp(); me.$el.addClass('panel-collapsed'); me._saveState('collapsed'); me._changeClassOfControl(me.$heading.find('[data-func="minimize"]')); } else { me.disableTooltips(); //get footer where we need to put panel var footer = me._getFooterForMinimizedPanels(); //find other panels which are already inside footer var children = footer.find('>*'); var left, top; //get top coordinate of footer top = footer.offset().top; //if there are no other panels inside footer, this panel will be first //and its left coordinate will be footer's left coordinate if (children.length === 0) { left = footer.offset().left; } else { //if there exist panels inside footer, then this panel's left //coordinate will be last panel's (in footer) right coordinate var ch = $(children[children.length - 1]); left = ch.offset().left + ch.width(); } //if panel was not expanded and it was jus unpin we need to save //panel's style if (!me.$el.hasClass('panel-expanded')) { me.$el.attr('old-style', me.$el.attr('style')); } me.$el.animate({ left: left, top: top, width: 200, height: footer.height() }, 100, function () { //if panel was expanded on full screen before we minimize it //after minimization we remove 'panel-expanded' class and we change icon if (me.$el.hasClass('panel-expanded')) { me.$el.removeClass('panel-expanded'); me.$el.find('.card-header [data-func=expand] .' + LobiPanel.PRIVATE_OPTIONS.iconClass) .removeClass(me.$options.expand.icon2) .addClass(me.$options.expand.icon) ; } //we add 'panel-minimized' class me.$el.addClass('panel-minimized'); me.$el.removeAttr('style'); me.disableDrag(); me.disableResize(); me._expandOnHeaderClick(); //animation was made and panel is positioned in place we it must be //so we append panel into footer footer.append(me.$el); $('body').addClass('lobipanel-minimized'); var maxWidth = 'calc(100% - ' + me.$heading.find('.dropdown-item>a:visible').length * me.$heading.find('.dropdown-item>a:visible').first().outerWidth() + "px)"; me.$heading.find('.card-title').css('max-width', maxWidth); me._saveState('minimized'); me._triggerEvent("onMinimize"); }); } return me; }, /** * Maximize the panel. This method works for minimized panel. * If panel is pinned it's maximized on its place. * If panel is unpinned it's maximized on position from which it was minimized * * @returns {LobiPanel} */ maximize: function () { var me = this; me._triggerEvent("beforeMaximize"); if (!me.isMinimized()) { return me; } if (me.isPinned()) { me.$body.slideDown(); me.$el.find('.card-footer').slideDown(); me.$el.removeClass('panel-collapsed'); me._saveState('pinned'); me._changeClassOfControl(me.$heading.find('[data-func="minimize"]')); } else { me.enableTooltips(); //we get css style which was saved before minimization var css = me.$el.attr('old-style').getCss(); //we give panel these css properties, coz animation work me.$el.css({ position: css.position || 'fixed', 'z-index': css['z-index'], left: me.$el.offset().left, top: me.$el.offset().top, width: me.$el.width(), height: me.$el.height() }); //we append panel into body $('body').append(me.$el); //It is not possible to make animations to these propeties and we remove it delete css['position']; delete css['z-index']; // css['position'] = 'absolute'; //and we animate panel to its saved style me.$el.animate(css, 100, function () { //we remove position property from style, before 'panel-unpin' //class has it to absolute me.$el.css('position', ''); me.$el.removeClass('panel-minimized'); //as panel is already in its place we remove 'old-style' property me.$el.removeAttr('old-style'); if (me.$options.draggable) { me.enableDrag(); } me.enableResize(); me._removeExpandOnHeaderClick(); //If there are no other elements inside footer, remove it also var footer = me._getFooterForMinimizedPanels(); if (footer.children().length === 0) { footer.remove(); } $('body').removeClass('lobipanel-minimized') .addClass('lobipanel-minimized'); var maxWidth = 'calc(100% - ' + me.$heading.find('.dropdown-item').length * me.$heading.find('.dropdown-item').first().outerWidth() + "px)"; me.$heading.find('.card-title').css('max-width', maxWidth); me._updateUnpinnedState(); me._triggerEvent("onMaximize"); }); } return me; }, /** * Toggles (minimize or maximize) the panel state. * * @returns {LobiPanel} */ toggleMinimize: function () { var me = this; if (me.isMinimized()) { me.maximize(); } else { me.minimize(); } return me; }, /** * Checks if panel is on full screen * * @returns {Boolean} */ isOnFullScreen: function () { var me = this; return me.$el.hasClass('panel-expanded'); }, /** * Expands the panel to full screen size * * @returns {LobiPanel} */ toFullScreen: function () { var me = this; me._triggerEvent("beforeFullScreen"); if (me.$el.hasClass("panel-collapsed")) { return me; } me._changeClassOfControl(me.$heading.find('[data-func="expand"]')); me.$el.css('position', 'fixed'); var res = me._getMaxZIndex(); //if panel is pinned or minimized, its position is not absolute and //animation will not work correctly so we change its position and //other css properties and we append panel into body if (me.isPinned() || me.isMinimized()) { me.enableTooltips(); me.$el.css({ "z-index": res["z-index"] + 1, left: me.$el.offset().left, top: me.$el.offset().top - $(window).scrollTop(), width: me.$el.width(), height: me.$el.height() }); $('body').append(me.$el); //If we are expanding panel to full screen from footer and in footer there are no more elements //remove footer also var footer = me._getFooterForMinimizedPanels(); if (footer.children().length === 0) { footer.remove(); } } else { me.$body.css({ width: '', height: '' }); me._setBodyHeight(); } //if panel is not minimized we save its style property, because when //toSmallSize() method is called panel needs to have style, it had before calling method // toFullScreen() if (!me.isMinimized()) { me.$el.attr('old-style', me.$el.attr('style')); me.disableResize(); } else { me.$el.removeClass('panel-minimized'); me._removeExpandOnHeaderClick(); } //get toolbar var toolbar = $('.' + LobiPanel.PRIVATE_OPTIONS.toolbarClass); var toolbarHeight = toolbar.outerHeight() || 0; me.$el.animate({ width: $(window).width(), height: $(window).height() - toolbarHeight, left: 0, top: 0 }, me.$options.expandAnimation, function () { me.$el.css({ width: '', height: '', right: 0, bottom: toolbarHeight }); me.$el.addClass('panel-expanded'); $('body').css('overflow', 'hidden'); me.$body.css({ width: me._calculateBodyWidth(me.$el.width()), height: me._calculateBodyHeight(me.$el.height()) }); me.disableDrag(); if (me.isPinned()) { me._disableSorting(); } me._saveState('fullscreen'); me._triggerEvent("onFullScreen"); }); return me; }, /** * Collapse the panel to small size * * @returns {LobiPanel} */ toSmallSize: function () { var me = this; me._triggerEvent("beforeSmallSize"); me._changeClassOfControl(me.$heading.find('[data-func="expand"]')); var css = me.$el.attr('old-style').getCss(); //we get css properties from old-style (saved before expanding) //and we animate panel to this css properties me.$el.animate({ position: 'absolute', left: css.left, top: css.top, width: css.width, height: css.height, right: css.right, bottom: css.bottom }, me.$options.collapseAnimation, function () { //we remove old-style as we do not need it me.$el.removeAttr('old-style'); //if panel is pinned we also remove its style attribute and we //append panel in its parent element if (!me.$el.hasClass('panel-unpin')) { me.$el.removeAttr('style'); me._insertInParent(); me._enableSorting(); } else { if (me.$options.draggable) { me.enableDrag(); } me.enableResize(); } me.$el.removeClass('panel-expanded'); $('body').css('overflow', 'auto'); var bWidth = ''; var bHeight = ''; if (!me.isPinned()) { bWidth = me._calculateBodyWidth(me.getWidth()); bHeight = me._calculateBodyHeight(me.getHeight()); } else if (me.$options.bodyHeight !== 'auto') { bHeight = me.$options.bodyHeight; } if (me.$options.bodyHeight !== 'auto'){ me._saveState('pinnned'); } else { me._updateUnpinnedState(); } me.$body.css({ width: bWidth, height: bHeight }); me._triggerEvent("onSmallSize"); }); return me; }, /** * Toggles (changes to full screen size or to small size) the panel size * * @returns {LobiPanel} */ toggleSize: function () { var me = this; if (me.isOnFullScreen()) { me.toSmallSize(); } else { me.toFullScreen(); } return me; }, /** * Closes the panel. Removes it from document * * @param {number} animationDuration * @returns {LobiPanel} */ close: function (animationDuration) { var me = this, animationDuration = animationDuration === undefined ? 100 : animationDuration; me._triggerEvent('beforeClose'); me.$el.hide(animationDuration, function () { if (me.isOnFullScreen()) { $('body').css('overflow', 'auto'); } me._triggerEvent('onClose'); me.$el.remove(); var footer = me._getFooterForMinimizedPanels(); if (footer.children().length === 0) { footer.remove(); } }); return me; }, /** * Moves unpinned panel to given position. * This method will do nothing if panel is pinned * * @param {number} left * @param {number} top * @param {number} animationDuration * @returns {LobiPanel} */ setPosition: function (left, top, animationDuration) { var me = this, animationDuration = animationDuration === undefined ? 100 : animationDuration; //this method works only if panel is not pinned if (me.isPinned()) { return me; } me.$el.animate({ 'left': left, 'top': top }, animationDuration); return me; }, /** * Set the width of the panel * * @param {number} w * @param {number} animationDuration * @returns {LobiPanel} */ setWidth: function (w, animationDuration) { var me = this, animationDuration = animationDuration === undefined ? 100 : animationDuration; if (me.isPinned()) { return me; } var bWidth = me._calculateBodyWidth(w); me.$el.animate({ width: w }, animationDuration); me.$body.animate({ width: bWidth }, animationDuration); return me; }, /** * Set the height of the panel * * @param {number} h * @param {number} animationDuration * @returns {LobiPanel} */ setHeight: function (h, animationDuration) { var me = this, animationDuration = animationDuration === undefined ? 100 : animationDuration; if (me.isPinned()) { return me; } var bHeight = me._calculateBodyHeight(h); me.$el.animate({ height: h }, animationDuration); me.$body.animate({ height: bHeight }, animationDuration); return me; }, /** * Set size (width and height) of the panel * * @param {number} w * @param {number} h * @param {number} animationDuration * @returns {LobiPanel} */ setSize: function (w, h, animationDuration) { var me = this, animationDuration = animationDuration === undefined ? 100 : animationDuration; if (me.isPinned()) { return me; } var bHeight = me._calculateBodyHeight(h); var bWidth = me._calculateBodyWidth(w); me.$el.animate({ height: h, width: w }, animationDuration); me.$body.animate({ height: bHeight, width: bWidth }, animationDuration); return me; }, /** * Get the position of the panel. * Returns object where x is left coordinate and y is top coordinate * * @returns {Object} */ getPosition: function () { var me = this; var offset = me.$el.offset(); return { x: offset.left, y: offset.top }; }, /** * Get width of the panel * * @returns {number} */ getWidth: function () { var me = this; return me.$el.width(); }, /** * Get height of the panel * * @returns {number} */ getHeight: function () { var me = this; return me.$el.height(); }, /** * If panel is overlapped by another panel this panel will be shown on front * (this panel will overlap other panels) * * @returns {LobiPanel} */ bringToFront: function () { var me = this; me._triggerEvent("beforeToFront"); var res = me._getMaxZIndex(); if (res['id'] === me.$el.data('inner-id')) { return me; } me.$el.css('z-index', res['z-index'] + 1); me._triggerEvent("onToFront"); return me; }, /** * Enable dragging of panel * * @returns {LobiPanel} */ enableDrag: function () { var me = this; me.$el.draggable({ handle: '.card-header', containment: me.$options.constrain, start: function () { me.$el.css('position', 'absolute'); }, stop: function () { me.$el.css('position', ''); me._updateUnpinnedState(); } }); return me; }, /** * Disable dragging of the panel * * @returns {LobiPanel} */ disableDrag: function () { var me = this; if (me.$el.hasClass('ui-draggable')) { me.$el.draggable("destroy"); } return me; }, /** * Enable resize of the panel * * @returns {LobiPanel} */ enableResize: function () { var me = this; var handles = false; if (me.$options.resize === 'vertical') { handles = 'n, s'; } else if (me.$options.resize === 'horizontal') { handles = 'e, w'; } else if (me.$options.resize === 'both') { handles = 'all'; } if (!handles) { return me; } me.$el.resizable({ minWidth: me.$options.minWidth, maxWidth: me.$options.maxWidth, minHeight: me.$options.minHeight, maxHeight: me.$options.maxHeight, handles: handles, start: function () { me.$el.disableSelection(); me._triggerEvent('resizeStart'); }, stop: function () { me.$el.enableSelection(); me._triggerEvent('resizeStop'); }, resize: function () { var bHeight = me._calculateBodyHeight(me.$el.height()); var bWidth = me._calculateBodyWidth(me.$el.width()); me.$body.css({ width: bWidth, height: bHeight }); me._updateUnpinnedState(); me._triggerEvent("onResize"); } }); return me; }, /** * Disable resize of the panel * * @returns {LobiPanel} */ disableResize: function () { var me = this; if (me.$el.hasClass('ui-resizable')) { me.$el.resizable("destroy"); } return me; }, /** * Start spinner of the panel loading * * @returns {LobiPanel} */ startLoading: function () { var me = this; var spinner = me._generateWindow8Spinner(); me.$el.append(spinner); var sp = spinner.find('.spinner'); sp.css('margin-top', 50); return me; }, /** * Stop spinner of the panel loading * * @returns {LobiPanel} */ stopLoading: function () { var me = this; me.$el.find('.spinner-wrapper').remove(); return me; }, /** * Set url. This url will be used to load data when Reload button is clicked * or user calls .load() method without url parameter * * @param {string} url * @returns {LobiPanel} */ setLoadUrl: function (url) { var me = this; me.$options.loadUrl = url; return me; }, /** * Load data into .card-block. * params object is in format * { * url: '', //Optional: load url * data: 'PlainObject or String', //Optional: A plain object or string of parameters which is sent to the server with the request. * callback: 'function' //Optional: callback function which is called when load is finished * } * * @param {Object} params * @returns {LobiPanel} */ load: function (params) { var me = this; params = params || {}; if (typeof params === 'string') { params = {url: params}; } var url = params.url || me.$options.loadUrl, data = params.data || {}, callback = params.callback || null; if (!url) { return me; } me._triggerEvent("beforeLoad"); me.startLoading(); me.$body.load(url, data, function (result, status, xhr) { if (callback && typeof callback === 'function') { me.callback(result, status, xhr); } me.stopLoading(); me._triggerEvent("loaded", result, status, xhr); }); return me; }, /** * Destroy the LobiPanel instance * * @returns {jQuery} */ destroy: function () { var me = this; me.disableDrag(); me.disableResize(); me.$options.sortable = false; me._enableSorting(); me._removeInnerIdFromParent(me.innerId); me.$el.removeClass('lobipanel') .removeAttr('data-inner-id') .removeAttr('data-index') .removeData('lobiPanel'); me.$heading.find('.dropdown').remove(); return me.$el; }, /** * Creates input field to edit panel title * * @returns {LobiPanel} */ startTitleEditing: function () { var me = this; var title = me.$heading.find('.card-title').text().trim(); var input = $(''); input.on('keydown', function (ev) { if (ev.which === 13) { me.finishTitleEditing(); } else if (ev.which === 27) { me.cancelTitleEditing(); } }); me.$heading.find('.card-title') .data('old-title', title) .html("").append(input); input[0].focus(); input[0].select(); me._changeClassOfControl(me.$heading.find('[data-func="editTitle"]')); return me; }, /** * Check if panel title is being edited (if it is in edit process) * * @returns {Boolean} */ isTitleEditing: function () { var me = this; return me.$heading.find('.card-title input').length > 0; }, /** * Cancel the panel new title and return to previous title when it is changed but not saved * * @returns {LobiPanel} */ cancelTitleEditing: function () { var me = this; var title = me.$heading.find('.card-title'); title.html(title.data('old-title')) .find('input').remove(); me._changeClassOfControl(me.$heading.find('[data-func="editTitle"]')); return me; }, /** * Finish the panel title editing process and save new title * * @returns {LobiPanel} */ finishTitleEditing: function () { var me = this, input = me.$heading.find('input'); if (me._triggerEvent('beforeTitleChange', input.val()) === false){ return me; } me.$heading.find('.card-title').html(input.val()); input.remove(); me._changeClassOfControl(me.$heading.find('[data-func="editTitle"]')); me._triggerEvent('onTitleChange', input.val()); return me; }, /** * Enable tooltips on panel controls * * @returns {LobiPanel} */ enableTooltips: function () { var me = this; if ($(window).width() < 768) { return me; } var controls = me.$heading.find('.dropdown-item>a'); controls.each(function (index, el) { var $el = $(el); $el.attr('data-toggle', 'tooltip') .attr('data-title', $el.data('tooltip')) .attr('data-placement', 'bottom') ; }); controls.each(function (ind, el) { $(el).tooltip({ container: 'body', template: '' }); }); return me; }, /** * Disable tooltips on panel controls * * @returns {LobiPanel} */ disableTooltips: function () { var me = this; var $links = me.$heading.find('.dropdown-item>a'); $links.each(function(ind, el){ var bsTooltip = $(el).data('bs.tooltip'); if (bsTooltip){ $(el).tooltip('destroy'); } }); // me.$heading.find('.dropdown-menu>li>a').tooltip('destroy'); return me; }, _generateControls: function () { var me = this; var dropdown = me._generateDropdown(); var menu = dropdown.find('.dropdown-menu'); if (me.$options.editTitle !== false) { menu.append(me._generateEditTitle()); } if (me.$options.unpin !== false) { menu.append(me._generateUnpin()); } if (me.$options.reload !== false) { menu.append(me._generateReload()); } if (me.$options.minimize !== false) { menu.append(me._generateMinimize()); } if (me.$options.expand !== false) { menu.append(me._generateExpand()); } if (me.$options.close !== false) { menu.append(me._generateClose()); } menu.find('>li>a').on('click', function (ev) { ev.preventDefault(); ev.stopPropagation(); }); return dropdown; }, _generateDropdown: function () { var me = this; return $('') .append('') .append(''); }, _generateEditTitle: function () { var me = this; var options = me.$options.editTitle; var control = $(''); control.append(''); if (options.tooltip && typeof options.tooltip === 'string') { control.append('' + options.tooltip + ''); control.attr('data-tooltip', options.tooltip); } me._attachEditTitleClickListener(control); return control; }, _attachEditTitleClickListener: function (control) { var me = this; control.on('mousedown', function (ev) { ev.stopPropagation(); }); control.on('click', function (ev) { ev.stopPropagation(); me.hideTooltip(control); if (me.isTitleEditing()) { me.finishTitleEditing(); } else { me.startTitleEditing(); } }); }, hideTooltip: function($el){ var bsTooltip = $el.data('bs.tooltip'); if (bsTooltip){ $el.tooltip('hide'); } return this; }, _generateUnpin: function () { var me = this; var options = me.$options.unpin; var control = $(''); control.append(''); if (options.tooltip && typeof options.tooltip === 'string') { control.append('' + options.tooltip + ''); control.attr('data-tooltip', options.tooltip); } me._attachUnpinClickListener(control); return control; }, _attachUnpinClickListener: function (control) { var me = this; //hide the tooltip control.on('mousedown', function (ev) { ev.stopPropagation(); }); control.on('click', function () { me.hideTooltip(control); me.doTogglePin(); }); }, _generateReload: function () { var me = this; var options = me.$options.reload; var control = $(''); control.append(''); if (options.tooltip && typeof options.tooltip === 'string') { control.append('' + options.tooltip + ''); control.attr('data-tooltip', options.tooltip); } me._attachReloadClickListener(control); return control; }, _attachReloadClickListener: function (control) { var me = this; control.on('mousedown', function (ev) { ev.stopPropagation(); }); control.on('click', function () { me.hideTooltip(control); me.load(); }); }, _generateMinimize: function () { var me = this; var options = me.$options.minimize; var control = $(''); control.append(''); if (options.tooltip && typeof options.tooltip === 'string') { control.append('' + options.tooltip + ''); control.attr('data-tooltip', options.tooltip); } me._attachMinimizeClickListener(control); return control; }, _attachMinimizeClickListener: function (control) { var me = this; control.on('mousedown', function (ev) { ev.stopPropagation(); }); control.on('click', function (ev) { ev.stopPropagation(); me.hideTooltip(control); me.toggleMinimize(); }); }, _generateExpand: function () { var me = this; var options = me.$options.expand; var control = $(''); control.append(''); if (options.tooltip && typeof options.tooltip === 'string') { control.append('' + options.tooltip + ''); control.attr('data-tooltip', options.tooltip); } me._attachExpandClickListener(control); return control; }, _attachExpandClickListener: function (control) { var me = this; control.on('mousedown', function (ev) { ev.stopPropagation(); }); control.on('click', function (ev) { ev.stopPropagation(); me.hideTooltip(control); me.toggleSize(); }); }, _generateClose: function () { var me = this; var options = me.$options.close; var control = $(''); control.append(''); if (options.tooltip && typeof options.tooltip === 'string') { control.append('' + options.tooltip + ''); control.attr('data-tooltip', options.tooltip); } me._attachCloseClickListener(control); return control; }, _attachCloseClickListener: function (control) { var me = this; control.on('mousedown', function (ev) { ev.stopPropagation(); }); control.on('click', function (ev) { ev.stopPropagation(); me.hideTooltip(control); me.close(); }); }, _getMaxZIndex: function () { var me = this; var panels = $('.lobipanel.panel-unpin:not(.panel-minimized.panel-expanded)'), style, max, cur; if (panels.length === 0) { return { "id": "", "z-index": LobiPanel.PRIVATE_OPTIONS.initialZIndex }; } style = $(panels[0]).attr('style'); var id = $(panels[0]).data('inner-id'); if (!style) { max = LobiPanel.PRIVATE_OPTIONS.initialZIndex; } else { max = style.getCss()['z-index']; } for (var i = 1; i < panels.length; i++) { style = $(panels[i]).attr('style'); if (!style) { cur = 0; } else { cur = style.getCss()['z-index']; } if (cur > max) { id = $(panels[i]).data('inner-id'); max = cur; } } return { "id": id, "z-index": parseInt(max, 10) }; }, _onPanelClick: function () { var me = this; me.$el.on('mousedown.lobiPanel', function () { if (me.isPinned() || me.isMinimized() || me.isOnFullScreen()) { return false; } me.bringToFront(); }); }, _offPanelClick: function () { var me = this; me.$el.off('mousedown.lobiPanel'); }, _changeClassOfControl: function (el) { var me = this; el = $(el); var opts = me.$options[el.attr('data-func')]; if (!opts.icon) { return; } el.find('.' + LobiPanel.PRIVATE_OPTIONS.iconClass).toggleClass(opts.icon).toggleClass(opts.icon2); }, _getFooterForMinimizedPanels: function () { var me = this; //we grab footer where minimized panels should go var minimizedCtr = $('.' + LobiPanel.PRIVATE_OPTIONS.toolbarClass); //if panel does not exist we create it and append to body if (minimizedCtr.length === 0) { minimizedCtr = $('
'); $('body').append(minimizedCtr); } return minimizedCtr; }, _expandOnHeaderClick: function () { var me = this; me.$heading.on('click.lobiPanel', function () { me.maximize(); me.bringToFront(); }); }, _removeExpandOnHeaderClick: function () { var me = this; me.$heading.off('click.lobiPanel'); }, _getAvailableWidth: function (calcWidth) { var me = this; if (me.$options.maxWidth) { calcWidth = Math.min(calcWidth, me.$options.maxWidth); } if (me.$options.minWidth) { calcWidth = Math.max(calcWidth, me.$options.minWidth); } return calcWidth; }, _getAvailableHeight: function (calcHeight) { var me = this; if (me.$options.maxHeight) { calcHeight = Math.min(calcHeight, me.$options.maxHeight); } if (me.$options.minHeight) { calcHeight = Math.max(calcHeight, me.$options.minHeight); } return calcHeight; }, _calculateBodyHeight: function (h) { var me = this; return h - me.$heading.outerHeight() - me.$el.find('.card-footer').outerHeight(); }, _calculateBodyWidth: function (w) { var me = this; return w - 2; }, _appendInnerIdToParent: function (parent, innerId) { var me = this; //If this is first lobipanel element of its parent if (parent.attr(LobiPanel.PRIVATE_OPTIONS.parentAttr) === undefined) { parent.attr(LobiPanel.PRIVATE_OPTIONS.parentAttr, innerId); } //This means that parent already has LobiPanel instance else { //if parent already has panel innerId than we do nothing if (parent.attr(LobiPanel.PRIVATE_OPTIONS.parentAttr).indexOf(innerId) > -1) { return; } var innerIds = parent.attr(LobiPanel.PRIVATE_OPTIONS.parentAttr); parent.attr(LobiPanel.PRIVATE_OPTIONS.parentAttr, innerIds + " " + innerId); } me.$el.attr('data-index', me.$el.index()); }, _insertInParent: function () { var me = this; //find its parent element var parent = $('[' + LobiPanel.PRIVATE_OPTIONS.parentAttr + '~=' + me.innerId + ']'); me.$el.insertAt(me.$el.attr('data-index'), parent); }, _generateWindow8Spinner: function () { var me = this; var template = ['
', '
', '
', '
', '
', '
', '
', '
', '
', '
', '
', '
', '
', '
', '
', '
', '
', '
', '
', '
', '
', '
'].join(""); return $('
' + template + '
'); }, _enableSorting: function () { var me = this; var parent = me.$el.parent(); if (parent.hasClass('ui-sortable')) { parent.sortable("destroy"); } if (me.$options.sortable) { me.$el.addClass('lobipanel-sortable'); parent.addClass('lobipanel-parent-sortable'); } else { me.$el.removeClass('lobipanel-sortable'); } parent.sortable({ connectWith: '.lobipanel-parent-sortable', items: '.lobipanel-sortable', handle: '.card-header', cursor: 'move', placeholder: 'lobipanel-placeholder', forcePlaceholderSize: true, opacity: 0.7, revert: 300, update: function (event, ui) { var innerId = ui.item.data('inner-id'); me._removeInnerIdFromParent(innerId); me._appendInnerIdToParent(ui.item.parent(), innerId); me._updateDataIndices(ui.item); me._triggerEvent('dragged'); } }); }, _disableSorting: function () { var me = this; var parent = me.$el.parent(); if (parent.hasClass('ui-sortable')) { parent.sortable("destroy"); } }, _updateDataIndices: function (panel) { var me = this; var items = panel.parent().children(); items.each(function (index, el) { $(el).attr('data-index', index); var lobiPanel = $(el).data('lobiPanel'); if (lobiPanel && lobiPanel.$options.stateful && !lobiPanel.hasRandomId){ lobiPanel._saveState('pinned', {index: index}); } }); // me._saveState('pinned', {index: panel.index()}) console.log("Save indices in localstorage"); }, _removeInnerIdFromParent: function (innerId) { var me = this; var parent = $('[' + LobiPanel.PRIVATE_OPTIONS.parentAttr + '~=' + innerId + ']'); var innerIds = parent.attr(LobiPanel.PRIVATE_OPTIONS.parentAttr).replace(innerId, '').trim().replace(/\s{2,}/g, ' '); parent.attr(LobiPanel.PRIVATE_OPTIONS.parentAttr, innerIds); }, _onToggleIconsBtnClick: function () { var me = this; me.$heading.find('.toggle-controls').on('click.lobiPanel', function () { me.$el.toggleClass("controls-expanded"); }); }, _adjustForScreenSize: function () { var me = this; me.disableTooltips(); if ($(window).width() > 768 && me.$options.tooltips) { me.enableTooltips(); } if (me.isOnFullScreen()) { me.$body.css({ width: me._calculateBodyWidth(me.$el.width()), height: me._calculateBodyHeight(me.$el.height()) }); } }, _enableResponsiveness: function () { var me = this; me._adjustForScreenSize(); $(window).on('resize.lobiPanel', function () { me._adjustForScreenSize(); }); }, _setBodyHeight: function () { var me = this; if (me.$options.bodyHeight !== 'auto') { me.$body.css({ 'height': me.$options.bodyHeight, overflow: 'auto' }); } }, _getOptionsFromAttributes: function () { var me = this; var $el = me.$el; var options = {}; for (var key in $.fn.lobiPanel.DEFAULTS) { var k = key.toDash(); var val = $el.data(k); if (val !== undefined) { if (typeof $.fn.lobiPanel.DEFAULTS[key] !== 'object') { options[key] = val; } else { options[key] = eval('(' + val + ')'); } } } return options; }, _saveState: function (state, params) { var me = this; console.log("Save state ", state, params); if (!me.hasRandomId && me.$options.stateful) { me.storage.state = state; if (params) { me.storage.stateParams = params; } me._saveLocalStorage(me.storage); } }, _saveLocalStorage: function (storage) { var me = this; localStorage.setItem(STORAGE_PREFIX + me.innerId, JSON.stringify(storage)); }, _applyState: function (state, params) { var me = this; switch (state) { case 'pinned': if (params && params.index !== null && params.index !== undefined) { me._applyIndex(params.index); } break; case 'unpinned': me.unpin(); me.setPosition(params.left, params.top, 0); me.setSize(params.width, params.height, 0); break; case 'minimized': me.unpin(); me.minimize(); break; case 'collapsed': me.minimize(); break; case 'fullscreen': me.toFullScreen(); break; default: break; } }, _applyIndex: function (index) { var me = this; if (index !== null) { me.$el.insertAt(index, me.$el.parent()); } }, _triggerEvent: function (eventType) { var me = this; var args = Array.prototype.slice.call(arguments, 1); args.unshift(me); me.$el.trigger(eventType + '.lobiPanel', args); if (me.$options[eventType] && typeof me.$options[eventType] === 'function'){ return me.$options[eventType].apply(me, args); } return true; }, doPin: function(){ var me = this; if (me._triggerEvent("beforePin") !== false){ me.pin(); me._saveState('pinned'); me._triggerEvent("onPin"); } return me; }, doUnpin: function(){ var me = this; if (me._triggerEvent('beforeUnpin') !== false){ me.unpin(); me._updateUnpinnedState(); me._triggerEvent('onUnpin'); } return me; }, doTogglePin: function(){ var me = this; if (this.isPinned()) { this.doUnpin(); } else { this.doPin(); } return me; }, _updateUnpinnedState: function(){ var me = this; me._saveState('unpinned', me.getAlignment()); }, getAlignment: function(){ var me = this; return { top: me.$el.css('top'), left: me.$el.css('left'), width: me.$el.css('width'), height: me.$el.css('height') }; } }; $.fn.lobiPanel = function (option) { var args = arguments, ret = null; this.each(function () { var $this = $(this); var data = $this.data('lobiPanel'); var options = typeof option === 'object' && option; if (!data) { $this.data('lobiPanel', (data = new LobiPanel($this, options))); } if (typeof option === 'string') { args = Array.prototype.slice.call(args, 1); ret = data[option].apply(data, args); } }); return ret; }; LobiPanel.PRIVATE_OPTIONS = { //We need to know what is the parent of the panel, that's why we add //this attribute to parent element and it contains space seperated inner-ids of all its child lobipanel parentAttr: 'data-lobipanel-child-inner-id', toolbarClass: 'lobipanel-minimized-toolbar', //This class is added to container which contains all minimized panels //First instance on lobiPanel will get this z-index css property. //Every next instance will get 1 + previous z-index initialZIndex: 10000, //This class is attached to every panel-control icon iconClass: 'panel-control-icon' }; $.fn.lobiPanel.DEFAULTS = { //Makes unpinned panel draggable //Warning!!! This requires jquery ui draggable widget to be included draggable: true, //Makes pinned panels sortable //Warning!!! This requires jquery ui sortable widget to be included sortable: false, //jquery ui sortable plugin option. //To avoid any problems this option must be same for all panels which are direct children of their parent connectWith: '.ui-sortable', //This parameter accepts string ['both', 'vertical', 'horizontal', 'none']. none means disable resize resize: 'both', //Minimum width unpin, resizable panel can have. minWidth: 200, //Minimum height unpin, resizable panel can have. minHeight: 100, //Maximum width unpin, resizable panel can have. maxWidth: 1200, //Maximum height unpin, resizable panel can have. maxHeight: 700, //The url which will be used to load content. If not provided reload button will do nothing loadUrl: "", //If loadUrl is provided plugin will load content as soon as plugin is initialized autoload: true, bodyHeight: 'auto', //This will enable tooltips on panel controls tooltips: true, toggleIcon: 'icon-settings', expandAnimation: 100, collapseAnimation: 100, state: 'pinned', // Initial state of the panel. Available options: pinned, unpinned, collapsed, minimized, fullscreen initialIndex: null, // Initial index of the panel among its siblings stateful: false, // If you set this to true you must specify data-inner-id. Plugin will save (in localStorage) it's states such as // pinned, unpinned, collapsed, minimized, fullscreen, position among it's siblings // and apply them when you reload the browser constrain: 'document', // 'parent', 'document', 'window' unpin: { icon: 'icon-pin', //You can user glyphicons if you do not want to use font-awesome tooltip: 'Unpin' //tooltip text, If you want to disable tooltip, set it to false }, reload: { icon: 'icon-refresh-cw', //You can user glyphicons if you do not want to use font-awesome tooltip: 'Reload' //tooltip text, If you want to disable tooltip, set it to false }, minimize: { icon: 'icon-minus-square', //icon is shown when panel is not minimized icon2: 'icon-plus-square', //icon2 is shown when panel is minimized tooltip: 'Minimize' //tooltip text, If you want to disable tooltip, set it to false }, expand: { icon: 'icon-resize-full-screen', //icon is shown when panel is not on full screen icon2: 'icon-resize-100', //icon2 is shown when pane is on full screen state tooltip: 'Fullscreen' //tooltip text, If you want to disable tooltip, set it to false }, close: { icon: 'icon-highlight_off', //You can user glyphicons if you do not want to use font-awesome tooltip: 'Close' //tooltip text, If you want to disable tooltip, set it to false }, editTitle: { icon: 'icon-edit-3', icon2: 'icon-save2', tooltip: 'Edit title' }, // Events /** * @event beforeTitleChange * Fires before title change happens. Returning false will prevent title change from happening. * @param {LobiPanel} The LobiPanel instance */ beforeTitleChange: null }; $('.lobipanel').lobiPanel(); });