/*! * Minified Utility Resources * Theme Core resources. */ ; (function ($) { var defaults = { width: 400, height: "65%", minimizedWidth: 200, gutter: 10, poppedOutDistance: "6%", title: function() { return ""; }, dialogClass: "", buttons: [], /* id, html, buttonClass, click */ animationSpeed: 400, opacity: 1, initialState: 'modal', /* "modal", "docked", "minimized" */ showClose: true, showPopout: true, showMinimize: true, create: undefined, open: undefined, beforeClose: undefined, close: undefined, beforeMinimize: undefined, minimize: undefined, beforeRestore: undefined, restore: undefined, beforePopout: undefined, popout: undefined }; var dClass = "dockmodal"; var windowWidth = $(window).width(); function setAnimationCSS($this, $el) { var aniSpeed = $this.options.animationSpeed / 1000; $el.css({"transition": aniSpeed + "s right, " + aniSpeed + "s left, " + aniSpeed + "s top, " + aniSpeed + "s bottom, " + aniSpeed + "s height, " + aniSpeed + "s width"}); return true; } function removeAnimationCSS($el) { $el.css({"transition": "none"}); return true; } var methods = { init: function (options) { return this.each(function () { var $this = $(this); var data = $this.data('dockmodal'); $this.options = $.extend({}, defaults, options); // Check to see if title is a returned function (function titleCheck() { if (typeof $this.options.title == "function") { $this.options.title = $this.options.title.call($this); } })(); // If the plugin hasn't been initialized yet if (!data) { $this.data('dockmodal', $this); } else { $("body").append($this.closest("." + dClass).show()); //methods.restore.apply($this); methods.refreshLayout(); setTimeout(function () { methods.restore.apply($this); }, $this.options.animationSpeed); return; } // create modal var $body = $("body"); var $window = $(window); var $dockModal = $('
').addClass(dClass).addClass($this.options.dialogClass); if ($this.options.initialState == "modal") { $dockModal.addClass("popped-out"); } else if ($this.options.initialState == "minimized") { $dockModal.addClass("minimized"); } //$dockModal.width($this.options.width); $dockModal.height(0); setAnimationCSS($this, $dockModal); // create title var $dockHeader = $('
').addClass(dClass + "-header"); if ($this.options.showClose) { $('').appendTo($dockHeader).click(function (e) { methods.destroy.apply($this); return false; }); } if ($this.options.showPopout) { $('').appendTo($dockHeader).click(function (e) { if ($dockModal.hasClass("popped-out")) { methods.restore.apply($this); } else { methods.popout.apply($this); } return false; }); } if ($this.options.showMinimize) { $('').appendTo($dockHeader).click(function (e) { if ($dockModal.hasClass("minimized")) { if ($dockModal.hasClass("popped-out")) { methods.popout.apply($this); } else { methods.restore.apply($this); } } else { methods.minimize.apply($this); } return false; }); } if ($this.options.showMinimize && $this.options.showPopout) { $dockHeader.click(function () { if ($dockModal.hasClass("minimized")) { if ($dockModal.hasClass("popped-out")) { methods.popout.apply($this); } else { methods.restore.apply($this); } } else { methods.minimize.apply($this); } return false; }); } $dockHeader.append('
' + ($this.options.title || $this.attr("title")) + '
'); $dockModal.append($dockHeader); // create body section var $placeholder = $('').insertAfter($this); $this.placeholder = $placeholder; var $dockBody = $('
').addClass(dClass + "-body").append($this); $dockModal.append($dockBody); // create footer if ($this.options.buttons.length) { var $dockFooter = $('
').addClass(dClass + "-footer"); var $dockFooterButtonset = $('
').addClass(dClass + "-footer-buttonset"); $dockFooter.append($dockFooterButtonset); $.each($this.options.buttons, function (indx, el) { var $btn = $(''); $btn.attr({ "id": el.id, "class": el.buttonClass }); $btn.html(el.html); $btn.click(function (e) { el.click(e, $this); return false; }); $dockFooterButtonset.append($btn); }); $dockModal.append($dockFooter); } else { $dockModal.addClass("no-footer"); } // create overlay var $overlay = $("." + dClass + "-overlay"); if (!$overlay.length) { $overlay = $('
').addClass(dClass + "-overlay"); } // raise create event if ($.isFunction($this.options.create)) { $this.options.create($this); } $body.append($dockModal); $dockModal.after($overlay); $dockBody.focus(); // raise open event if ($.isFunction($this.options.open)) { setTimeout(function () { $this.options.open($this); }, $this.options.animationSpeed); } //methods.restore.apply($this); if ($dockModal.hasClass("minimized")) { $dockModal.find(".dockmodal-body, .dockmodal-footer").hide(); methods.minimize.apply($this); } else { if ($dockModal.hasClass("popped-out")) { methods.popout.apply($this); } else { methods.restore.apply($this); } } // attach resize event // track width, set to window width $body.data("windowWidth", $window.width()); $window.unbind("resize.dockmodal").bind("resize.dockmodal", function () { // do nothing if the width is the same // update new width value if ($window.width() == $body.data("windowWidth")) { return; } $body.data("windowWidth", $window.width()); methods.refreshLayout(); }); }); }, destroy: function () { return this.each(function () { var $this = $(this).data('dockmodal'); if (!$this) return; // raise beforeClose event if ($.isFunction($this.options.beforeClose)) { if ($this.options.beforeClose($this) === false) { return; } } try { var $dockModal = $this.closest("." + dClass); if ($dockModal.hasClass("popped-out") && !$dockModal.hasClass("minimized")) { $dockModal.css({ "left": "50%", "right": "50%", "top": "50%", "bottom": "50%" }); } else { $dockModal.css({ "width": "0", "height": "0" }); } setTimeout(function () { $this.removeData('dockmodal'); $this.placeholder.replaceWith($this); $dockModal.remove(); $("." + dClass + "-overlay").hide(); methods.refreshLayout(); // raise close event if ($.isFunction($this.options.close)) { $this.options.close($this); } }, $this.options.animationSpeed); } catch (err) { alert(err.message); } // other destroy routines }) }, close: function () { methods.destroy.apply(this); }, minimize: function () { return this.each(function () { var $this = $(this).data('dockmodal'); if (!$this) return; // raise beforeMinimize event if ($.isFunction($this.options.beforeMinimize)) { if ($this.options.beforeMinimize($this) === false) { return; } } var $dockModal = $this.closest("." + dClass); var headerHeight = $dockModal.find(".dockmodal-header").outerHeight(); $dockModal.addClass("minimized").css({ "width": $this.options.minimizedWidth + "px", "height": headerHeight + "px", "left": "auto", "right": "auto", "top": "auto", "bottom": "0" }); setTimeout(function () { // for safty, hide the body and footer $dockModal.find(".dockmodal-body, .dockmodal-footer").hide(); // raise minimize event if ($.isFunction($this.options.minimize)) { $this.options.minimize($this); } }, $this.options.animationSpeed); $("." + dClass + "-overlay").hide(); $dockModal.find(".action-minimize").attr("title", "Restore"); methods.refreshLayout(); }) }, restore: function () { return this.each(function () { var $this = $(this).data('dockmodal'); if (!$this) return; // raise beforeRestore event if ($.isFunction($this.options.beforeRestore)) { if ($this.options.beforeRestore($this) === false) { return; } } var $dockModal = $this.closest("." + dClass); $dockModal.removeClass("minimized popped-out"); $dockModal.find(".dockmodal-body, .dockmodal-footer").show(); $dockModal.css({ "width": $this.options.width + "px", "height": $this.options.height, "left": "auto", "right": "auto", "top": "auto", "bottom": "0" }); $("." + dClass + "-overlay").hide(); $dockModal.find(".action-minimize").attr("title", "Minimize"); $dockModal.find(".action-popout").attr("title", "Pop-out"); setTimeout(function () { // raise restore event if ($.isFunction($this.options.restore)) { $this.options.restore($this); } }, $this.options.animationSpeed); methods.refreshLayout(); }) }, popout: function () { return this.each(function () { var $this = $(this).data('dockmodal'); if (!$this) return; // raise beforePopout event if ($.isFunction($this.options.beforePopout)) { if ($this.options.beforePopout($this) === false) { return; } } var $dockModal = $this.closest("." + dClass); $dockModal.find(".dockmodal-body, .dockmodal-footer").show(); // prepare element for animation removeAnimationCSS($dockModal); var offset = $dockModal.position(); var windowWidth = $(window).width(); $dockModal.css({ "width": "auto", "height": "auto", "left": offset.left + "px", "right": (windowWidth - offset.left - $dockModal.outerWidth(true)) + "px", "top": offset.top + "px", "bottom": 0 }); setAnimationCSS($this, $dockModal); setTimeout(function () { $dockModal.removeClass("minimized").addClass("popped-out").css({ "width": "auto", "height": "auto", "left": $this.options.poppedOutDistance, "right": $this.options.poppedOutDistance, "top": $this.options.poppedOutDistance, "bottom": $this.options.poppedOutDistance }); $("." + dClass + "-overlay").show(); $dockModal.find(".action-popout").attr("title", "Pop-in"); methods.refreshLayout(); }, 10); setTimeout(function () { // raise popout event if ($.isFunction($this.options.popout)) { $this.options.popout($this); } }, $this.options.animationSpeed); }); }, refreshLayout: function () { var right = 0; var windowWidth = $(window).width(); $.each($("." + dClass).toArray().reverse(), function (i, val) { var $dockModal = $(this); var $this = $dockModal.find("." + dClass + "-body > div").data("dockmodal"); if ($dockModal.hasClass("popped-out") && !$dockModal.hasClass("minimized")) { return; } right += $this.options.gutter; $dockModal.css({ "right": right + "px" }); if ($dockModal.hasClass("minimized")) { right += $this.options.minimizedWidth; } else { right += $this.options.width; } if (right > windowWidth) { $dockModal.hide(); } else { setTimeout(function () { $dockModal.show(); }, $this.options.animationSpeed); } }); } }; $.fn.dockmodal = function (method) { if (methods[method]) { return methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); } else if (typeof method === 'object' || !method) { return methods.init.apply(this, arguments); } else { $.error('Method ' + method + ' does not exist on jQuery.dockmodal'); } }; })(jQuery); ; (function($, window, document, undefined) { // Plugin definition. $.fn.adminpanel = function(options) { // Default plugin options. var defaults = { grid: '.admin-grid', draggable: false, mobile: false, preserveGrid: false, onPanel: function() { console.log('callback:', 'onPanel'); }, onStart: function() { console.log('callback:', 'onStart'); }, onSave: function() { console.log('callback:', 'onSave'); }, onDrop: function() { // An "onSave" callback will also be called if // the drop also changes the elements DOM position console.log('callback:', 'onDrop'); }, onFinish: function() { console.log('callback:', 'onFinish'); }, }; // Extend default options. var options = $.extend({}, defaults, options); // Variables. var Body = $('body'); var plugin = $(this); var pluginID = plugin.attr('id'); var pluginGrid = options.grid; var dragSetting = options.draggable; var mobileSetting = options.mobile; var preserveSetting = options.preserveGrid; var panels = plugin.find('.panel'); // HTML5 LocalStorage Keys var settingsKey = 'panel-settings_' + location.pathname; var positionsKey = 'panel-positions_' + location.pathname; // HTML5 LocalStorage Gets var settingsGet = localStorage.getItem(settingsKey); var positionsGet = localStorage.getItem(positionsKey); // Control Menu Click Handler $('.panel').on('click', '.panel-controls > a', function(e) { e.preventDefault(); // if a panel is being dragged disable menu clicks if ($('body.ui-drag-active').length) { return; } // determine appropriate event response methods.controlHandlers.call(this, options); }); var methods = { init: function(options) { var This = $(this); // onStart callback if (typeof options.onStart == 'function') { options.onStart(); } // Check onload to see if positions key is empty if (!positionsGet) { localStorage.setItem(positionsKey, methods.findPositions()); } else { methods.setPositions(); } // Check onload to see if settings key is empty if (!settingsGet) { localStorage.setItem(settingsKey, methods.modifySettings()); } // Helper function that adds unique ID's to grid elements $(pluginGrid).each(function(i, e) { $(e).attr('id', 'grid-' + i); }); // Check if empty columns should be preserved using an invisible panel if (preserveSetting) { var Panel = "
"; $(pluginGrid).each(function(i, e) { $(e).append(Panel); }); } // Prep admin panel/container prior to menu creation methods.createControls(options); // Loop through settings key and apply options to panels methods.applySettings(); // Create Mobile Menus methods.createMobileControls(options); if (dragSetting === true) { // Activate jQuery sortable on declared grids/panels plugin.sortable({ items: plugin.find('.panel:not(".sort-disable")'), connectWith: pluginGrid, containment: '#content', // should be set to parent container revert: 250, handle: '.panel-heading', opacity: 1, delay: 100, tolerance: "pointer", scroll: true, placeholder: 'panel-placeholder', forcePlaceholderSize: true, forceHelperSize: true, create: function() { // kind of hacky but used to prevent bottom // panel items from causing a page jump on drag Body.css('overflow-x', 'initial'); $('.panel-heading').mousedown(function() { Body.css('overflow-x', 'initial'); }); setTimeout(function() { var savedHeight = plugin.height(); plugin.css('height', savedHeight); },250); }, start: function(e, ui) { Body.addClass('ui-drag-active').css('overflow-x', 'hidden'); ui.placeholder.height(ui.helper.outerHeight() + 3); }, beforeStop: function() { // onMove callback if (typeof options.onDrop == 'function') { options.onDrop(); } }, stop: function() { Body.removeClass('ui-drag-active'); // kind of hacky but used to prevent bottom // panel items from causing a page jump on drag var newHeight = plugin.css('height', 'auto').height(); plugin.height(newHeight); }, update: function(event, ui) { // toggle loading indicator methods.toggleLoader(); // store the positions of the plugins */ methods.updatePositions(options); } }); } // onFinish callback if (typeof options.onFinish == 'function') { options.onFinish(); } }, createMobileControls: function(options) { var controls = panels.find('.panel-controls'); var arr = {}; $.each(controls, function(i, e) { var This = $(e); var ID = $(e).parents('.panel').attr('id'); var controlW = This.width(); var titleW = This.siblings('.panel-title').width(); var headingW = This.parent('.panel-heading').width(); var mobile = (controlW + titleW); arr[ID] = mobile; }); console.log(arr) $.each(arr, function(i, e) { var This = $('#' + i); var headingW = This.width() - 75; var controls = This.find('.panel-controls'); if (mobileSetting === true || headingW < e) { This.addClass('mobile-controls'); var options = { html: true, placement: "left", content: function(e) { var Content = $(this).clone(); return Content; }, template: '' } controls.popover(options); } else { controls.removeClass('mobile-controls'); } }); // Toggle mobile controls menu open on click $('.mobile-controls .panel-heading > .panel-controls').on('click', function() { $(this).toggleClass('panel-controls-open'); }); }, applySettings: function(options) { // Variables. var obj = this; var localSettings = localStorage.getItem(settingsKey); var parseSettings = JSON.parse(localSettings); // Possible panel colors var panelColors = "panel-primary panel-success panel-info panel-warning panel-danger panel-alert panel-system panel-dark panel-default"; // Pull localstorage obj, parse the data, and then loop // through each panel and apply its given settings $.each(parseSettings, function(i, e) { $.each(e, function(i, e) { var panelID = e['id']; var panelTitle = e['title']; var panelCollapsed = e['collapsed']; var panelHidden = e['hidden']; var panelColor = e['color']; var Target = $('#' + panelID); if (panelTitle) { Target.children('.panel-heading').find('.panel-title').text(panelTitle); } if (panelCollapsed === 1) { Target.addClass('panel-collapsed') .children('.panel-body, .panel-menu, .panel-footer').hide(); } if (panelColor) { Target.removeClass(panelColors).addClass(panelColor).attr('data-panel-color', panelColor); } if (panelHidden === 1) { Target.addClass('panel-hidden').hide().remove(); } }); }); }, createControls: function(options) { // List of available panel controls var panelControls = ''; var panelTitle = ''; var panelColor = ''; var panelCollapse = ''; var panelFullscreen = ''; var panelRemove = ''; var panelCallback = ''; var panelDock = ''; var panelExpose = ''; var panelLoader = ''; panels.each(function(i, e) { var This = $(e); // Create panel menu container var panelHeader = This.children('.panel-heading'); $(panelControls).appendTo(panelHeader); // Check panel for settings specific attr. Use this // value to determine if menu item should be displayed var title = This.attr('data-panel-title'); var color = This.attr('data-panel-color'); var collapse = This.attr('data-panel-collapse'); var fullscreen = This.attr('data-panel-fullscreen'); var remove = This.attr('data-panel-remove'); var callback = This.attr('data-panel-callback'); var paneldock = This.attr('data-panel-dockable'); var expose = This.attr('data-panel-expose'); var loader = This.attr('data-panel-loader'); // attach loading indicator like any other button if (!loader) { // Create button var panelMenu = panelHeader.find('.panel-controls'); $(panelLoader).appendTo(panelMenu); } // Upcoming feature, not currently implemented if (expose) { // Create button var panelMenu = panelHeader.find('.panel-controls'); $(panelExpose).appendTo(panelMenu); } // Upcoming feature, not currently implemented if (paneldock) { // Create button var panelMenu = panelHeader.find('.panel-controls'); $(panelDock).appendTo(panelMenu); } // callback attr must be set true, else icon hidden if (callback) { // Create button var panelMenu = panelHeader.find('.panel-controls'); $(panelCallback).appendTo(panelMenu); } if (!remove) { // Create button var panelMenu = panelHeader.find('.panel-controls'); $(panelRemove).appendTo(panelMenu); } if (!title) { // Create button var panelMenu = panelHeader.find('.panel-controls'); $(panelTitle).appendTo(panelMenu); } if (!color) { var panelMenu = panelHeader.find('.panel-controls'); $(panelColor).appendTo(panelMenu); } if (!collapse) { // Create button var panelMenu = panelHeader.find('.panel-controls'); $(panelCollapse).appendTo(panelMenu); } if (!fullscreen) { // Create button var panelMenu = panelHeader.find('.panel-controls'); $(panelFullscreen).appendTo(panelMenu); } }); }, controlHandlers: function(e) { var This = $(this); // Control button indentifiers var action = This.attr('class'); var panel = This.parents('.panel'); // Panel header variables var panelHeading = panel.children('.panel-heading'); var panelTitle = panel.find('.panel-title'); // Edit Title definition var panelEditTitle = function() { // function for toggling the editbox menu var toggleBox = function() { var panelEditBox = panel.find('.panel-editbox'); panelEditBox.slideToggle('fast', function() { panel.toggleClass('panel-editbox-open'); // Save settings to key if editbox is being closed if (!panel.hasClass('panel-editbox-open')) { panelTitle.text(panelEditBox.children('input').val()); methods.updateSettings(options); } }); }; // If editbox not found, create one and attach handlers if (!panel.find('.panel-editbox').length) { var editBox = '
'; panelHeading.after(editBox); // New editbox container var panelEditBox = panel.find('.panel-editbox'); // Update panel title on key up panelEditBox.children('input').on('keyup', function() { panelTitle.text(panelEditBox.children('input').val()); }); // Save panel title on enter key press panelEditBox.children('input').on('keypress', function(e) { if (e.which == 13) { toggleBox(); } }); // Toggle editbox toggleBox(); } else { // If found simply toggle the menu toggleBox(); } }; // Panel color definition var panelColor = function() { // Create an editbox if one is not found if (!panel.find('.panel-colorbox').length) { var colorBox = '
' panelHeading.after(colorBox); } // Editbox container var panelColorBox = panel.find('.panel-colorbox'); // Update panel contextual color on click panelColorBox.on('click', '> span', function(e) { var dataColor = $(this).data('panel-color'); var altColors = 'panel-primary panel-info panel-success panel-warning panel-danger panel-alert panel-system panel-dark panel-default panel-white'; panel.removeClass(altColors).addClass(dataColor).data('panel-color', dataColor) methods.updateSettings(options); }); // Toggle elements visability and '.panel-editbox' class // If the box is being closed update settings key panelColorBox.slideToggle('fast', function() { panel.toggleClass('panel-colorbox-open'); }); }; // Collapse definition var panelCollapse = function() { // Toggle class panel.toggleClass('panel-collapsed'); // Toggle elements visability panel.children('.panel-body, .panel-menu, .panel-footer').slideToggle('fast', function() { methods.updateSettings(options); }); }; // Fullscreen definition var panelFullscreen = function() { // If fullscreen mode is active, remove class and enable panel sorting if ($('body.panel-fullscreen-active').length) { $('body').removeClass('panel-fullscreen-active'); panel.removeClass('panel-fullscreen'); if (dragSetting === true) { plugin.sortable("enable"); } } // if not active add fullscreen classes and disable panel sorting else { $('body').addClass('panel-fullscreen-active'); panel.addClass('panel-fullscreen'); if (dragSetting === true) { plugin.sortable("disable"); } } // Hide any open mobile menus or popovers $('.panel-controls').removeClass('panel-controls-open'); $('.popover').popover('hide'); // Trigger a global window resize to resize any plugins // the fullscreened content might contain. setTimeout(function() { $(window).trigger('resize'); }, 100); }; // Remove definition var panelRemove = function() { // check for Bootbox plugin - should be in core if (bootbox.confirm) { bootbox.confirm("Are You Sure?!", function(e) { // e returns true if user clicks "accept" // false if "cancel" or dismiss icon are clicked if (e) { // Timeout simply gives the user a second for the modal to // fade away so they can visibly see the panel disappear setTimeout(function() { panel.addClass('panel-removed').hide(); methods.updateSettings(options); }, 200); } }); } else { panel.addClass('panel-removed').hide(); methods.updateSettings(options); } }; // Remove definition var panelCallback = function() { if (typeof options.onPanel == 'function') { options.onPanel(); } }; // Expose.js definition var panelExpose = function() { // Code removed, feature will be added to next update // once all of the dynamic/responsive aspects of resizing // an exposed panel are worked out }; // Responses if ($(this).hasClass('panel-control-collapse')) { panelCollapse(); } if ($(this).hasClass('panel-control-title')) { panelEditTitle(); } if ($(this).hasClass('panel-control-color')) { panelColor(); } if ($(this).hasClass('panel-control-fullscreen')) { panelFullscreen(); } if ($(this).hasClass('panel-control-remove')) { panelRemove(); } if ($(this).hasClass('panel-control-callback')) { panelCallback(); } if ($(this).hasClass('panel-control-expose')) { panelExpose(); } if ($(this).hasClass('panel-control-dockable')) { return } if ($(this).hasClass('panel-control-loader')) { return; } // Toggle Loader indicator in response to action methods.toggleLoader.call(this); }, toggleLoader: function(options) { var This = $(this); var panel = This.parents('.panel'); // Add loader to panel panel.addClass('panel-loader-active'); // Remove loader after specified duration setTimeout(function() { panel.removeClass('panel-loader-active'); }, 650); }, modifySettings: function(options) { // Settings obj var settingsArr = []; // Determine settings of each panel. panels.each(function(i, e) { var This = $(e); var panelObj = {}; // Settings variables. var panelID = This.attr('id'); var panelTitle = This.children('.panel-heading').find('.panel-title').text(); var panelCollapsed = (This.hasClass('panel-collapsed') ? 1 : 0); var panelHidden = (This.is(':hidden') ? 1 : 0); var panelColor = This.data('panel-color'); panelObj['id'] = This.attr('id'); panelObj['title'] = This.children('.panel-heading').find('.panel-title').text(); panelObj['collapsed'] = (This.hasClass('panel-collapsed') ? 1 : 0); panelObj['hidden'] = (This.is(':hidden') ? 1 : 0); panelObj['color'] = (panelColor ? panelColor : null); settingsArr.push({ 'panel': panelObj }); }); var checkedSettings = JSON.stringify(settingsArr); // Log Results // console.log('Key contains: ', checkedSettings); // return panel positions array return checkedSettings; }, findPositions: function(options) { var grids = plugin.find(pluginGrid); var gridsArr = []; // Determine panels present. grids.each(function(index, ele) { var panels = $(ele).find('.panel'); var panelArr = []; $(ele).attr('id', 'grid-' + index); panels.each(function(i, e) { var panelID = $(e).attr('id'); panelArr.push(panelID); }); gridsArr[index] = panelArr; }); var checkedPosition = JSON.stringify(gridsArr); // return panel positions array return checkedPosition; }, setPositions: function(options) { // Variables var obj = this; var localPositions = localStorage.getItem(positionsKey); var parsePosition = JSON.parse(localPositions); // Pull localstorage obj, parse the data, and then loop // through each panel and set its position $(pluginGrid).each(function(i, e) { var rowID = $(e) $.each(parsePosition[i], function(i, ele) { $('#' + ele).appendTo(rowID); }); }); }, updatePositions: function(options) { localStorage.setItem(positionsKey, methods.findPositions()); // onSave callback if (typeof options.onSave == 'function') { options.onSave(); } }, updateSettings: function(options) { localStorage.setItem(settingsKey, methods.modifySettings()); // onSave callback if (typeof options.onSave == 'function') { options.onSave(); } } } // Plugin implementation return this.each(function() { methods.init.call(plugin, options); }); }; })(jQuery, window, document); ; // @see https://github.com/makeusabrew/bootbox/issues/180 // @see https://github.com/makeusabrew/bootbox/issues/186 (function (root, factory) { "use strict"; if (typeof define === "function" && define.amd) { // AMD. Register as an anonymous module. define(["jquery"], factory); } else if (typeof exports === "object") { // Node. Does not work with strict CommonJS, but // only CommonJS-like environments that support module.exports, // like Node. module.exports = factory(require("jquery")); } else { // Browser globals (root is window) root.bootbox = factory(root.jQuery); } }(this, function init($, undefined) { "use strict"; // the base DOM structure needed to create a modal var templates = { dialog: "", header: "", footer: "", closeButton: "", form: "
", inputs: { text: "", textarea: "", email: "", select: "", checkbox: "
", date: "", time: "", number: "", password: "" } }; var defaults = { // default language locale: "en", // show backdrop or not backdrop: true, // animate the modal in/out animate: true, // additional class string applied to the top level dialog className: null, // whether or not to enable keyboard binding keyboard: false, // whether or not to include a close button closeButton: true, // show the dialog immediately by default show: true, // dialog container container: "body" }; // our public object; augmented after our private API var exports = {}; /** * @private */ function _t(key) { var locale = locales[defaults.locale]; return locale ? locale[key] : locales.en[key]; } function processCallback(e, dialog, callback) { e.stopPropagation(); e.preventDefault(); // by default we assume a callback will get rid of the dialog, // although it is given the opportunity to override this // so, if the callback can be invoked and it *explicitly returns false* // then we'll set a flag to keep the dialog active... var preserveDialog = $.isFunction(callback) && callback(e) === false; // ... otherwise we'll bin it if (!preserveDialog) { dialog.modal("hide"); } } function getKeyLength(obj) { // @TODO defer to Object.keys(x).length if available? var k, t = 0; for (k in obj) { t ++; } return t; } function each(collection, iterator) { var index = 0; $.each(collection, function(key, value) { iterator(key, value, index++); }); } function sanitize(options) { var buttons; var total; if (typeof options !== "object") { throw new Error("Please supply an object of options"); } if (!options.message) { throw new Error("Please specify a message"); } // make sure any supplied options take precedence over defaults options = $.extend({}, defaults, options); if (!options.buttons) { options.buttons = {}; } // we only support Bootstrap's "static" and false backdrop args // supporting true would mean you could dismiss the dialog without // explicitly interacting with it options.backdrop = options.backdrop ? "static" : false; buttons = options.buttons; total = getKeyLength(buttons); each(buttons, function(key, button, index) { if ($.isFunction(button)) { // short form, assume value is our callback. Since button // isn't an object it isn't a reference either so re-assign it button = buttons[key] = { callback: button }; } // before any further checks make sure by now button is the correct type if ($.type(button) !== "object") { throw new Error("button with key " + key + " must be an object"); } if (!button.label) { // the lack of an explicit label means we'll assume the key is good enough button.label = key; } if (!button.className) { if (total <= 2 && index === total-1) { // always add a primary to the main option in a two-button dialog button.className = "btn-primary"; } else { button.className = "btn-default"; } } }); return options; } /** * map a flexible set of arguments into a single returned object * if args.length is already one just return it, otherwise * use the properties argument to map the unnamed args to * object properties * so in the latter case: * mapArguments(["foo", $.noop], ["message", "callback"]) * -> { message: "foo", callback: $.noop } */ function mapArguments(args, properties) { var argn = args.length; var options = {}; if (argn < 1 || argn > 2) { throw new Error("Invalid argument length"); } if (argn === 2 || typeof args[0] === "string") { options[properties[0]] = args[0]; options[properties[1]] = args[1]; } else { options = args[0]; } return options; } /** * merge a set of default dialog options with user supplied arguments */ function mergeArguments(defaults, args, properties) { return $.extend( // deep merge true, // ensure the target is an empty, unreferenced object {}, // the base options object for this type of dialog (often just buttons) defaults, // args could be an object or array; if it's an array properties will // map it to a proper options object mapArguments( args, properties ) ); } /** * this entry-level method makes heavy use of composition to take a simple * range of inputs and return valid options suitable for passing to bootbox.dialog */ function mergeDialogOptions(className, labels, properties, args) { // build up a base set of dialog properties var baseOptions = { className: "bootbox-" + className, buttons: createLabels.apply(null, labels) }; // ensure the buttons properties generated, *after* merging // with user args are still valid against the supplied labels return validateButtons( // merge the generated base properties with user supplied arguments mergeArguments( baseOptions, args, // if args.length > 1, properties specify how each arg maps to an object key properties ), labels ); } /** * from a given list of arguments return a suitable object of button labels * all this does is normalise the given labels and translate them where possible * e.g. "ok", "confirm" -> { ok: "OK, cancel: "Annuleren" } */ function createLabels() { var buttons = {}; for (var i = 0, j = arguments.length; i < j; i++) { var argument = arguments[i]; var key = argument.toLowerCase(); var value = argument.toUpperCase(); buttons[key] = { label: _t(value) }; } return buttons; } function validateButtons(options, buttons) { var allowedButtons = {}; each(buttons, function(key, value) { allowedButtons[value] = true; }); each(options.buttons, function(key) { if (allowedButtons[key] === undefined) { throw new Error("button key " + key + " is not allowed (options are " + buttons.join("\n") + ")"); } }); return options; } exports.defineLocale = function (name, values) { if (values) { locales[name] = { OK: values.OK, CANCEL: values.CANCEL, CONFIRM: values.CONFIRM }; return locales[name]; } else { delete locales[name]; return null; } }; exports.alert = function() { var options; options = mergeDialogOptions("alert", ["ok"], ["message", "callback"], arguments); if (options.callback && !$.isFunction(options.callback)) { throw new Error("alert requires callback property to be a function when provided"); } /** * overrides */ options.buttons.ok.callback = options.onEscape = function() { if ($.isFunction(options.callback)) { return options.callback(); } return true; }; return exports.dialog(options); }; exports.confirm = function() { var options; options = mergeDialogOptions("confirm", ["cancel", "confirm"], ["message", "callback"], arguments); /** * overrides; undo anything the user tried to set they shouldn't have */ options.buttons.cancel.callback = options.onEscape = function() { return options.callback(false); }; options.buttons.confirm.callback = function() { return options.callback(true); }; // confirm specific validation if (!$.isFunction(options.callback)) { throw new Error("confirm requires a callback"); } return exports.dialog(options); }; exports.prompt = function() { var options; var defaults; var dialog; var form; var input; var shouldShow; var inputOptions; // we have to create our form first otherwise // its value is undefined when gearing up our options // @TODO this could be solved by allowing message to // be a function instead... form = $(templates.form); // prompt defaults are more complex than others in that // users can override more defaults // @TODO I don't like that prompt has to do a lot of heavy // lifting which mergeDialogOptions can *almost* support already // just because of 'value' and 'inputType' - can we refactor? defaults = { className: "bootbox-prompt", buttons: createLabels("cancel", "confirm"), value: "", inputType: "text" }; options = validateButtons( mergeArguments(defaults, arguments, ["title", "callback"]), ["cancel", "confirm"] ); // capture the user's show value; we always set this to false before // spawning the dialog to give us a chance to attach some handlers to // it, but we need to make sure we respect a preference not to show it shouldShow = (options.show === undefined) ? true : options.show; /** * overrides; undo anything the user tried to set they shouldn't have */ options.message = form; options.buttons.cancel.callback = options.onEscape = function() { return options.callback(null); }; options.buttons.confirm.callback = function() { var value; switch (options.inputType) { case "text": case "textarea": case "email": case "select": case "date": case "time": case "number": case "password": value = input.val(); break; case "checkbox": var checkedItems = input.find("input:checked"); // we assume that checkboxes are always multiple, // hence we default to an empty array value = []; each(checkedItems, function(_, item) { value.push($(item).val()); }); break; } return options.callback(value); }; options.show = false; // prompt specific validation if (!options.title) { throw new Error("prompt requires a title"); } if (!$.isFunction(options.callback)) { throw new Error("prompt requires a callback"); } if (!templates.inputs[options.inputType]) { throw new Error("invalid prompt type"); } // create the input based on the supplied type input = $(templates.inputs[options.inputType]); switch (options.inputType) { case "text": case "textarea": case "email": case "date": case "time": case "number": case "password": input.val(options.value); break; case "select": var groups = {}; inputOptions = options.inputOptions || []; if (!inputOptions.length) { throw new Error("prompt with select requires options"); } each(inputOptions, function(_, option) { // assume the element to attach to is the input... var elem = input; if (option.value === undefined || option.text === undefined) { throw new Error("given options in wrong format"); } // ... but override that element if this option sits in a group if (option.group) { // initialise group if necessary if (!groups[option.group]) { groups[option.group] = $("").attr("label", option.group); } elem = groups[option.group]; } elem.append(""); }); each(groups, function(_, group) { input.append(group); }); // safe to set a select's value as per a normal input input.val(options.value); break; case "checkbox": var values = $.isArray(options.value) ? options.value : [options.value]; inputOptions = options.inputOptions || []; if (!inputOptions.length) { throw new Error("prompt with checkbox requires options"); } if (!inputOptions[0].value || !inputOptions[0].text) { throw new Error("given options in wrong format"); } // checkboxes have to nest within a containing element, so // they break the rules a bit and we end up re-assigning // our 'input' element to this container instead input = $("
"); each(inputOptions, function(_, option) { var checkbox = $(templates.inputs[options.inputType]); checkbox.find("input").attr("value", option.value); checkbox.find("label").append(option.text); // we've ensured values is an array so we can always iterate over it each(values, function(_, value) { if (value === option.value) { checkbox.find("input").prop("checked", true); } }); input.append(checkbox); }); break; } if (options.placeholder) { input.attr("placeholder", options.placeholder); } if(options.pattern){ input.attr("pattern", options.pattern); } // now place it in our form form.append(input); form.on("submit", function(e) { e.preventDefault(); // Fix for SammyJS (or similar JS routing library) hijacking the form post. e.stopPropagation(); // @TODO can we actually click *the* button object instead? // e.g. buttons.confirm.click() or similar dialog.find(".btn-primary").click(); }); dialog = exports.dialog(options); // clear the existing handler focusing the submit button... dialog.off("shown.bs.modal"); // ...and replace it with one focusing our input, if possible dialog.on("shown.bs.modal", function() { input.focus(); }); if (shouldShow === true) { dialog.modal("show"); } return dialog; }; exports.dialog = function(options) { options = sanitize(options); var dialog = $(templates.dialog); var innerDialog = dialog.find(".modal-dialog"); var body = dialog.find(".modal-body"); var buttons = options.buttons; var buttonStr = ""; var callbacks = { onEscape: options.onEscape }; if ($.fn.modal === undefined) { throw new Error( "$.fn.modal is not defined; please double check you have included " + "the Bootstrap JavaScript library. See http://getbootstrap.com/javascript/ " + "for more details." ); } each(buttons, function(key, button) { // @TODO I don't like this string appending to itself; bit dirty. Needs reworking // can we just build up button elements instead? slower but neater. Then button // can just become a template too buttonStr += ""; callbacks[key] = button.callback; }); body.find(".bootbox-body").html(options.message); if (options.animate === true) { dialog.addClass("fade"); } if (options.className) { dialog.addClass(options.className); } if (options.size === "large") { innerDialog.addClass("modal-lg"); } if (options.size === "small") { innerDialog.addClass("modal-sm"); } if (options.title) { body.before(templates.header); } if (options.closeButton) { var closeButton = $(templates.closeButton); if (options.title) { dialog.find(".modal-header").prepend(closeButton); } else { closeButton.css("margin-top", "-10px").prependTo(body); } } if (options.title) { dialog.find(".modal-title").html(options.title); } if (buttonStr.length) { body.after(templates.footer); dialog.find(".modal-footer").html(buttonStr); } /** * Bootstrap event listeners; used handle extra * setup & teardown required after the underlying * modal has performed certain actions */ dialog.on("hidden.bs.modal", function(e) { // ensure we don't accidentally intercept hidden events triggered // by children of the current dialog. We shouldn't anymore now BS // namespaces its events; but still worth doing if (e.target === this) { dialog.remove(); } }); /* dialog.on("show.bs.modal", function() { // sadly this doesn't work; show is called *just* before // the backdrop is added so we'd need a setTimeout hack or // otherwise... leaving in as would be nice if (options.backdrop) { dialog.next(".modal-backdrop").addClass("bootbox-backdrop"); } }); */ dialog.on("shown.bs.modal", function() { dialog.find(".btn-primary:first").focus(); }); /** * Bootbox event listeners; experimental and may not last * just an attempt to decouple some behaviours from their * respective triggers */ dialog.on("escape.close.bb", function(e) { if (callbacks.onEscape) { processCallback(e, dialog, callbacks.onEscape); } }); /** * Standard jQuery event listeners; used to handle user * interaction with our dialog */ dialog.on("click", ".modal-footer button", function(e) { var callbackKey = $(this).data("bb-handler"); processCallback(e, dialog, callbacks[callbackKey]); }); dialog.on("click", ".bootbox-close-button", function(e) { // onEscape might be falsy but that's fine; the fact is // if the user has managed to click the close button we // have to close the dialog, callback or not processCallback(e, dialog, callbacks.onEscape); }); dialog.on("keyup", function(e) { if (e.which === 27) { dialog.trigger("escape.close.bb"); } }); // the remainder of this method simply deals with adding our // dialogent to the DOM, augmenting it with Bootstrap's modal // functionality and then giving the resulting object back // to our caller $(options.container).append(dialog); dialog.modal({ backdrop: options.backdrop, keyboard: options.keyboard || false, show: false }); if (options.show) { dialog.modal("show"); } // @TODO should we return the raw element here or should // we wrap it in an object on which we can expose some neater // methods, e.g. var d = bootbox.alert(); d.hide(); instead // of d.modal("hide"); /* function BBDialog(elem) { this.elem = elem; } BBDialog.prototype = { hide: function() { return this.elem.modal("hide"); }, show: function() { return this.elem.modal("show"); } }; */ return dialog; }; exports.setDefaults = function() { var values = {}; if (arguments.length === 2) { // allow passing of single key/value... values[arguments[0]] = arguments[1]; } else { // ... and as an object too values = arguments[0]; } $.extend(defaults, values); }; exports.hideAll = function() { $(".bootbox").modal("hide"); return exports; }; /** * standard locales. Please add more according to ISO 639-1 standard. Multiple language variants are * unlikely to be required. If this gets too large it can be split out into separate JS files. */ var locales = { br : { OK : "OK", CANCEL : "Cancelar", CONFIRM : "Sim" }, cs : { OK : "OK", CANCEL : "Zrušit", CONFIRM : "Potvrdit" }, da : { OK : "OK", CANCEL : "Annuller", CONFIRM : "Accepter" }, de : { OK : "OK", CANCEL : "Abbrechen", CONFIRM : "Akzeptieren" }, el : { OK : "Εντάξει", CANCEL : "Ακύρωση", CONFIRM : "Επιβεβαίωση" }, en : { OK : "OK", CANCEL : "Cancel", CONFIRM : "OK" }, es : { OK : "OK", CANCEL : "Cancelar", CONFIRM : "Aceptar" }, et : { OK : "OK", CANCEL : "Katkesta", CONFIRM : "OK" }, fi : { OK : "OK", CANCEL : "Peruuta", CONFIRM : "OK" }, fr : { OK : "OK", CANCEL : "Annuler", CONFIRM : "D'accord" }, he : { OK : "אישור", CANCEL : "ביטול", CONFIRM : "אישור" }, hu : { OK : "OK", CANCEL : "Mégsem", CONFIRM : "Megerősít" }, hr : { OK : "OK", CANCEL : "Odustani", CONFIRM : "Potvrdi" }, id : { OK : "OK", CANCEL : "Batal", CONFIRM : "OK" }, it : { OK : "OK", CANCEL : "Annulla", CONFIRM : "Conferma" }, ja : { OK : "OK", CANCEL : "キャンセル", CONFIRM : "確認" }, lt : { OK : "Gerai", CANCEL : "Atšaukti", CONFIRM : "Patvirtinti" }, lv : { OK : "Labi", CANCEL : "Atcelt", CONFIRM : "Apstiprināt" }, nl : { OK : "OK", CANCEL : "Annuleren", CONFIRM : "Accepteren" }, no : { OK : "OK", CANCEL : "Avbryt", CONFIRM : "OK" }, pl : { OK : "OK", CANCEL : "Anuluj", CONFIRM : "Potwierdź" }, pt : { OK : "OK", CANCEL : "Cancelar", CONFIRM : "Confirmar" }, ru : { OK : "OK", CANCEL : "Отмена", CONFIRM : "Применить" }, sv : { OK : "OK", CANCEL : "Avbryt", CONFIRM : "OK" }, tr : { OK : "Tamam", CANCEL : "İptal", CONFIRM : "Onayla" }, zh_CN : { OK : "OK", CANCEL : "取消", CONFIRM : "确认" }, zh_TW : { OK : "OK", CANCEL : "取消", CONFIRM : "確認" } }; exports.init = function(_$) { return init(_$ || $); }; return exports; })); ; (function($) { function defined(a) { return typeof a !== 'undefined'; } function extend(child, parent, prototype) { var F = function() {}; F.prototype = parent.prototype; child.prototype = new F(); child.prototype.constructor = child; parent.prototype.constructor = parent; child._super = parent.prototype; if (prototype) { $.extend(child.prototype, prototype); } } var SUBST = [ ['', ''], // spec ['exit', 'cancel'], // firefox & old webkits expect cancelFullScreen instead of exitFullscreen ['screen', 'Screen'] // firefox expects FullScreen instead of Fullscreen ]; var VENDOR_PREFIXES = ['', 'o', 'ms', 'moz', 'webkit', 'webkitCurrent']; function native(obj, name) { var prefixed; if (typeof obj === 'string') { name = obj; obj = document; } for (var i = 0; i < SUBST.length; ++i) { name = name.replace(SUBST[i][0], SUBST[i][1]); for (var j = 0; j < VENDOR_PREFIXES.length; ++j) { prefixed = VENDOR_PREFIXES[j]; prefixed += j === 0 ? name : name.charAt(0).toUpperCase() + name.substr(1); if (defined(obj[prefixed])) { return obj[prefixed]; } } } return void 0; }var ua = navigator.userAgent; var fsEnabled = native('fullscreenEnabled'); var IS_ANDROID_CHROME = ua.indexOf('Android') !== -1 && ua.indexOf('Chrome') !== -1; var IS_NATIVELY_SUPPORTED = !IS_ANDROID_CHROME && defined(native('fullscreenElement')) && (!defined(fsEnabled) || fsEnabled === true); var version = $.fn.jquery.split('.'); var JQ_LT_17 = (parseInt(version[0]) < 2 && parseInt(version[1]) < 7); var FullScreenAbstract = function() { this.__options = null; this._fullScreenElement = null; this.__savedStyles = {}; }; FullScreenAbstract.prototype = { _DEFAULT_OPTIONS: { styles: { 'boxSizing': 'border-box', 'MozBoxSizing': 'border-box', 'WebkitBoxSizing': 'border-box' }, toggleClass: null }, __documentOverflow: 'visible', __htmlOverflow: 'visible', _preventDocumentScroll: function() { // Disabled ability this.__documentOverflow = $('body')[0].style.overflow; this.__htmlOverflow = $('html')[0].style.overflow; }, _allowDocumentScroll: function() { $('body')[0].style.overflow = this.__documentOverflow; $('html')[0].style.overflow = this.__htmlOverflow; }, _fullScreenChange: function() { if (!this.__options) return; // only process fullscreenchange events caused by this plugin if (!this.isFullScreen()) { this._allowDocumentScroll(); this._revertStyles(); this._triggerEvents(); this._fullScreenElement = null; } else { this._preventDocumentScroll(); this._triggerEvents(); } }, _fullScreenError: function(e) { if (!this.__options) return; // only process fullscreenchange events caused by this plugin this._revertStyles(); this._fullScreenElement = null; if (e) { $(document).trigger('fscreenerror', [e]); } }, _triggerEvents: function() { $(this._fullScreenElement).trigger(this.isFullScreen() ? 'fscreenopen' : 'fscreenclose'); $(document).trigger('fscreenchange', [this.isFullScreen(), this._fullScreenElement]); }, _saveAndApplyStyles: function() { var $elem = $(this._fullScreenElement); this.__savedStyles = {}; for (var property in this.__options.styles) { // save this.__savedStyles[property] = this._fullScreenElement.style[property]; // apply this._fullScreenElement.style[property] = this.__options.styles[property]; } if (this.__options.toggleClass) { $elem.addClass(this.__options.toggleClass); } }, _revertStyles: function() { var $elem = $(this._fullScreenElement); for (var property in this.__options.styles) { this._fullScreenElement.style[property] = this.__savedStyles[property]; } if (this.__options.toggleClass) { $elem.removeClass(this.__options.toggleClass); } }, open: function(elem, options) { // do nothing if request is for already fullscreened element if (elem === this._fullScreenElement) { return; } // exit active fullscreen before opening another one if (this.isFullScreen()) { this.exit(); } // save fullscreened element this._fullScreenElement = elem; // apply options, if any this.__options = $.extend(true, {}, this._DEFAULT_OPTIONS, options); // save current element styles and apply new this._saveAndApplyStyles(); }, exit: null, isFullScreen: null, isNativelySupported: function() { return IS_NATIVELY_SUPPORTED; } }; var FullScreenNative = function() { FullScreenNative._super.constructor.apply(this, arguments); this.exit = $.proxy(native('exitFullscreen'), document); this._DEFAULT_OPTIONS = $.extend(true, {}, this._DEFAULT_OPTIONS, { 'styles': { 'width': '100%', 'height': '100%' } }); $(document) .bind(this._prefixedString('fullscreenchange') + ' MSFullscreenChange', $.proxy(this._fullScreenChange, this)) .bind(this._prefixedString('fullscreenerror') + ' MSFullscreenError', $.proxy(this._fullScreenError, this)); }; extend(FullScreenNative, FullScreenAbstract, { VENDOR_PREFIXES: ['', 'o', 'moz', 'webkit'], _prefixedString: function(str) { return $.map(this.VENDOR_PREFIXES, function(s) { return s + str; }).join(' '); }, open: function(elem, options) { FullScreenNative._super.open.apply(this, arguments); var requestFS = native(elem, 'requestFullscreen'); requestFS.call(elem); }, exit: $.noop, isFullScreen: function() { return native('fullscreenElement') !== null; }, element: function() { return native('fullscreenElement'); } }); var FullScreenFallback = function() { FullScreenFallback._super.constructor.apply(this, arguments); this._DEFAULT_OPTIONS = $.extend({}, this._DEFAULT_OPTIONS, { 'styles': { 'position': 'fixed', 'zIndex': '2147483647', 'left': 0, 'top': 0, 'bottom': 0, 'right': 0 } }); this.__delegateKeydownHandler(); }; extend(FullScreenFallback, FullScreenAbstract, { __isFullScreen: false, __delegateKeydownHandler: function() { var $doc = $(document); $doc.delegate('*', 'keydown.fullscreen', $.proxy(this.__keydownHandler, this)); var data = JQ_LT_17 ? $doc.data('events') : $._data(document).events; var events = data['keydown']; if (!JQ_LT_17) { events.splice(0, 0, events.splice(events.delegateCount - 1, 1)[0]); } else { data.live.unshift(data.live.pop()); } }, __keydownHandler: function(e) { if (this.isFullScreen() && e.which === 27) { this.exit(); return false; } return true; }, _revertStyles: function() { FullScreenFallback._super._revertStyles.apply(this, arguments); // force redraw (fixes bug in IE7 with content dissapearing) this._fullScreenElement.offsetHeight; }, open: function(elem) { FullScreenFallback._super.open.apply(this, arguments); this.__isFullScreen = true; this._fullScreenChange(); }, exit: function() { this.__isFullScreen = false; this._fullScreenChange(); }, isFullScreen: function() { return this.__isFullScreen; }, element: function() { return this.__isFullScreen ? this._fullScreenElement : null; } });$.fullscreen = IS_NATIVELY_SUPPORTED ? new FullScreenNative() : new FullScreenFallback(); $.fn.fullscreen = function(options) { var elem = this[0]; options = $.extend({ toggleClass: null, overflow: 'hidden' }, options); options.styles = { overflow: options.overflow }; delete options.overflow; if (elem) { $.fullscreen.open(elem, options); } return this; }; })(jQuery); /*! * hoverIntent v1.8.0 - Copyright 2014 Brian Cherne * http://cherne.net/brian/resources/jquery.hoverIntent.html * You are free to use hoverIntent as long as this header is left intact. */ ; (function($){$.fn.hoverIntent=function(handlerIn,handlerOut,selector){var cfg={interval:100,sensitivity:6,timeout:0};if(typeof handlerIn==="object"){cfg=$.extend(cfg,handlerIn)}else{if($.isFunction(handlerOut)){cfg=$.extend(cfg,{over:handlerIn,out:handlerOut,selector:selector})}else{cfg=$.extend(cfg,{over:handlerIn,out:handlerIn,selector:handlerOut})}}var cX,cY,pX,pY;var track=function(ev){cX=ev.pageX;cY=ev.pageY};var compare=function(ev,ob){ob.hoverIntent_t=clearTimeout(ob.hoverIntent_t);if(Math.sqrt((pX-cX)*(pX-cX)+(pY-cY)*(pY-cY))0?l.push(this):(e[n](1),o=e[n]()>0,o&&l.push(this),e[n](0))}}),l.length||this.each(function(){"BODY"===this.nodeName&&(l=[this])}),"first"===e.el&&l.length>1&&(l=[l[0]]),l};t.fn.extend({scrollable:function(t){var e=s.call(this,{dir:t});return this.pushStack(e)},firstScrollable:function(t){var e=s.call(this,{el:"first",dir:t});return this.pushStack(e)},smoothScroll:function(l,o){if(l=l||{},"options"===l)return o?this.each(function(){var e=t(this),l=t.extend(e.data("ssOpts")||{},o);t(this).data("ssOpts",l)}):this.first().data("ssOpts");var n=t.extend({},t.fn.smoothScroll.defaults,l),s=t.smoothScroll.filterPath(location.pathname);return this.unbind("click.smoothscroll").bind("click.smoothscroll",function(l){var o=this,r=t(this),i=t.extend({},n,r.data("ssOpts")||{}),c=n.exclude,a=i.excludeWithin,f=0,h=0,u=!0,d={},p=location.hostname===o.hostname||!o.hostname,m=i.scrollTarget||t.smoothScroll.filterPath(o.pathname)===s,S=e(o.hash);if(i.scrollTarget||p&&m&&S){for(;u&&c.length>f;)r.is(e(c[f++]))&&(u=!1);for(;u&&a.length>h;)r.closest(a[h++]).length&&(u=!1)}else u=!1;u&&(i.preventDefault&&l.preventDefault(),t.extend(d,i,{scrollTarget:i.scrollTarget||S,link:o}),t.smoothScroll(d))}),this}}),t.smoothScroll=function(e,l){if("options"===e&&"object"==typeof l)return t.extend(o,l);var n,s,r,i,c,a=0,f="offset",h="scrollTop",u={},d={};"number"==typeof e?(n=t.extend({link:null},t.fn.smoothScroll.defaults,o),r=e):(n=t.extend({link:null},t.fn.smoothScroll.defaults,e||{},o),n.scrollElement&&(f="position","static"===n.scrollElement.css("position")&&n.scrollElement.css("position","relative"))),h="left"===n.direction?"scrollLeft":h,n.scrollElement?(s=n.scrollElement,/^(?:HTML|BODY)$/.test(s[0].nodeName)||(a=s[h]())):s=t("html, body").firstScrollable(n.direction),n.beforeScroll.call(s,n),r="number"==typeof e?e:l||t(n.scrollTarget)[f]()&&t(n.scrollTarget)[f]()[n.direction]||0,u[h]=r+a+n.offset,i=n.speed,"auto"===i&&(c=u[h]-s.scrollTop(),0>c&&(c*=-1),i=c/n.autoCoefficient),d={duration:i,easing:n.easing,complete:function(){n.afterScroll.call(n.link,n)}},n.step&&(d.step=n.step),s.length?s.stop().animate(u,d):n.afterScroll.call(n.link,n)},t.smoothScroll.version=l,t.smoothScroll.filterPath=function(t){return t=t||"",t.replace(/^\//,"").replace(/(?:index|default).[a-zA-Z]{3,4}$/,"").replace(/\/$/,"")},t.fn.smoothScroll.defaults=n}); /* * jQuery UI Touch Punch 0.2.3 */ ; !function(a){function f(a,b){if(!(a.originalEvent.touches.length>1)){a.preventDefault();var c=a.originalEvent.changedTouches[0],d=document.createEvent("MouseEvents");d.initMouseEvent(b,!0,!0,window,1,c.screenX,c.screenY,c.clientX,c.clientY,!1,!1,!1,!1,0,null),a.target.dispatchEvent(d)}}if(a.support.touch="ontouchend"in document,a.support.touch){var e,b=a.ui.mouse.prototype,c=b._mouseInit,d=b._mouseDestroy;b._touchStart=function(a){var b=this;!e&&b._mouseCapture(a.originalEvent.changedTouches[0])&&(e=!0,b._touchMoved=!1,f(a,"mouseover"),f(a,"mousemove"),f(a,"mousedown"))},b._touchMove=function(a){e&&(this._touchMoved=!0,f(a,"mousemove"))},b._touchEnd=function(a){e&&(f(a,"mouseup"),f(a,"mouseout"),this._touchMoved||f(a,"click"),e=!1)},b._mouseInit=function(){var b=this;b.element.bind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),c.call(b)},b._mouseDestroy=function(){var b=this;b.element.unbind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),d.call(b)}}}(jQuery); /* * https://github.com/douglascrockford/JSON-js/blob/master/json2.js */ ; var JSON;if(!JSON){JSON={}}(function(){function f(a){return a<10?"0"+a:a}function quote(a){escapable.lastIndex=0;return escapable.test(a)?'"'+a.replace(escapable,function(a){var b=meta[a];return typeof b==="string"?b:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+a+'"'}function str(a,b){var c,d,e,f,g=gap,h,i=b[a];if(i&&typeof i==="object"&&typeof i.toJSON==="function"){i=i.toJSON(a)}if(typeof rep==="function"){i=rep.call(b,a,i)}switch(typeof i){case"string":return quote(i);case"number":return isFinite(i)?String(i):"null";case"boolean":case"null":return String(i);case"object":if(!i){return"null"}gap+=indent;h=[];if(Object.prototype.toString.apply(i)==="[object Array]"){f=i.length;for(c=0;cb+e+d}var b="onmousewheel"in window?"ActiveXObject"in window?"wheel":"mousewheel":"DOMMouseScroll",c=".scrollLock",d=a.fn.scrollLock;a.fn.scrollLock=function(d,f,g){return"string"!=typeof f&&(f=null),void 0!==d&&!d||"off"===d?this.each(function(){a(this).off(c)}):this.each(function(){a(this).on(b+c,f,function(b){if(!b.ctrlKey){var c=a(this);if(g===!0||e(c)){b.stopPropagation();var d=c.scrollTop(),f=c.prop("scrollHeight"),h=c.prop("clientHeight"),i=b.originalEvent.wheelDelta||-1*b.originalEvent.detail||-1*b.originalEvent.deltaY,j=0;if("wheel"===b.type){var k=c.height()/a(window).height();j=b.originalEvent.deltaY*k}(i>0&&0>=d+j||0>i&&d+j>=f-h)&&(b.preventDefault(),j&&c.scrollTop(d+j))}}})})},a.fn.scrollLock.noConflict=function(){return a.fn.scrollLock=d,this}}); ; ! function($) { "use strict"; // jshint ;_; if (typeof ko !== 'undefined' && ko.bindingHandlers && !ko.bindingHandlers.multiselect) { ko.bindingHandlers.multiselect = { init: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { var listOfSelectedItems = allBindingsAccessor().selectedOptions; var config = ko.utils.unwrapObservable(valueAccessor()); $(element).multiselect(config); if (isObservableArray(listOfSelectedItems)) { // Set the initial selection state on the multiselect list. $(element).multiselect('select', ko.utils.unwrapObservable(listOfSelectedItems)); // Subscribe to the selectedOptions: ko.observableArray listOfSelectedItems.subscribe(function(changes) { var addedArray = [], deletedArray = []; forEach(changes, function(change) { switch (change.status) { case 'added': addedArray.push(change.value); break; case 'deleted': deletedArray.push(change.value); break; } }); if (addedArray.length > 0) { $(element).multiselect('select', addedArray); } if (deletedArray.length > 0) { $(element).multiselect('deselect', deletedArray); } }, null, "arrayChange"); } }, update: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { var listOfItems = allBindingsAccessor().options, ms = $(element).data('multiselect'), config = ko.utils.unwrapObservable(valueAccessor()); if (isObservableArray(listOfItems)) { // Subscribe to the options: ko.observableArray incase it changes later listOfItems.subscribe(function(theArray) { $(element).multiselect('rebuild'); }); } if (!ms) { $(element).multiselect(config); } else { ms.updateOriginalOptions(); } } }; } function isObservableArray(obj) { return ko.isObservable(obj) && !(obj.destroyAll === undefined); } function forEach(array, callback) { for (var index = 0; index < array.length; ++index) { callback(array[index]); } } /** * Constructor to create a new multiselect using the given select. * * @param {jQuery} select * @param {Object} options * @returns {Multiselect} */ function Multiselect(select, options) { this.$select = $(select); this.options = this.mergeOptions($.extend({}, options, this.$select.data())); // Initialization. // We have to clone to create a new reference. this.originalOptions = this.$select.clone()[0].options; this.query = ''; this.searchTimeout = null; this.options.multiple = this.$select.attr('multiple') === "multiple"; this.options.onChange = $.proxy(this.options.onChange, this); this.options.onDropdownShow = $.proxy(this.options.onDropdownShow, this); this.options.onDropdownHide = $.proxy(this.options.onDropdownHide, this); this.options.onDropdownShown = $.proxy(this.options.onDropdownShown, this); this.options.onDropdownHidden = $.proxy(this.options.onDropdownHidden, this); // Build select all if enabled. this.buildContainer(); this.buildButton(); this.buildDropdown(); this.buildSelectAll(); this.buildDropdownOptions(); this.buildFilter(); this.updateButtonText(); this.updateSelectAll(); if (this.options.disableIfEmpty && $('option', this.$select).length <= 0) { this.disable(); } this.$select.hide().after(this.$container); }; Multiselect.prototype = { defaults: { /** * Default text function will either print 'None selected' in case no * option is selected or a list of the selected options up to a length * of 3 selected options. * * @param {jQuery} options * @param {jQuery} select * @returns {String} */ buttonText: function(options, select) { if (options.length === 0) { return this.nonSelectedText + ' '; } else if (options.length == $('option', $(select)).length) { return this.allSelectedText + ' '; } else if (options.length > this.numberDisplayed) { return options.length + ' ' + this.nSelectedText + ' '; } else { var selected = ''; options.each(function() { var label = ($(this).attr('label') !== undefined) ? $(this).attr('label') : $(this).html(); selected += label + ', '; }); return selected.substr(0, selected.length - 2) + ' '; } }, /** * Updates the title of the button similar to the buttonText function. * * @param {jQuery} options * @param {jQuery} select * @returns {@exp;selected@call;substr} */ buttonTitle: function(options, select) { if (options.length === 0) { return this.nonSelectedText; } else { var selected = ''; options.each(function() { selected += $(this).text() + ', '; }); return selected.substr(0, selected.length - 2); } }, /** * Create a label. * * @param {jQuery} element * @returns {String} */ label: function(element) { return $(element).attr('label') || $(element).html(); }, /** * Triggered on change of the multiselect. * * Not triggered when selecting/deselecting options manually. * * @param {jQuery} option * @param {Boolean} checked */ onChange: function(option, checked) { }, /** * Triggered when the dropdown is shown. * * @param {jQuery} event */ onDropdownShow: function(event) { }, /** * Triggered when the dropdown is hidden. * * @param {jQuery} event */ onDropdownHide: function(event) { }, /** * Triggered after the dropdown is shown. * * @param {jQuery} event */ onDropdownShown: function(event) { }, /** * Triggered after the dropdown is hidden. * * @param {jQuery} event */ onDropdownHidden: function(event) { }, buttonClass: 'btn btn-default', buttonWidth: 'auto', buttonContainer: '
', dropRight: false, selectedClass: 'active', // Maximum height of the dropdown menu. // If maximum height is exceeded a scrollbar will be displayed. maxHeight: false, checkboxName: false, includeSelectAllOption: false, includeSelectAllIfMoreThan: 0, selectAllText: ' Select all', selectAllValue: 'multiselect-all', selectAllName: false, enableFiltering: false, enableCaseInsensitiveFiltering: false, enableClickableOptGroups: false, filterPlaceholder: 'Search', // possible options: 'text', 'value', 'both' filterBehavior: 'text', includeFilterClearBtn: true, preventInputChangeEvent: false, nonSelectedText: 'None selected', nSelectedText: 'selected', allSelectedText: 'All selected', numberDisplayed: 3, disableIfEmpty: false, templates: { button: '', ul: '', filter: '
  • ', filterClearBtn: '', li: '
  • ', divider: '
  • ', liGroup: '
  • ' } }, constructor: Multiselect, /** * Builds the container of the multiselect. */ buildContainer: function() { this.$container = $(this.options.buttonContainer); this.$container.on('show.bs.dropdown', this.options.onDropdownShow); this.$container.on('hide.bs.dropdown', this.options.onDropdownHide); this.$container.on('shown.bs.dropdown', this.options.onDropdownShown); this.$container.on('hidden.bs.dropdown', this.options.onDropdownHidden); }, /** * Builds the button of the multiselect. */ buildButton: function() { this.$button = $(this.options.templates.button).addClass(this.options.buttonClass); // Adopt active state. if (this.$select.prop('disabled')) { this.disable(); } else { this.enable(); } // Manually add button width if set. if (this.options.buttonWidth && this.options.buttonWidth !== 'auto') { this.$button.css({ 'width': this.options.buttonWidth }); this.$container.css({ 'width': this.options.buttonWidth }); } // Keep the tab index from the select. var tabindex = this.$select.attr('tabindex'); if (tabindex) { this.$button.attr('tabindex', tabindex); } this.$container.prepend(this.$button); }, /** * Builds the ul representing the dropdown menu. */ buildDropdown: function() { // Build ul. this.$ul = $(this.options.templates.ul); if (this.options.dropRight) { this.$ul.addClass('pull-right'); } // Set max height of dropdown menu to activate auto scrollbar. if (this.options.maxHeight) { // TODO: Add a class for this option to move the css declarations. this.$ul.css({ 'max-height': this.options.maxHeight + 'px', 'overflow-y': 'auto', 'overflow-x': 'hidden' }); } this.$container.append(this.$ul); }, /** * Build the dropdown options and binds all nessecary events. * * Uses createDivider and createOptionValue to create the necessary options. */ buildDropdownOptions: function() { this.$select.children().each($.proxy(function(index, element) { var $element = $(element); // Support optgroups and options without a group simultaneously. var tag = $element.prop('tagName') .toLowerCase(); if ($element.prop('value') === this.options.selectAllValue) { return; } if (tag === 'optgroup') { this.createOptgroup(element); } else if (tag === 'option') { if ($element.data('role') === 'divider') { this.createDivider(); } else { this.createOptionValue(element); } } // Other illegal tags will be ignored. }, this)); // Bind the change event on the dropdown elements. $('li input', this.$ul).on('change', $.proxy(function(event) { var $target = $(event.target); var checked = $target.prop('checked') || false; var isSelectAllOption = $target.val() === this.options.selectAllValue; // Apply or unapply the configured selected class. if (this.options.selectedClass) { if (checked) { $target.closest('li') .addClass(this.options.selectedClass); } else { $target.closest('li') .removeClass(this.options.selectedClass); } } // Get the corresponding option. var value = $target.val(); var $option = this.getOptionByValue(value); var $optionsNotThis = $('option', this.$select).not($option); var $checkboxesNotThis = $('input', this.$container).not($target); if (isSelectAllOption) { if (checked) { this.selectAll(); } else { this.deselectAll(); } } if (!isSelectAllOption) { if (checked) { $option.prop('selected', true); if (this.options.multiple) { // Simply select additional option. $option.prop('selected', true); } else { // Unselect all other options and corresponding checkboxes. if (this.options.selectedClass) { $($checkboxesNotThis).closest('li').removeClass(this.options.selectedClass); } $($checkboxesNotThis).prop('checked', false); $optionsNotThis.prop('selected', false); // It's a single selection, so close. this.$button.click(); } if (this.options.selectedClass === "active") { $optionsNotThis.closest("a").css("outline", ""); } } else { // Unselect option. $option.prop('selected', false); } } this.$select.change(); this.updateButtonText(); this.updateSelectAll(); this.options.onChange($option, checked); if (this.options.preventInputChangeEvent) { return false; } }, this)); $('li a', this.$ul).on('touchstart click', function(event) { event.stopPropagation(); var $target = $(event.target); if (document.getSelection().type === 'Range') { var $input = $(this).find("input:first"); $input.prop("checked", !$input.prop("checked")) .trigger("change"); } if (event.shiftKey) { var checked = $target.prop('checked') || false; if (checked) { var prev = $target.closest('li') .siblings('li[class="active"]:first'); var currentIdx = $target.closest('li') .index(); var prevIdx = prev.index(); if (currentIdx > prevIdx) { $target.closest("li").prevUntil(prev).each( function() { $(this).find("input:first").prop("checked", true) .trigger("change"); } ); } else { $target.closest("li").nextUntil(prev).each( function() { $(this).find("input:first").prop("checked", true) .trigger("change"); } ); } } } $target.blur(); }); // Keyboard support. this.$container.off('keydown.multiselect').on('keydown.multiselect', $.proxy(function(event) { if ($('input[type="text"]', this.$container).is(':focus')) { return; } if (event.keyCode === 9 && this.$container.hasClass('open')) { this.$button.click(); } else { var $items = $(this.$container).find("li:not(.divider):not(.disabled) a").filter(":visible"); if (!$items.length) { return; } var index = $items.index($items.filter(':focus')); // Navigation up. if (event.keyCode === 38 && index > 0) { index--; } // Navigate down. else if (event.keyCode === 40 && index < $items.length - 1) { index++; } else if (!~index) { index = 0; } var $current = $items.eq(index); $current.focus(); if (event.keyCode === 32 || event.keyCode === 13) { var $checkbox = $current.find('input'); $checkbox.prop("checked", !$checkbox.prop("checked")); $checkbox.change(); } event.stopPropagation(); event.preventDefault(); } }, this)); if (this.options.enableClickableOptGroups && this.options.multiple) { $('li.multiselect-group', this.$ul).on('click', $.proxy(function(event) { event.stopPropagation(); var group = $(event.target).parent(); // Search all option in optgroup var $options = group.nextUntil('li.multiselect-group'); // check or uncheck items var allChecked = true; var optionInputs = $options.find('input'); optionInputs.each(function() { allChecked = allChecked && $(this).prop('checked'); }); optionInputs.prop('checked', !allChecked).trigger('change'); }, this)); } }, /** * Create an option using the given select option. * * @param {jQuery} element */ createOptionValue: function(element) { var $element = $(element); if ($element.is(':selected')) { $element.prop('selected', true); } // Support the label attribute on options. var label = this.options.label(element); var value = $element.val(); var inputType = this.options.multiple ? "checkbox" : "radio"; var $li = $(this.options.templates.li); var $label = $('label', $li); $label.addClass(inputType); var $checkbox = $('').attr('type', inputType); if (this.options.checkboxName) { $checkbox.attr('name', this.options.checkboxName); } $label.append($checkbox); var selected = $element.prop('selected') || false; $checkbox.val(value); if (value === this.options.selectAllValue) { $li.addClass("multiselect-item multiselect-all"); $checkbox.parent().parent() .addClass('multiselect-all'); } $label.append(" " + label); $label.attr('title', $element.attr('title')); this.$ul.append($li); if ($element.is(':disabled')) { $checkbox.attr('disabled', 'disabled') .prop('disabled', true) .closest('a') .attr("tabindex", "-1") .closest('li') .addClass('disabled'); } $checkbox.prop('checked', selected); if (selected && this.options.selectedClass) { $checkbox.closest('li') .addClass(this.options.selectedClass); } }, /** * Creates a divider using the given select option. * * @param {jQuery} element */ createDivider: function(element) { var $divider = $(this.options.templates.divider); this.$ul.append($divider); }, /** * Creates an optgroup. * * @param {jQuery} group */ createOptgroup: function(group) { var groupName = $(group).prop('label'); // Add a header for the group. var $li = $(this.options.templates.liGroup); $('label', $li).text(groupName); if (this.options.enableClickableOptGroups) { $li.addClass('multiselect-group-clickable'); } this.$ul.append($li); if ($(group).is(':disabled')) { $li.addClass('disabled'); } // Add the options of the group. $('option', group).each($.proxy(function(index, element) { this.createOptionValue(element); }, this)); }, /** * Build the selct all. * * Checks if a select all has already been created. */ buildSelectAll: function() { if (typeof this.options.selectAllValue === 'number') { this.options.selectAllValue = this.options.selectAllValue.toString(); } var alreadyHasSelectAll = this.hasSelectAll(); if (!alreadyHasSelectAll && this.options.includeSelectAllOption && this.options.multiple && $('option', this.$select).length > this.options.includeSelectAllIfMoreThan) { // Check whether to add a divider after the select all. if (this.options.includeSelectAllDivider) { this.$ul.prepend($(this.options.templates.divider)); } var $li = $(this.options.templates.li); $('label', $li).addClass("checkbox"); if (this.options.selectAllName) { $('label', $li).append(''); } else { $('label', $li).append(''); } var $checkbox = $('input', $li); $checkbox.val(this.options.selectAllValue); $li.addClass("multiselect-item multiselect-all"); $checkbox.parent().parent() .addClass('multiselect-all'); $('label', $li).append(" " + this.options.selectAllText); this.$ul.prepend($li); $checkbox.prop('checked', false); } }, /** * Builds the filter. */ buildFilter: function() { // Build filter if filtering OR case insensitive filtering is enabled and the number of options exceeds (or equals) enableFilterLength. if (this.options.enableFiltering || this.options.enableCaseInsensitiveFiltering) { var enableFilterLength = Math.max(this.options.enableFiltering, this.options.enableCaseInsensitiveFiltering); if (this.$select.find('option').length >= enableFilterLength) { this.$filter = $(this.options.templates.filter); $('input', this.$filter).attr('placeholder', this.options.filterPlaceholder); // Adds optional filter clear button if (this.options.includeFilterClearBtn) { var clearBtn = $(this.options.templates.filterClearBtn); clearBtn.on('click', $.proxy(function(event) { clearTimeout(this.searchTimeout); this.$filter.find('.multiselect-search').val(''); $('li', this.$ul).show().removeClass("filter-hidden"); this.updateSelectAll(); }, this)); this.$filter.find('.input-group').append(clearBtn); } this.$ul.prepend(this.$filter); this.$filter.val(this.query).on('click', function(event) { event.stopPropagation(); }).on('input keydown', $.proxy(function(event) { // Cancel enter key default behaviour if (event.which === 13) { event.preventDefault(); } // This is useful to catch "keydown" events after the browser has updated the control. clearTimeout(this.searchTimeout); this.searchTimeout = this.asyncFunction($.proxy(function() { if (this.query !== event.target.value) { this.query = event.target.value; var currentGroup, currentGroupVisible; $.each($('li', this.$ul), $.proxy(function(index, element) { var value = $('input', element).val(); var text = $('label', element).text(); var filterCandidate = ''; if ((this.options.filterBehavior === 'text')) { filterCandidate = text; } else if ((this.options.filterBehavior === 'value')) { filterCandidate = value; } else if (this.options.filterBehavior === 'both') { filterCandidate = text + '\n' + value; } if (value !== this.options.selectAllValue && text) { // By default lets assume that element is not // interesting for this search. var showElement = false; if (this.options.enableCaseInsensitiveFiltering && filterCandidate.toLowerCase().indexOf(this.query.toLowerCase()) > -1) { showElement = true; } else if (filterCandidate.indexOf(this.query) > -1) { showElement = true; } // Toggle current element (group or group item) according to showElement boolean. $(element).toggle(showElement).toggleClass('filter-hidden', !showElement); // Differentiate groups and group items. if ($(element).hasClass('multiselect-group')) { // Remember group status. currentGroup = element; currentGroupVisible = showElement; } else { // Show group name when at least one of its items is visible. if (showElement) { $(currentGroup).show().removeClass('filter-hidden'); } // Show all group items when group name satisfies filter. if (!showElement && currentGroupVisible) { $(element).show().removeClass('filter-hidden'); } } } }, this)); } this.updateSelectAll(); }, this), 300, this); }, this)); } } }, /** * Unbinds the whole plugin. */ destroy: function() { this.$container.remove(); this.$select.show(); this.$select.data('multiselect', null); }, /** * Refreshs the multiselect based on the selected options of the select. */ refresh: function() { $('option', this.$select).each($.proxy(function(index, element) { var $input = $('li input', this.$ul).filter(function() { return $(this).val() === $(element).val(); }); if ($(element).is(':selected')) { $input.prop('checked', true); if (this.options.selectedClass) { $input.closest('li') .addClass(this.options.selectedClass); } } else { $input.prop('checked', false); if (this.options.selectedClass) { $input.closest('li') .removeClass(this.options.selectedClass); } } if ($(element).is(":disabled")) { $input.attr('disabled', 'disabled') .prop('disabled', true) .closest('li') .addClass('disabled'); } else { $input.prop('disabled', false) .closest('li') .removeClass('disabled'); } }, this)); this.updateButtonText(); this.updateSelectAll(); }, /** * Select all options of the given values. * * If triggerOnChange is set to true, the on change event is triggered if * and only if one value is passed. * * @param {Array} selectValues * @param {Boolean} triggerOnChange */ select: function(selectValues, triggerOnChange) { if (!$.isArray(selectValues)) { selectValues = [selectValues]; } for (var i = 0; i < selectValues.length; i++) { var value = selectValues[i]; if (value === null || value === undefined) { continue; } var $option = this.getOptionByValue(value); var $checkbox = this.getInputByValue(value); if ($option === undefined || $checkbox === undefined) { continue; } if (!this.options.multiple) { this.deselectAll(false); } if (this.options.selectedClass) { $checkbox.closest('li') .addClass(this.options.selectedClass); } $checkbox.prop('checked', true); $option.prop('selected', true); } this.updateButtonText(); this.updateSelectAll(); if (triggerOnChange && selectValues.length === 1) { this.options.onChange($option, true); } }, /** * Clears all selected items. */ clearSelection: function() { this.deselectAll(false); this.updateButtonText(); this.updateSelectAll(); }, /** * Deselects all options of the given values. * * If triggerOnChange is set to true, the on change event is triggered, if * and only if one value is passed. * * @param {Array} deselectValues * @param {Boolean} triggerOnChange */ deselect: function(deselectValues, triggerOnChange) { if (!$.isArray(deselectValues)) { deselectValues = [deselectValues]; } for (var i = 0; i < deselectValues.length; i++) { var value = deselectValues[i]; if (value === null || value === undefined) { continue; } var $option = this.getOptionByValue(value); var $checkbox = this.getInputByValue(value); if ($option === undefined || $checkbox === undefined) { continue; } if (this.options.selectedClass) { $checkbox.closest('li') .removeClass(this.options.selectedClass); } $checkbox.prop('checked', false); $option.prop('selected', false); } this.updateButtonText(); this.updateSelectAll(); if (triggerOnChange && deselectValues.length === 1) { this.options.onChange($option, false); } }, /** * Selects all enabled & visible options. * * If justVisible is true or not specified, only visible options are selected. * * @param {Boolean} justVisible */ selectAll: function(justVisible) { var justVisible = typeof justVisible === 'undefined' ? true : justVisible; var allCheckboxes = $("li input[type='checkbox']:enabled", this.$ul); var visibleCheckboxes = allCheckboxes.filter(":visible"); var allCheckboxesCount = allCheckboxes.length; var visibleCheckboxesCount = visibleCheckboxes.length; if (justVisible) { visibleCheckboxes.prop('checked', true); $("li:not(.divider):not(.disabled)", this.$ul).filter(":visible").addClass(this.options.selectedClass); } else { allCheckboxes.prop('checked', true); $("li:not(.divider):not(.disabled)", this.$ul).addClass(this.options.selectedClass); } if (allCheckboxesCount === visibleCheckboxesCount || justVisible === false) { $("option:enabled", this.$select).prop('selected', true); } else { var values = visibleCheckboxes.map(function() { return $(this).val(); }).get(); $("option:enabled", this.$select).filter(function(index) { return $.inArray($(this).val(), values) !== -1; }).prop('selected', true); } }, /** * Deselects all options. * * If justVisible is true or not specified, only visible options are deselected. * * @param {Boolean} justVisible */ deselectAll: function(justVisible) { var justVisible = typeof justVisible === 'undefined' ? true : justVisible; if (justVisible) { var visibleCheckboxes = $("li input[type='checkbox']:enabled", this.$ul).filter(":visible"); visibleCheckboxes.prop('checked', false); var values = visibleCheckboxes.map(function() { return $(this).val(); }).get(); $("option:enabled", this.$select).filter(function(index) { return $.inArray($(this).val(), values) !== -1; }).prop('selected', false); if (this.options.selectedClass) { $("li:not(.divider):not(.disabled)", this.$ul).filter(":visible").removeClass(this.options.selectedClass); } } else { $("li input[type='checkbox']:enabled", this.$ul).prop('checked', false); $("option:enabled", this.$select).prop('selected', false); if (this.options.selectedClass) { $("li:not(.divider):not(.disabled)", this.$ul).removeClass(this.options.selectedClass); } } }, /** * Rebuild the plugin. * * Rebuilds the dropdown, the filter and the select all option. */ rebuild: function() { this.$ul.html(''); // Important to distinguish between radios and checkboxes. this.options.multiple = this.$select.attr('multiple') === "multiple"; this.buildSelectAll(); this.buildDropdownOptions(); this.buildFilter(); this.updateButtonText(); this.updateSelectAll(); if (this.options.disableIfEmpty && $('option', this.$select).length <= 0) { this.disable(); } if (this.options.dropRight) { this.$ul.addClass('pull-right'); } }, /** * The provided data will be used to build the dropdown. */ dataprovider: function(dataprovider) { var optionDOM = ""; var groupCounter = 0; var tags = $(''); // create empty jQuery array $.each(dataprovider, function(index, option) { var tag; if ($.isArray(option.children)) { // create optiongroup tag groupCounter++; tag = $('').attr({ label: option.label || 'Group ' + groupCounter }); forEach(option.children, function(subOption) { // add children option tags tag.append($(''; } else { // create option tag tag = $('