!function ($) { /* ======================= * Popover jQuery Plugin * ======================= * Current version: v1.0.0, Last updated: 01-28-2013 * Description: Cross-browser, mobile compatible popover. * * Author: Jordan Kelly * Github: https://github.com/FoundOPS/popover * * Compatible browsers: IE 9+, Chrome ?+, FF 3.6+, Android 2.3+, iOS 4+ * * jQuery functions: * $(selector).popover([methods] or [config]); * $(selector).optionsPopover([methods] or [config]); * * Config parameters: Example usage - $(selector).popover({..., fontColor: "#000", ...}); * id - When passed for initial menu, id must be the same as the id/class used in selector. * eg) "#popoverButton" * * title - Title to be displayed on header. * * contents - popover: An html string to be inserted. * - optionsPopover: An array of row data. * //TODO: Document more. * * backgroundColor - Sets the background color for all popups. Accepts hex and color keywords. * eg) "#000000", "black", etc. * * fontColor - Sets the font color for all popups. Accepts hex and color keywords. * eg) "#000000", "black", etc. * * borderColor - Sets the border color for all popups. Accepts hex and color keywords. * eg) "#000000", "black", etc. * * keepData - Boolean that indicates if header and content should be cleared/set on visible. * WARNING: MAY BE REMOVED IN FUTURE VERSIONS. * eg) truthy or falsesy values * * childToAppend - A documentFragment or dom element to be appended after content is set. * WARNING: MAY BE REMOVED IN FUTURE VERSIONS. * eg) * * onCreate - A function to be called after popover is created. * eg) function(){ console.log("popover has been created!"); } * * onVisible - A function to be called after popover is visible. * eg) function(){ console.log("popover is visible!"); } * * disableHeader - Boolean that indicates if a header should not be used on parent listener. * eg) Truthy/Falsey values * * Methods: Example usage - $(selector).popover("methodName", argument1, argument2 ...); * [Internal] - Functions needed for setup/initialization. * _popoverInit - Internal function used to setup popover. * Arguments: options_config, popover_instance * _optionsPopoverInit - Internal function used to setup optionsPopover. * Arguments: options_config, popover_instance * [Public] * disableHeader - Function used to disable header for a popover instance. * Arguments: popover_instance * enableHeader - Function used to enable header for a popover instance. * Arguments: popover_instance * lockPopover - Function used to lock all popovers. Prevents popover from opening/closing. * Arguments: none * unlockPopover - Function used to unlock all popovers. * Arguments: none * addMenu - Function used to add a new menu. Menus can be accessed by all popover instances. * Arguments: id, title, contents * closePopover - Function used to close popover. * Arguments: none * [Private] - Note: Only use if you have to. * _getPopoverClass - Function used to return internal Popover class. * Arguments: none * * Triggers: Currently all events are namespaced under popover.* This may change in future versions. * popover.created - Fired when popup is created and placed in DOM. * popover.listenerClicked - Fired when root popup listener is clicked. * popover.action - Fired when a menu changes. * Arguments: DOM Element causing action. * popover.visible - Fired when popover is visible. * popover.updatePositions - Fired when left and top positions are updated. * popover.resize - Fired when popover is resized. * popover.closing - Fired before popover closes. * popover.setContent - Fired after popover's contenet is set. ================================================================================================================*/ var methods = { _init: function(options, popover) { //Theme modifiers if(typeof(options.backgroundColor) !== 'undefined'){ Popover.setBackgroundColor(options.backgroundColor); } if(typeof(options.fontColor) !== 'undefined'){ Popover.setFontColor(options.fontColor); } if(typeof(options.borderColor) !== 'undefined'){ Popover.setBorderColor(options.borderColor); } //Functionality modifiers //TODO: Rename disableBackButton option. if(typeof(options.disableBackButton) !== "undefined"){ if(options.disableBackButton === true){ popover.disableBackButton(); }else if(options.disableBackButton === false){ popover.enableBackButton(); } } if(typeof(options.enableBackButton) !== "undefined"){ if(options.enableBackButton === true){ popover.enableBackButton(); }else if(options.enableBackButton === false){ popover.disableBackButton(); } } if(typeof(options.disableHeader) !== 'undefined'){ if(options.disableHeader === true){ popover.disableHeader(); }else if(options.disableHeader === false){ popover.enableHeader(); } } if(typeof(options.keepData) !== 'undefined'){ popover.keepData(options.keepData); } if(typeof(options.childToAppend) !== 'undefined'){ popover.childToAppend = options.childToAppend; } //Callbacks if(typeof(options.onCreate) !== 'undefined'){ popover._onCreate = options.onCreate; } if(typeof(options.onVisible) !== 'undefined'){ popover._onVisible = options.onVisible; } Popover.addMenu(options.id, options.title, options.contents); }, _popoverInit: function(options) { var popover = new Popover(this.selector); methods._init(options, popover); return popover; }, _optionsPopoverInit: function (options) { var popover = new OptionsPopover(this.selector); methods._init(options, popover); return popover; }, //Requires instance to be passed. disableHeader: function(popover) { popover.disableHeader(); }, //Requires instance to be passed. enableHeader: function(popover) { popover.enableHeader(); }, //Static functions lockPopover: function() { Popover.lockPopover(); }, unlockPopover: function() { Popover.unlockPopover(); }, addMenu: function (menu) { Popover.addMenu(menu.id, menu.title, menu.contents); }, closePopover: function () { Popover.closePopover(); }, _getPopoverClass: function() { return Popover; } }; $.fn.optionsPopover = function (method) { // Create some defaults, extending them with any options that were provided //var settings = $.extend({}, options); // Method calling logic if (methods[method]) { return methods[ method ].apply(this, Array.prototype.slice.call(arguments, 1)); } else if (typeof method === 'object' || !method) { return methods._optionsPopoverInit.apply(this, arguments); } else { $.error('Method ' + method + ' does not exist on jQuery.optionsPopover'); } return this.each(function () {}); }; $.fn.popover = function (method) { // Create some defaults, extending them with any options that were provided //var settings = $.extend({}, options); // Method calling logic if (methods[method]) { return methods[ method ].apply(this, Array.prototype.slice.call(arguments, 1)); } else if (typeof method === 'object' || !method) { return methods._popoverInit.apply(this, arguments); } else { $.error('Method ' + method + ' does not exist on jQuery.popover'); } return this.each(function () {}); }; //////////////////////////////////////////////////////////// // Popover Block //////////////////////////////////////////////////////////// /** Popover CONSTRUCTOR **/ function Popover(popoverListener) { this.constructor = Popover; //Set this popover's number and increment Popover count. this.popoverNumber = ++Popover.popoverNum; //Class added to detect clicks on primary buttons triggering popovers. this.popoverListenerID = "popoverListener"+this.popoverNumber; this.isHeaderDisabled = true; this.isDataKept = false; this.hasBeenOpened = false; var thisPopover = this; var listenerElements = $(popoverListener); listenerElements.addClass(this.popoverListenerID); listenerElements.css("cursor", "pointer"); listenerElements.click(function (e) { thisPopover.toggleVisible(e, $(this)); $(document).trigger("popover.listenerClicked"); }); } Popover.prototype.disableHeader = function() { this.isHeaderDisabled = true; }; Popover.prototype.enableHeader = function() { this.isHeaderDisabled = false; }; Popover.prototype.disablePopover = function() { this.isDisabled = true; }; Popover.prototype.enablePopover = function() { this.isDisabled = false; }; Popover.prototype.keepData = function(bool){ this.isDataKept = bool; }; Popover.prototype.appendChild = function(){ var child = this.childToAppend; if(!child)return; $("#popoverContent")[0].appendChild(child); }; Popover.prototype.toggleVisible = function (e, clicked) { Popover.lastPopoverClicked = this; var clickedDiv = $(clicked); if (!clickedDiv) { console.log("ERROR: No element clicked!"); return; } var popoverWrapperDiv = $("#popoverWrapper"); if (popoverWrapperDiv.length === 0) { //console.log("Popover not initialized; initializing."); popoverWrapperDiv = this.createPopover(); if (popoverWrapperDiv.length === 0) { console.log("ERROR: Failed to create Popover!"); return; } } //TODO: In the future, add passed id to selected div's data-* or add specific class. var id = clickedDiv.attr("id"); var identifierList = clickedDiv.attr('class').split(/\s+/); //NOTE: identifierList contains the clicked element's id and class names. This is used to find its // associated menu. The next version will have a specialized field to indicate this. identifierList.push(id); //console.log("List: "+identifierList); //TODO: Fix repetition. if ($("#popover").is(":visible") && Popover.lastElementClick) { if (clickedDiv.is("#" + Popover.lastElementClick)) { console.log("Clicked on same element!"); console.log("Last clicked: " + Popover.lastElementClick); Popover.closePopover(); return; } console.log("Clicked on different element!"); Popover.closePopover(); } //Blocking statement that waits until popover closing animation is complete. $("#popover").promise().done(function () {}); //If popover is locked, don't continue actions. if(Popover.isLocked||this.isDisabled)return; //Update content this.populate(identifierList); clickedDiv.trigger("popover.action", clickedDiv); if(Popover.backgroundColor){ $("#popoverHeader").css("backgroundColor", Popover.backgroundColor); $("#popoverContent").css("backgroundColor", Popover.backgroundColor); } if(Popover.fontColor){ $("#popover").css("color", Popover.fontColor); //TODO: Trigger color change event and move to OptionsPopover. $("#popover a").css("color", Popover.fontColor); } if(Popover.borderColor){ $("#popoverHeader").css("border-color", Popover.borderColor); $("#popoverContent").css("border-color", Popover.borderColor); $(".popoverContentRow").css("border-color", Popover.borderColor); } //Make popover visible $("#popover").stop(false, true).fadeIn('fast'); $("#popoverWrapper").css("visibility", "visible"); $("#popover").promise().done(function () {}); popoverWrapperDiv.trigger("popover.visible"); if(this._onVisible){ //console.log("LOG: Executing onVisible callback."); this._onVisible(); } if((this.isDataKept && !this.hasBeenOpened) || (!this.isDataKept)){ var child = this.childToAppend; if(child){ this.appendChild(child); } } this.hasBeenOpened = true; //Update left, right and caret positions for popover. //NOTE: Must be called after popover.visible event, in order to trigger jspScrollPane update. Popover.updatePositions(clickedDiv); Popover.lastElementClick = clickedDiv.attr("id"); }; Popover.updatePositions = function(target){ Popover.updateTopPosition(target); Popover.updateLeftPosition(target); $(document).trigger("popover.updatePositions"); }; Popover.updateTopPosition = function(target){ var top = Popover.getTop(target); $("#popoverWrapper").css("padding-top", top + "px"); }; Popover.updateLeftPosition = function(target){ var offset = Popover.getLeft(target); $("#popoverWrapper").css("left", offset.popoverLeft); Popover.setCaretPosition(offset.targetLeft - offset.popoverLeft + Popover.padding); }; //Function returns the left offset of the popover and target element. Popover.getLeft = function (target) { var popoverWrapperDiv = $("#popoverWrapper"); Popover.currentTarget = target; var targetLeft = target.offset().left + target.outerWidth() / 2; var rightOffset = targetLeft + popoverWrapperDiv.outerWidth() / 2; var offset = targetLeft - popoverWrapperDiv.outerWidth() / 2 + Popover.padding + 1; var windowWidth = $(window).width(); Popover.offScreenX = false; if (offset < 0) { Popover.offScreenX = true; offset = Popover.padding; } else if (rightOffset > windowWidth) { Popover.offScreenX = true; offset = windowWidth - popoverWrapperDiv.outerWidth(); } //Returns left offset of popover from window. return {targetLeft: targetLeft, popoverLeft: offset}; }; Popover.getTop = function(target){ var caretHeight = $("#popoverArrow").height(); //TODO: Make more readable. //If absolute position from mobile css, don't offset from scroll. var scrollTop = ($("#popoverWrapper").css("position")==="absolute")?0:$(window).scrollTop(); var targetTop = target.offset().top - scrollTop; var targetBottom = targetTop + target.outerHeight(); var popoverTop = targetBottom + caretHeight; var windowHeight = $(window).height(); var popoverContentHeight = $("#popoverContent").height(); var popoverHeight = popoverContentHeight + $("#popoverHeader").outerHeight() + caretHeight; Popover.above = false; Popover.offScreenY = false; //If popover is past the bottom of the screen. //else if popover is above the top of the screen. if (windowHeight < targetBottom + popoverHeight) { Popover.offScreenY = true; //If there is room above, move popover above target //else keep popover bottom at bottom of screen. if(targetTop >= popoverHeight){ popoverTop = targetTop - popoverHeight; Popover.above = true; }else{ popoverTop = windowHeight - popoverHeight; } } else if (popoverTop < 0) { Popover.offScreenY = true; popoverTop = Popover.padding + caretHeight; } /* //Debug logs console.log("------------------------------------------------------------"); console.log("Caret Height: " + caretHeight); console.log("TargetTop: " + targetTop); console.log("Popover Cont Height: " + popoverContentHeight); console.log("Cont Height: " + $("#popoverContent").height()); console.log("Header Height: " + $("#popoverHeader").outerHeight()); console.log("targetBottom: " + targetBottom); console.log("popoverHeight: " + popoverHeight); console.log("popoverBottom: " + (targetBottom + popoverHeight)); console.log("Popover Height: " + $("#popover").height()); console.log("PopoverWrapper Height: " + $("#popoverWrapper").height()); console.log("PopoverWrapper2 Height: " + $("#popoverWrapper").height(true)); console.log("popoverTop: " + popoverTop); console.log("windowHeight: " + windowHeight); console.log("offScreenY: " + Popover.offScreenY); console.log("Popover.above: " + Popover.above); console.log("\n"); */ return popoverTop; }; Popover.setCaretPosition = function(offset){ //console.log("LOG: Setting caret position."); var caretPos = "50%"; var caret = $("#popoverArrow"); if (Popover.offScreenX) { caretPos = offset; } //Moves carrot on popover div. caret.css("left", caretPos); //console.log("LOG: Popover.above: "+Popover.above); if(Popover.above){ var popoverHeight = $("#popoverContent").outerHeight() - 4; $("#popoverArrow").css("margin-top", popoverHeight+"px") .addClass("flipArrow") .html("▼"); }else{ $("#popoverArrow").css("margin-top", "") .removeClass("flipArrow") .html("▲"); } Popover.caretLeftOffset = caretPos; }; // createPopover: Prepends popover to dom Popover.prototype.createPopover = function () { //Creates popover div that will be populated in the future. var popoverWrapperDiv = $(document.createElement("div")); popoverWrapperDiv.attr("id", "popoverWrapper"); var s = "
" + "
" + "" + "
" + "
" + "
" + "
"; popoverWrapperDiv.html(s); popoverWrapperDiv.find("#popover").css("display", "none"); //Appends created div to page. $("body").prepend(popoverWrapperDiv); //Window resize listener to check if popover is off screen. var popoverContentWrapperDiv = $("#popoverContentWrapper"); $(window).on('resize', function () { if ($("#popover").is(":visible")) { Popover.updatePositions(Popover.currentTarget); } var popoverWrapperDiv = $("#popoverWrapper"); if(popoverWrapperDiv.css("position")==="absolute"){ popoverWrapperDiv.css("height", $(document).height()); }else{ popoverWrapperDiv.css("height", ""); } popoverContentWrapperDiv.trigger("popover.resize"); } ); //Click listener to detect clicks outside of popover $('html') .on('click touchend', function (e) { var clicked = $(e.target); //TODO: Return if not visible. var popoverHeaderLen = clicked.parents("#popoverHeader").length + clicked.is("#popoverHeader") ? 1 : 0; var popoverContentLen = (clicked.parents("#popoverContentWrapper").length && !clicked.parent().is("#popoverContentWrapper")) ? 1 : 0; var isListener = clicked.parents("."+Popover.lastPopoverClicked.popoverListenerID).length + clicked.is("."+Popover.lastPopoverClicked.popoverListenerID) ? 1 : 0; if (popoverHeaderLen === 0 && popoverContentLen === 0 && isListener === 0) { Popover.closePopover(); } } ); popoverContentWrapperDiv.trigger("popover.created"); if(this._onCreate)this._onCreate(); //Function also returns the popover div for ease of use. return popoverWrapperDiv; }; //Closes the popover Popover.closePopover = function () { if(Popover.isLocked)return; Popover.lastElementClick = null; $(document).trigger("popover.closing"); Popover.history = []; $("#popover").stop(false, true).fadeOut('fast'); $("#popoverWrapper").css("visibility", "hidden"); }; Popover.getAction = function () { return $("#currentPopoverAction").html(); }; Popover.setAction = function (id) { $("#currentPopoverAction").html(id); }; Popover.prototype.disableBackButton = function(){ this.isBackEnabled = false; }; Popover.prototype.enableBackButton = function(){ this.isBackEnabled = true; }; Popover.prototype.previousPopover = function(){ Popover.history.pop(); if (Popover.history.length <= 0) { Popover.closePopover(); return; } var menu = Popover.history[Popover.history.length - 1]; this.populateByMenu(menu); }; //Public setter function for static var title and sets title of the html popover element. Popover.setTitle = function (t) { Popover.title = t; $("#popoverTitle").html(t); }; // Public getter function that returns a popover menu. // Returns: Popover menu object if found, null if not. // Arguments: id - id of menu to lookup Popover.getMenu = function (id) { //Searches for a popover data object by the id passed, returns data object if found. var i; for (i = 0; i < Popover.menus.length; i += 1) { //console.log("LOG: getMenu - Popover.menus["+i+"]: "+Popover.menus[i].id); if (Popover.menus[i].id === id) { return Popover.menus[i]; } } //Null result returned if popover data object is not found. //console.log("LOG: getMenu - No data found, returning null."); return null; }; Popover.addMenu = function (id, title, contents) { Popover.menus.push({'id': id, 'title': title, 'contents': contents}); }; Popover.prototype.populateByMenu = function(menu){ $(document).trigger('popover.populating'); this.lastContentHeight = Popover.getPopoverContentHeight(); if(!this.isDataKept){ this.clearData(); } //If data is kept, header and other content will still be in dom, so don't do either. if(!this.isHeaderDisabled && !this.isDataKept) { this.insertHeader(); }else{ this.removeHeader(); } var popoverDisplay = $("#popover").css("display"); if(!this.isDataKept || !this.hasBeenOpened)this.setData(menu); this.currentContentHeight = Popover.getPopoverContentHeight(); if(Popover.above && popoverDisplay!=="none"){ var oldPopoverTop = parseInt($("#popoverWrapper").css("padding-top"), 10); var contentHeightDelta = this.currentContentHeight - this.lastContentHeight; var popoverTop = oldPopoverTop - (contentHeightDelta); $("#popoverWrapper").css("padding-top", popoverTop + "px"); Popover.setCaretPosition(Popover.caretLeftOffset); } return true; }; //Public void function that populates setTitle and setContent with data found by id passed. Popover.prototype.populate = function(identifierList){ //console.log(identifierList); var newMenu = null; var i=0; for(i; i" + "" + ""; $("#popoverContentWrapper").before(header); //Create back button //Don't create back button or listener if disabled. if(this.isBackEnabled){ //console.log("LOG: Creating back button."); var thisPopover = this; $("#popoverHeader").prepend(""); $("#popoverBack").on("click", function () { thisPopover.previousPopover(); }); } //Click listener for popover close button. $("#popoverClose").on("click", function () { Popover.closePopover(); }); $("#popoverContent").css("paddingTop", "47px"); }; Popover.prototype.removeHeader = function() { $("#popoverBack").off("click"); $("#popoverClose").off("click"); $("#popoverHeader").remove(); $("#popoverContent").css("paddingTop", ""); }; Popover.prototype.clearData = function (){ this.removeHeader(); $("#popoverTitle").html(""); $("#popoverContent").html(""); }; Popover.prototype.setData = function (data) { Popover.setAction(data.id); Popover.setTitle(data.title); Popover.setContent(data.contents); }; Popover.prototype.replaceMenu = function (menu, newMenu){ var property; for(property in menu){ delete menu[property]; } for(property in newMenu){ menu[property] = newMenu[property]; } }; //Public setter function for private var content and sets content of the html popover element. Popover.setContent = function (cont) { Popover.content = cont; //$("#popoverContentWrapper").data('jsp').getContentPane().find("#popoverContent").html(cont); //Note: Popover content set without using jscrollpane api. $("#popoverContent").html(cont); //Note: Removed 'this' reference passed. $("#popoverContentWrapper").trigger("popover.setContent"); }; /** STATIC VARIABLES **/ Popover.popoverNum = 0; Popover.lastElementClick = null; Popover.currentTarget = null; Popover.title = ""; Popover.content = ""; Popover.menus = []; Popover.history = []; Popover.backgroundColor = null; Popover.fontColor = null; Popover.borderColor = null; Popover.padding = 3; Popover.offScreenX = false; Popover.offScreenY = false; Popover.isLocked = false; Popover.above = false; Popover.caretLeftOffset = "50%"; Popover.lastPopoverClicked = null; /** STATIC FUNCTIONS **/ Popover.setBackgroundColor = function(color){ Popover.backgroundColor = color; }; Popover.setFontColor = function(color){ Popover.fontColor = color; }; Popover.setBorderColor = function(color){ Popover.borderColor = color; }; Popover.lockPopover = function(){ Popover.isLocked = true; }; Popover.unlockPopover = function(){ Popover.isLocked = false; }; //////////////////////////////////////////////////////////// // OptionsPopover Block //////////////////////////////////////////////////////////// /** OptionsPopover CONSTRUCTOR **/ function OptionsPopover(popoverListener){ //Super constructor call. Popover.apply(this, [popoverListener]); this.constructor = OptionsPopover; this.superConstructor = Popover; this.isHeaderDisabled = false; this.isBackEnabled = true; if(!OptionsPopover.hasRun){ this.init(); OptionsPopover.hasRun = true; } } //Inherit Popover OptionsPopover.prototype = new Popover(); OptionsPopover.constructor = OptionsPopover; /** STATIC VARIABLES **/ OptionsPopover.hasRun = false; /** PROTOTYPE FUNCTIONS **/ //Run-once function for listeners OptionsPopover.prototype.init = function(){ $(document) .on('touchstart mousedown', '#popover a', function () { $(this).css({backgroundColor: "#488FCD"}); }) .on('touchend mouseup mouseout', '#popover a', function () { $(this).css({backgroundColor: ""}); }) .on('click', '.popoverContentRow', function () { var newId = []; newId.push($(this).attr('id')); if ($(this).hasClass("popoverEvent")) { $(this).trigger("popover.action", $(this)); } var keepOpen = Popover.lastPopoverClicked.populate(newId); if (!keepOpen) Popover.closePopover(); }); }; OptionsPopover.prototype.setData = function (data) { var contArray = data.contents; var c = ""; var i; for (i = 0; i < contArray.length; i++) { var lastElement = ""; var popoverEvent = ""; var menuId = ""; var menuUrl = ""; if (i === contArray.length - 1) { lastElement = " last"; } //Links are given the popoverEvent class if no url passed. If link has popoverEvent, // event is fired based on currentPopoverAction. if (typeof(contArray[i].id) !== 'undefined') { menuId = " id='" + contArray[i].id + "'"; } if (typeof(contArray[i].url) !== 'undefined') { menuUrl = " href='" + contArray[i].url + "'"; } else { popoverEvent = " popoverEvent"; } c += "" + contArray[i].name + ""; } Popover.setAction(data.id); Popover.setTitle(data.title); Popover.setContent(c); }; }(window.jQuery);