//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: '
LobiPanel
instance
*/
beforeTitleChange: null
};
$('.lobipanel').lobiPanel();
});