UNPKG

vpn.email

Version:
427 lines (332 loc) 20.3 kB
$.widget("metro.appbar", { version: "3.0.0", options: { flexstyle: "app-bar-menu", //app-bar-menu | YOUR_OWN class for the pull flexmenu, basic support for "sidebar2" are integrated in the appbar.less file flexclean: false, //true | false. if set all entries except the no-flexible ones will removed flextolerance: 3 //in px. if set the freespace is runnig out a little bit earlier, so floats //and not no-wrap elements have no chance to wrap. help for rounding errors also }, _create: function () { var that = this, element = this.element, o = this.options; $.each(element.data(), function (key, value) { if (key in o) { try { o[key] = $.parseJSON(value); } catch (e) { o[key] = value; } } }); this._initBar(); element.data('appbar', this); }, _calculateFreeSpace: function () { var that = this, element = this.element, o = this.options; var menusParentWidth = 0, childrenWidth = 0, children; var freeSpace; //get the overall free space from the wrapping parent of the menus menusParentWidth = $(that.menusParent).width(); //get the width of all visible children children = $(that.menusParent).children(":visible").not(".app-bar-pullmenu"); //margin support: because there could be margins between elements, we do not summarize the width up with a one liner //but calculate width of all children in an intelligent way, we takte the left offsett of the first element and right offset of the right element //for that we have to support float left and right too: //float left and right support: we can not be sure that the first element in dom is on the left and the last is on the right //right floated // - sort the children as the user see them //sort the children as the user see them according to the css float var childrenLeftFloated = []; var childrenRightFloated = []; var childrenAsUsual = []; var floatState; for (var i = 0, len = children.length; i < len; i++) { floatState = $(children[i]).css("float"); switch (floatState) { case "left": childrenLeftFloated.push(children[i]); break; case "right": childrenRightFloated.push(children[i]); break; default: childrenAsUsual.push(children[i]); } } //right floats are from right to left childrenRightFloated.reverse(); //=== build up the new children jquery object === //join the left, right and normal children children = new Array(); children = childrenLeftFloated.concat(childrenAsUsual, childrenRightFloated); //convert the array to jquery object again children = $(children); //=== calculate the width of the elements with margin support === //adds the left margin dedicated to the first child childrenWidth += parseInt($(children).first().css("margin-left")); //walk trough the children and add the size, for (var i = 0, len = children.length - 1; i <= len; i++) { childrenWidth += $(children[i]).outerWidth(); if (i !== len) { //the highest margin between two elements counts childrenWidth += Math.max( parseInt($(children[i]).css("margin-right")), parseInt($(children[i + 1]).css("margin-left")) ); } } //the right margin for the right child childrenWidth += parseInt($(children[len]).css("margin-right")); //now we have all data for calculation. Yippie-Ya-Yeah, Schweinebacke!! (much cooler German translation of B. W. Yippie-Ya-Yeah, Motherf***er) freeSpace = menusParentWidth - childrenWidth; //writing the data we found out to the element's data that.freeSpace = freeSpace; //not used space within the parent(mostly the appbar itself) that.childrenWidth = childrenWidth; //the total width of the children that.menusParentWidth = menusParentWidth; //the width without padding or something return freeSpace; }, _originIndexMove: function(menu, child) { //find all children which are lower than we var flexChildren = $(menu).children().filter(function () { return parseInt($(this).attr("data-flexorderorigin")) < parseInt($(child).attr("data-flexorderorigin")); }); if (flexChildren.length > 0) { //because we are greater, we set it after the childern which are lower $(flexChildren).last().after(child); } else { //find all children which are greater than we are flexChildren = $(menu).children().filter(function () { return parseInt($(this).attr("data-flexorderorigin")) > parseInt($(child).attr("data-flexorderorigin")); }); if (flexChildren.length > 0) { //because we are lower, we set us before the childern which are greater $(flexChildren).first().before(child); } else { //we have no children, just append it $(menu).append(child); } } }, _moveMenuEntry: function (direction) { var that = this, element = this.element, o = this.options; direction = direction || "toPullMenu"; // "fromPullMenu" is also an option if (direction === "toPullMenu") { //get next candidate which could be moved to the pullmenu, in fact the last which not have a mark as pullmenu-entry var nextToHide = $(that.allMenuEntries).not(".app-bar-pullmenu-entry").last(); if (nextToHide.length === 0) { //nothing left, we have nothing to do return false; } //find out in which menubar we are located in var topMenu = $(nextToHide).parent(); //this is only a appbar-menu not the appbar itself //find out where we have to go var topMenuIndex = $(that.flexVisibles).index($(nextToHide).parent()); var pullMenuBar = $(that.pullMenu).find(".app-bar-pullmenubar").eq(topMenuIndex); //TODO: Make the class app-bar-menu configurable - perhaps sidebar that._originIndexMove(pullMenuBar, nextToHide); //move it to the pullmenu // if ($(topMenu).is("[data-flexdirection='reverse']")) {//data-flexdirection="reverse" support // $(nextToHide).appendTo(pullMenuBar); // } else { //normal way // $(nextToHide).prependTo(pullMenuBar); // } //mark the entry as a entry of the pullmenu $(nextToHide).addClass("app-bar-pullmenu-entry"); //the menubar is initiated with the hidden class, so we do not see empty pullmenubars, we must unhide them //it does not matter, if we see it already, we do it always: $(pullMenuBar).removeClass("hidden") .show(); //in case there are no more entries in the top menu bar we can hide it if ($(topMenu).children().length === 0) { $(topMenu).addClass("hidden"); } //we show the pullbutton now $(that.pullButton).show(); return nextToHide; } else if (direction === "fromPullMenu") { //get next candidate which could be moved to the topbar menu, in fact the first which is still marked as pullmenu-entry var nextToShow = $(that.allMenuEntries).filter(".app-bar-pullmenu-entry").first(); //find out in which pullmenu we are located in var pullMenuBar = $(nextToShow).parent(); //only one single menu, not the whole thing //find out where we have to go var topMenuIndex = $(pullMenuBar).index(); //it is the same structur as that.flexVisibles, so we can use the simple index var topMenu = $(that.flexVisibles).eq(topMenuIndex); $(topMenu).removeClass("hidden"); //remove the mark as a entry of the pullmenu and move it to the normal top menu $(nextToShow).removeClass("app-bar-pullmenu-entry"); //cosider the flexorder //walk trough the children in topMenu and find out what we must do //find all children which are lower than we that._originIndexMove(topMenu, nextToShow); //in case there are no more entries left, we can hide the pullbar menu from this entry if ($(pullMenuBar).children().length === 0) { $(pullMenuBar).addClass("hidden") .hide(); } //in case we have no more menus in the pullbar area, we hide the pullbar thing if ($(that.pullMenu).children(".app-bar-pullmenubar").not(".hidden").length === 0) { $(that.pullMenu).hide().addClass("hidden"); $(that.pullButton).hide(); } if (nextToShow.length === 0) { //nothing left, we have nothing to do return false; } return nextToShow; } }, _checkMenuEntries: function () { var that = this, element = this.element, o = this.options; var forceEndLoop = false; for (var maxLoop = 0, maxLoopLen = that.allMenuEntries.length; maxLoop < maxLoopLen; maxLoop++) { //we do nothing with this, we could use while(true) but there is a danger of infinite loops //calculate the empty space within the appbar we can use for hidden children that._calculateFreeSpace(); var freeSpace = that.freeSpace; if (freeSpace < o.flextolerance || o.flexclean) { //3px is tolerance and to be faster than the wrapping. TODO: make this configurable //no space left, we hide a menu entry now //move the menu entry to the pullbar and check if there are more menuentries left if (!(that._moveMenuEntry("toPullMenu"))) { //nothing left to hide break; } else { //we moved successfully, perhaps we can hide more entries, we recheck the appbar, //remember, we are in a endless loop, which checks this for us if (!forceEndLoop) { continue; } } } else { //we have space here, we try to get more entries there //check if there is something to do if (!(that._moveMenuEntry("fromPullMenu"))) { //nothing left to show break; } else { forceEndLoop = true; continue; } } //we continue manually. if we reach the end of the loop we end this better so we do not produce infinite loop accidentally break; } }, resize: function () { var that = this, element = this.element, o = this.options; if (that.initiatedAsFlex) { this._checkMenuEntries(); } }, _initBar: function () { var that = this, element = this.element, o = this.options; that.lastFlexAction = undefined; that.pullButton = $(element).find('.app-bar-pullbutton'); var menus = $(element).find('.app-bar-menu'); that.initiatedAsFlex = false; //we change it later in the code - conditionally o.flexclean = $(element).is("[data-flexclean='true']") || o.flexclean; o.flexstyle = $(element).attr("data-flexstyle") || o.flexstyle; var flexVisible, menuEntries; //temporarly used vars that.flexVisibles = $(); //the menus which are directly in the appbar that.allMenuEntries = $(); //all menu entries in a sorted order that.menusParent = $(); //common parent from the menus, which can but do not need to be this.element. We get the max width from it that.pullMenu = $(); if (menus.length > 0 && $(element).is(":not('.no-flexible')")) { //strip off all .no-flexible menus that.flexVisibles = $(menus).not(".no-flexible"); if (that.flexVisibles.length > 0) { that.initiatedAsFlex = true; //sort the menus according to the data-flexorder attribute that.flexVisibles.sort(function (a, b) { var aValue = (parseInt($(a).data("flexorder")) || $(a).index() + 1); var bValue = (parseInt($(b).data("flexorder")) || $(b).index() + 1); return aValue - bValue; }); //get all children in a sorted order according to the data-flexorder attribute $(that.flexVisibles).each(function () { flexVisible = this; menuEntries = $(flexVisible).children(); //give all menuEntries a flexorder which have not one and save the original order $(menuEntries).each(function () { $(this).attr("data-flexorderorigin", $(this).index()); if(!($(this).is("[data-flexorder]"))) { $(this).attr("data-flexorder", $(this).index() + 1); } }); menuEntries.sort(function (a, b) { var aValue = parseInt($(a).data("flexorder")); var bValue = parseInt($(b).data("flexorder")); return aValue - bValue; }); //data-flexdirection="reverse" support if ($(flexVisible).is("[data-flexdirection='reverse']")) { menuEntries.reverse(); } $.merge(that.allMenuEntries, $(menuEntries).not(".no-flexible")); //strip off all .no-flexible elements }); //find the parent, which contains all menus that.menusParent = $(element).find(".app-bar-menu").first().parent(); // === create a pull down button + pull menu === //check if a pulldown button already exists, if not we create one if (!(that.pullButton.length > 0)) { //DOC: We can create a display:none button, if we want to force to not show a pull button that.pullButton = $('<div class="app-bar-pullbutton automatic"></div>'); $(that.menusParent).append(that.pullButton); } //create a pullmenu that.pullMenu = $('<nav class="app-bar-pullmenu hidden" />'); //create menubars within the pullmenu that.flexVisibles.each(function () { $(that.pullMenu).append($('<ul class="app-bar-pullmenubar hidden ' + o.flexstyle + '" />')); }); // WORKAROUND: this is because a :after:before clearfix for the pullmenu do not work for some reason //position: absolute does not work if we do not break the float. another pure css solution should be written in the appbar.less //after that remove this line $(that.menusParent).append($('<div class="clearfix" style="width: 0;">')); //----------- $(that.pullMenu).addClass("flexstyle-" + o.flexstyle); $(that.menusParent).append(that.pullMenu); //check for the first time the menu entries /hide them if needed, etc. that._checkMenuEntries(); //=== EVENTS ================================================= //activate the click event for the pull button $(that.pullButton).on("click", function () { //who am i? that = $(this).closest("[data-role=appbar]").data("appbar"); //we show /hide the pullmenu if ($(that.pullMenu).is(":hidden")) { $(that.pullMenu).show(); $(that.pullMenu).find(".app-bar-pullmenubar") .hide().not(".hidden").slideDown("fast"); } else { $(that.pullMenu).find(".app-bar-pullmenubar") .not(".hidden").show().slideUp("fast", function () { $(that.pullMenu).hide(); }); } }); //we have to calculate everything new, if the user resizes or zooms the window $(window).resize(function () { $("[data-role=appbar]:not(.no-flexible)").each(function () { $(this).data("appbar").resize(); }); }); //because fonts(also icon-fonts) are often loaded async after the page has loaded and this script walked through already, //we have to check again after these elements loaded. Because there is no way to observe only specific elements, we do it for the window $(window).load(function () { $("[data-role=appbar]:not(.no-flexible)").each(function () { $(this).data("appbar").resize(); }); }); //pictures (or other outside stuff was loaded - pictures are also often loaded async or have a lazy load or are injected after a while. //a picture can change a size of the element from the appbar, so we must recheck it again. $("[data-role=appbar]:not(.no-flexible) [src]").on("load", function () { //who am i? var appbar = $(this).closest("[data-role=appbar]").data("appbar"); appbar.resize(); }); } } }, _destroy: function () { }, _setOption: function (key, value) { this._super('_setOption', key, value); } });