!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 = "