"use strict"; /* rathar then using dialog from jqueryUI etc I created this simple plugin below it might be good idea to move it to another js file once its tested, minimise etc it requires jqueryUI so it can be dragged and uses HTML structure as shown in html */ $(document).ready(function(){ (function($) { // make dialog draggable, attach listeners etc // requires jqueryUI draggable component var currentPosition=null; var fullyOpen=false; // make draggable $("#dialog > .dialogPanel").draggable({ handle: ".dialogHeader", containment: ".cloak" }); // make resizeable, only bottom. jqueryUI required $("#dialog > .dialogPanel").resizable({ handles: "s", start: function(event, ui) { $("#dialog > .dialogPanel").addClass("active"); currentPosition=ui.position; }, resize: function( event, ui) { // resize main content as well at the same time $("#dialog > .dialogPanel .mainContent").css("min-height",ui.size.height-140); $("#dialog > .dialogPanel").css({ left: currentPosition.left, top: currentPosition.top }); }, stop: function() { $("#dialog > .dialogPanel").removeClass("active"); } }); // make rhandle tall enough to it can be grabbed and dragged $("#dialog > .dialogPanel .ui-resizable-handle.ui-resizable-s").css({ "cursor":"s-resize", "position":"relative", "top":"6px", "height":"4px" }); // this assigns listeners to key presses. Need to be carefull later on what we trigerring and so here we check if the dialog is open and assign actions only when dialog is open $(document).keyup(function(e) { // make sure these are only working when dialog is actually open if(fullyOpen) { if (e.keyCode == 9) { // we need to check here if anything else is in focus e.g. when browsers URL has focus and user preses TAB then first focusable element in a page will get in focus rather then dialog itself. So we need to check if anything that has focus is actually part of the dialog itself and if not then simply put dialogs first element in focus. THis is different in between browsers and first element in page often still can be focused directly by tabbing from browsers URL but then next TAB should focus on dialog // Might redoit later with openDialog method, put some sort of check in therer and detect what gets focused outside of the dialog. Its edge case though if(!$(":focus").closest("#dialog").length) { $($('#dialog :tabbable')[0]).focus(); return; } } } }) $(document).keydown(function(e) { // make sure these are only working when dialog is actually open if(fullyOpen) { if (e.keyCode == 13) { $('#dialog .footer input').click(); } // enter if (e.keyCode == 27) { $('#dialog .footer .cancel').click(); } // esc } }); function insertHTML(settings) { // fill in mainCOntent with whatever the HTML needs to be $("#dialog .mainContent").html(""); $(settings.id).appendTo("#dialog .mainContent"); $(settings.id).fadeIn(300); $("#dialog .dialogHeader > h2").text(settings.title); // call after html inserted callback if(settings.onInsertHTML) settings.onInsertHTML(); } function openDialog(settings) { // size and position dialog $( "#dialog .dialogPanel").css("width",settings.width+"px"); $("#dialog .dialogPanel").css("left",$(window).width()/2-($("#dialog .dialogPanel").width()/2)); // make it invisible and 0 in height so it might be animated nicely to min of its height. This will help us avoid flashing of the panel initially before aniimation kicks in $( "#dialog .dialogPanel").css("visibility","hidden"); $( "#dialog .dialogPanel").css("height","0"); // the dialog biox is invisible now so fade it the whole dialog $("#dialog").fadeIn(300,function() { // store min height of the panel so we can use this value later var tempMinHeight=settings.height; // reset minHeight as this will otherwise block whatever height values have // also we need to make sure overflow is not visible when we animating panel later $( "#dialog .dialogPanel").css( { minHeight:0, overflow: "hidden" }); // now as panel is 0 in size we need to make it visible and then animate $( "#dialog .dialogPanel").css("visibility","visible"); $("#dialog .dialogPanel").animate({height: tempMinHeight},300, function() { // animation is finished now so make overflow of the panel visible now. this is needed tso the resizing bar can be positioned over the bottom border of the panel $( "#dialog .dialogPanel").css( { overflow: "visible" }); // we dont need height anymore so make it auto $( "#dialog .dialogPanel").css( "height", "auto"); // set min height of the panel via its options so "resizable" will take care of it and we dont have to manually check it ourselves $( "#dialog .dialogPanel").resizable( "option", "minHeight", tempMinHeight); // als make sure that even if the main content is too long or too short it fits in the panel $( "#dialog .dialogPanel .mainContent").css( "maxHeight", tempMinHeight-140); $( "#dialog .dialogPanel .mainContent").css( "minHeight", tempMinHeight-140); // paste html content insertHTML(settings); // now make all the fields in a form (if any) // insert footer buttons as specified in settings // this have to be dynamically added as their functionality differes for different dialogs e.g. help "OK" button might just close the dialog, but advanced search "SEARCH" button might submit ajax data and need callback $("#dialog .footer .buttons").html(""); $('%&').appendTo("#dialog .footer .buttons"); // assign listeners to newly added buttons $("#dialog .footer .buttons .cancel").click(function(e) { if(settings.onCancel) settings.onCancel(); closeDialog(); // cancel changing url # as this will reload the page in our framework e.preventDefault(); }); $("#dialog .footer .buttons input[type=button]").click(function(e) { if(settings.onOK) settings.onOK(); closeDialog(); }); fullyOpen=true; }); }); } function closeDialog() { $("#dialog").fadeOut(300, function() { // and move content of dialog to the end of body and make it invisible again $("#dialog .mainContent >*:first-child").appendTo("body").css("display","none"); fullyOpen=false; }); } $.fn.dialog = function(options) { // default settings var settings = $.extend( { 'onOpen': null, 'onCancel': null, 'onOK': null, 'onInsertHTML': null, 'width' : '638', // takes any value but 360 & 640 are the ones JT wants to stick with 'height' : '484', // same, takes any height 'id' : null, 'title' : 'Dialog', 'okButton' : "OK" }, options); return this.each(function() { $(this).click(function() { // reset dialog to its original position just in case someone went ctr+++ zoming the page and now dialog is not accessible as the top draggable heading is out of window $( "#dialog .dialogPanel").attr("style",""); $( "#dialog .dialogPanel .footer .buttons input[type=button]").val(settings.okButton); // call onOpen callback if(settings.onOpen) settings.onOpen(); // get the size of the dialog and open in correct size openDialog(settings); }); }); }; $(window).resize(function(e) { $("#dialog .dialogPanel").css("left",$(window).width()/2-($("#dialog .dialogPanel").width()/2)); }); })(jQuery); }); // plugin attaches scrollbar to element specified. scrollbar can be located anywhere in document and is defined as empty element // e.g.
// its enhanced jqueryUI scrollbar element which does basic scrolling but needed quite a bit of work to make it into scrollbar with handle that resizes accordingly, can change its size etc. // To work it also requires certain HTML structure with .scroll-content class element as the first child. (function($) { $.fn.customScrollBar=function(options,extra) { // default settings var settings = $.extend( { 'scrollPane': $(".contentContainer"), // scroll-pane so frame inside of which the div with another content will be moving 'width': 200 }, options); var returnValue=0; this.each(function() { // reset returnValue as it might have been previously set returnValue=0; // whole scrollbar thingy var t=$(this); // check if option passed to a plugin is a call for public function, if so call the function rather then attaching plugin if(typeof options == 'string') { switch(options) { case "setSize": { setSize.call(this,extra); return; } case "getProportion": { returnValue = getProportion.call(this); return; } } } // store for possible later use when we come back with options $.data(this,"scrollPane",settings.scrollPane); // public function returning proportion (floating number) rather than jquery object. this can be used outside of plugin to find out if plugin should be displayed etc. function getProportion() { // if scrollbar is not initialised there will be no data on the element then we have to get ignore the request. Dumb IE8 causing issues here hence the check if(!$.data(this,"scrollPane")) return; // window size changes so we should change scrollbar and proportionally handle size var scrollPane=$.data(this,"scrollPane"); var scrollContent = scrollPane.find("> .scroll-content"); return proportion=scrollPane.width()/scrollContent.width(); } function setSize(size) { // if scrollbar is not initialised there will be no data on the element then we have to get ignore the request. Dumb IE8 causing issues here hence the check if(!$.data(this,"scrollPane")) return; // window size changes so we should change scrollbar and proportionally handle size var scrollPane=$.data(this,"scrollPane"); var scrollContent = scrollPane.find("> .scroll-content"); var proportion=scrollPane.width()/scrollContent.width(); var initialScrollBarWidth=size; // get handle width for this size of scrollbar var handleWidth=initialScrollBarWidth*proportion; // if scrollbar is not needed cause contant is shorter than viewport then in here we simply fade it out. later might be a good idea to make it configurable via options so user could decide if the scrollbar should not show, or be inactive or something. for now we just fade it out if(proportion>1) { t.fadeOut(300); scrollContent.css( "margin-left", 0 ); return; } else if(!t.is(":visible")) t.fadeIn(300); // resize the size of scrollbar keeping handle size same var scrollBarWidth=initialScrollBarWidth-handleWidth; // after scrollbar is ready to go change the width of it. The reason is that handle when dragged will have value of its center point and depending on handle width, sides will stick on both sides when slider in min or max position. We changing clidebars size leaving handle size the same so it will look like handle is inside of the scrollbar t.width(scrollBarWidth).css("margin-left",(handleWidth/2)+"px"); // set handle size t.find( ".ui-slider-handle" ).css({ width: handleWidth, "margin-left": -handleWidth / 2 }); // rather then calling scrollbars "slide" method via call and then deal with ui vars inside of it we take care of updating content panel in here directly scrollContent.css( "margin-left", Math.round( t.slider("value") / 100 * ( scrollPane.width() - scrollContent.width()) ) + "px" ); } // frame, hole, window, viewport (call what you will) where the content will be showing inside of it var scrollPane=settings.scrollPane; //actual content to scroll var scrollContent = scrollPane.find("> .scroll-content"); // attach slider, this will create another anchor element inside of it that will act as handle var scrollbar = t.slider({ slide: function( event, ui ) { if ( scrollContent.width() > scrollPane.width() ) { scrollContent.css( "margin-left", Math.round( ui.value / 100 * ( scrollPane.width() - scrollContent.width()) ) + "px" ); } else { scrollContent.css( "margin-left", 0 ); } } }); // scrollbar is the whole area where the handle will slide. initially we set it to whatever width user specified via options so we can calculate width of the handle that should be displayed given that width. Once we got width of the handle we calculate new width of the scrollbar but handle width stays the same and does not change. We do it to avoid situation when center of the handle is in min/max position and the sides of the handle are sticking out of the area of scrollbar. Thats why we keep the handle size making scrollbar narrower. It will give impression that handle slides inside of scrollbar area but in fact area undern handle is shorter. If need different background color for area under handle then wrap scrollbar into separate container. oy its all hard to explain var initialScrollBarWidth=settings.width; // how much proportionally smaller is content pane (the viewport window) then the whole content? var proportion=scrollPane.width()/scrollContent.width(); // get handle width for this size of scrollbar var handleWidth=initialScrollBarWidth*proportion; // resize the size of scrollbar keeping handle size same var scrollBarWidth=initialScrollBarWidth-handleWidth; // after scrollbar is ready to go change the width of it. The reason is that handle when dragged will have value of its center point and depending on handle width, sides will stick on both sides when slider in min or max position. We changing scrollbar size leaving handle size the same so it will look like handle is inside of the scrollbar t.width(scrollBarWidth).css("margin-left",(handleWidth/2)+"px"); // set handle size t.find( ".ui-slider-handle" ).css({ width: handleWidth, "margin-left": -handleWidth / 2 }); // hide if all content can be displayed in viewport so theres nothing to scroll // later we can add option so user can control if in this situation scrollbar should not show, or might not be draggable or anything else really if(proportion>=1) t.hide(); }) return returnValue || this; } })(jQuery); $(document).ready(function(){ (function($) { $.fn.tabGuard = function(options) { // default settings var settings = $.extend( { 'onOpen': null, 'onCancel': null, 'onOK': null, 'width' : '638', // takes any value but 360 & 640 are the ones JT wants to stick with 'height' : '484', // same, takes any height 'id' : null, 'title' : 'Dialog', 'okButton' : "OK" }, options); return this.each(function() { /* these are flags used to track which field in specified container is in focus. We need to make sure that pressing TAB or SHIFT+TAB dosent focus on fieds outside of container so we need to check on keydown (NOT UP YET!) if the tab or shift+tab is ressed. If so then check if the field currently being in focus is the first or last in container. If so then we need to move the focus to first or last field in a form accordingly. This has to be done in keydown as otherwise browsers will focus url field or development panel or anything else in its chrome. Now, this is done in keydown, however we need keyup as in keydown the field being currently in focus (the one we navigating away from) is returned. We need to hook event in keyup now to put focus on to the field we want to navigate to. See the code for all logic but thats in a nutshell working of tabbing in between fields. */ // flags used to track focus on first and last fields var isFirstInFocus=false; var isLastInFocus=false; $(this).keyup(function(e) { // make sure these are only working when dialog is actually open if($(this).find(":focus")) { if (e.keyCode == 9) { // now one of the fields is in focus so in here we simply set flags for later use if($(this).find(':tabbable:first:focus').length) { isFirstInFocus=true; } else if($(this).find(':tabbable:last:focus').length) { isLastInFocus=true; } } } }) $(this).keydown(function(e) { // make sure these are only working when dialog is actually open if($(this).find(":focus")) { // now check tab and tab+shift if (e.keyCode == 9) { var tabbables = $(this).find(':tabbable'), first = tabbables.filter(':first'), last = tabbables.filter(':last'); if(e.shiftKey) { if(isFirstInFocus) { last.focus(1); isFirstInFocus=false; isLastInFocus=true; return false; } else if (e.target === first[0]) { isFirstInFocus=true; isLastInFocus=false; return false; } else { isFirstInFocus=false; isLastInFocus=false; } } else { if(isLastInFocus) { first.focus(1); isFirstInFocus=true; isLastInFocus=false; return false; } else if (e.target === last[0]) { isLastInFocus=true; isFirstInFocus=false; return false; } else { isFirstInFocus=false; isLastInFocus=false; } } } } }); }); }; })(jQuery); }); // plugin for custom drop down boxes (function($) { $.fn.multiDropDown=function(options) { // default settings var settings = $.extend( { 'editable': true, 'multiSelect': true, 'duplicatesAllowed': true, 'onSelect': function() {}, 'scrollingContainer': null, }, options); return this.each(function() { // check if option passed to a plugin is a call for public function, if so call the function rather then attaching plugin if(typeof options == 'string') { switch(options) { case "refresh": { refresh.call(this); return; } } } // public function that updates preselected values. Call it when preselected values change and need to update lozenges or otherwise selected value // also call it after the dropdown becomes visible and the values are preselected function refresh() { var t=$(this); var c=t.find("> .options"); var allLI=c.find("> div > ul > li"); var editableInput=t.find("> .selectedItems > .inputField > input"); // remove all selected items first t.find("> .selectedItems > ul >li").remove(); $.data(t[0],"displayedLozengesWidth",0); var displayedLozengesWidth=0; c.find(">div > ul > li").filter(".selected").each(function() { var selectedLI=this; // lozenge is being added // here get the content that needs to be put into lozenge. This might be some inside span, title attribute or something and might be different from the content of the LI as this might contain html, icons images etc var lozengeContent=$(this).find(">div").text(); if($.data(t[0],"multiDropDownSettings").multiSelect) { // add new lozenge to list of lozenges selected var newLozenge=t.find("> .selectedItems > ul").prepend('
  • '+lozengeContent+'
  • '); newLozenge=t.find("> .selectedItems > ul").find('li[data-selected-option='+$(selectedLI).data("selected-option")+']'); // we dont want to display this lozenge yet as we don't know if it will fit in with all the other lozenges in space provided by max-width in css // so we make new lozenge position absolute and make it invisible so its width can be measured without actually outputting it to the screen newLozenge.css({ position: "absolute", opacity: 0 }); // add width of new lozenge to total width of lozenges and check how much horizontal space all the lozenges take and if more than available display message how many is selected displayedLozengesWidth=displayedLozengesWidth+newLozenge.width(); $.data(t[0],"displayedLozengesWidth",displayedLozengesWidth); // check if this lozenge once added to sum of all lozenges width will be more than space available for our list of lozenges if(displayedLozengesWidth>parseInt(t.find("> .selectedItems > ul").css("max-width"))) { // all lozenges would take too much space so hide lizt of lozenges and add one in front of the list with message total // lozenges are hidden now so add new one with number and message // adjust the size of input field where user can type in stuff // this will haoppen simultaneously as animation for loznges plays so its mooth effect t.find("> .selectedItems > .inputField").width(t.find("> .selectedItems").width()-t.find("> .selectedItems > ul").width()); // check if total message is already displayed, if so change message only but not animate it anymore if(t.find("> .selectedItems > ul > .totalLozenges").length) { // here we simply change message t.find("> .selectedItems > ul > .totalLozenges").html('
    Total selected '+(t.find("> .selectedItems > ul > li").length-1)+'
    '); // -1 as there is still total lozenge in a list // adjust the size of input field where user can type in stuff // this will haoppen simultaneously as animation for loznges plays so its mooth effect t.find("> .selectedItems > .inputField").width(t.find("> .selectedItems").width()-t.find("> .selectedItems > ul").width()); modyfyingSelected=false; } else { // hide all current lozenges t.find("> .selectedItems > ul > li").hide(); // add new item to front of the list with total message t.find("> .selectedItems > ul").prepend('
  • Total selected '+(t.find("> .selectedItems > ul > li").length)+"
  • "); // adjust the size of input field where user can type in stuff // this will haoppen simultaneously as animation for loznges plays so its mooth effect t.find("> .selectedItems > .inputField").width(t.find("> .selectedItems").width()-t.find("> .selectedItems > ul> .totalLozenges").width()); t.find("> .selectedItems > ul > .totalLozenges").hide().show(); } // t.find("> .selectedItems > ul > .totalLozenges").text(t.find("> .selectedItems > ul > li").length-1).show(300); } else { // we need it to display now so make it static again and later animate in newLozenge.css({ position: "static", opacity: 1 }); // adjust the size of input field where user can type in stuff // this will haoppen simultaneously as animation for loznges plays so its mooth effect t.find("> .selectedItems > .inputField").width(t.find("> .selectedItems").width()-t.find("> .selectedItems > ul").width()); // animate new lozenge in t.find("> .selectedItems > ul > li:first-child").hide().show(); } //////console.log(displayedLozengesWidth); } else { $(allLI).removeClass("selected"); $(selectedLI).addClass("selected"); settings.onSelect.call(selectedLI); editableInput.val(lozengeContent); } } ); } // otherwise continue to initialise plugin // later we can do checks in here to see if plugin is attached etc var t=$(this); var c=t.find("> .options"); var displayedLozengesWidth=0; $.data(t[0],"multiDropDownSettings",settings); // check if field is NOT editable and multiselect. If so then we need to make it invisible so user will not be able to tyope in it, cursor won't be blinking. Still watermark text can be put outside of the field if(!settings.editable && settings.multiSelect) { t.find("> .selectedItems > .inputField").css({ opacity: 0, position: "absolute", left: "-99999em", height: 0 }); } // flag used to control if the lozenges are being added or removed as this all will be animated and if we dont check for this all hell breaks loose var modyfyingSelected=false; // resize input field to the width of whole container t.find("> .selectedItems > .inputField").css({ width: "100%"}); // c will contain options list when its moved to the end of body. we need reference to it so we can navigate thru its item with keys and animate and what not var c=t.find("> .options"); // end public "functions" // store in variables for later use var editableInput=t.find("> .selectedItems > .inputField > input"); var allLI=c.find("> div > ul > li"); function moveToEndOfBody() { // check if dropdown is not already direct child of the body if(c.parent("body").length) return; // this is needed so the drop down will show on top of everything else. it needs to be removed from current place in DOM, then moved to the end of body, then displayed on top of everything else and mocved back in dom once drop down close down c.appendTo("body"); // we need to position it under the input field c.css({ left: getPositionOnScreen().x, top: getPositionOnScreen().y }); } function moveBack() { // check if dropdown direct child of the body if(!c.parent("body").length) return; // when field losing focus animation is finished move back drop down from the end of body and place it in where it was originally c.appendTo(t); } // function opening dropdown function openDropDown(e) { console.log("displayedLozengesWidth: "+displayedLozengesWidth); moveToEndOfBody(); // first of all hide all dropdowns if any open $("body > .options").not(c).slideUp(300); $(".selectEditable").not(t).removeClass("open"); // unfold the one clicked c.slideDown(300); //if(settings.editable) editableInput.focus(); // dont you bubble anywhere e.stopPropagation(); // attach listener so we know if click outside of drop down occures and we can close it $("html").on("click.dropDowns",function(e) { // if clicked anywhere it should bubble to html then we need to hide dropdowns closeDropDown(); }); t.addClass("open"); } function closeDropDown() { $("html").off("click.dropDowns"); c.slideUp(300,function() { t.removeClass("open"); // alsoo remove items marked as selected as it might happen that in editable and not multiselect mode user selects item, then modifies it. This will cause item in options menu to stay as selected although its not anymore //if(settings.editable && !settings.multiSelect) // c.find("> div > ul > li.selected").removeClass("selected"); moveBack(); // and remove hovered li as this might cause trouble later when adding items if leftout in options c.find("> div > ul > li.hover").removeClass("hover"); }); } // now check if any scrolling container is specified as the dropdown might be in a dialog or something else that can be scrolled. if so then move options dropdown according to scrolling container rether than window and on window scroll simply close container as it gets too messy to track scrolls for both if(settings.scrollingContainer) { $(settings.scrollingContainer).scroll(function(e) { c.css({ left: getPositionOnScreen().x, top: getPositionOnScreen().y }); }); $(window).scroll(function(e) { closeDropDown(); }); } else $(window).scroll(function(e) { c.css({ left: getPositionOnScreen().x, top: getPositionOnScreen().y }); }); t.on("click", function(e) { // check if user clicked lozenge and if so delete lozenge if($(e.target).parent()[0].nodeName=="LI" && !$(e.target).parent().hasClass("totalLozenges")) { updateSelectedValue(c.find("> div > ul > li[data-selected-option="+$(e.target).parent().data("selected-option")+"]")); if(settings.editable) editableInput.focus(); } else if($(e.target).parent().hasClass("totalLozenges")) { // if total lozenges clicked then show dropdown only with selected items // dropdown might be open or closed by now, no matter we have to display selected items only // hide all the items that do not match what user typed in c.find("> div > ul > li:not(.selected)").slideUp(300); // unfold current set of items var a=c; moveToEndOfBody(); a.slideDown(300); a.promise().done(function() { c.find("> div > ul > li.hover").removeClass("hover"); c.find("> div > ul > li.selected").slideDown(300); }); editableInput.focus(); // dont you bubble anywhere e.stopPropagation(); // attach listener so we know if click outside of drop down occures and we can close it $("html").on("click.dropDowns",function(e) { // if clicked anywhere it should bubble to html then we need to hide dropdowns closeDropDown(); }); t.addClass("open"); } else if(!t.hasClass("open")) { // by now we know that dropdown is clicked and none of the lozenges or total number lozenge is clicked so simply open dropdown with all in it editableInput.focus(); // on click make always all the items visible //if(editableInput.val()=="") c.find("> div > ul > li").show(); openDropDown(e); } else { // now we know its open drop down and none of the lozenges were clicked so simply close the dropdown closeDropDown(); } }); function updateSelectedValue(selectedLI) { if($.data(t[0],"displayedLozengesWidth")) displayedLozengesWidth=$.data(t[0],"displayedLozengesWidth"); if($(selectedLI).hasClass("selected")) { // lozege is being removed if(settings.multiSelect) { // ignore if the lozenge is being added/removed if(!modyfyingSelected) { modyfyingSelected=true; $(selectedLI).removeClass("selected"); // check how much horizontal space all the lozenges take and if more than available display message how many is selected // first find lozenge that needs to be removed var lozengeToBeRemoved=t.find("> .selectedItems > ul").find(">[data-selected-option="+$(selectedLI).data("selected-option")+"]"); // update total width of lozenges we have after removing this one displayedLozengesWidth=displayedLozengesWidth-lozengeToBeRemoved.width(); $.data(t[0],"displayedLozengesWidth",displayedLozengesWidth); // check if width of lozenges we have now will fit into space we provided if(displayedLozengesWidth .selectedItems > ul").css("max-width"))) { if(t.find("> .selectedItems > ul > .totalLozenges").length) { lozengeToBeRemoved.remove(); // so now lozenges will fit in space provided // so we remove total lozenges message and display lozenges list t.find("> .selectedItems > ul > .totalLozenges").hide(300,function() { $(this).remove(); }); // lozenges were off the screen till now so we chnage position and display in place again t.find("> .selectedItems > ul > li").css({ position: "relative", left: "auto", opacity: 1 }); // adjust the size of input field where user can type in stuff // this will haoppen simultaneously as animation for loznges plays so its mooth effect t.find("> .selectedItems > .inputField").animate({width: t.find("> .selectedItems").width()-displayedLozengesWidth},300); t.find("> .selectedItems > ul > li").not(".totalLozenges").hide().show(300,function() { modyfyingSelected=false; }); } else { // adjust the size of input field where user can type in stuff // this will haoppen simultaneously as animation for loznges plays so its mooth effect t.find("> .selectedItems > .inputField").animate({width: t.find("> .selectedItems").width()-displayedLozengesWidth},300); lozengeToBeRemoved.hide(300,function() { $(this).remove(); modyfyingSelected=false;}); } } else { // check if total message is already displayed, if so change message only but not animate it anymore if(t.find("> .selectedItems > ul > .totalLozenges").length) { lozengeToBeRemoved.remove(); // here we simply change message t.find("> .selectedItems > ul > .totalLozenges").html('
    Total selected '+(t.find("> .selectedItems > ul > li").length-1)+'
    '); // -1 as there is still total lozenge in a list // adjust the size of input field where user can type in stuff // this will haoppen simultaneously as animation for loznges plays so its smooth effect t.find("> .selectedItems > .inputField").animate({width: t.find("> .selectedItems").width()-t.find("> .selectedItems > ul").width()},300,function() { }); modyfyingSelected=false; } else { // hide all current lozenges t.find("> .selectedItems > ul > li").hide(300); t.find("> .selectedItems > ul > .totalLozenges").remove(); // add new item to front of the list with total message t.find("> .selectedItems > ul").prepend('
  • Total selected '+(t.find("> .selectedItems > ul > li").length)+"
  • "); // adjust the size of input field where user can type in stuff // this will haoppen simultaneously as animation for loznges plays so its smooth effect t.find("> .selectedItems > .inputField").animate({width: t.find("> .selectedItems").width()-t.find("> .selectedItems > ul > .totalLozenges").width()},300); t.find("> .selectedItems > ul > .totalLozenges").hide().show(300,function() { modyfyingSelected=false; }); } } //////console.log(displayedLozengesWidth); } } else { // no multiple select so reset content of dropdown input box and deselect option in dropdown $(allLI).removeClass("selected"); editableInput.val(""); } } else { // lozenge is being added // here get the content that needs to be put into lozenge. This might be some inside span, title attribute or something and might be different from the content of the LI as this might contain html, icons images etc var lozengeContent=$(selectedLI).find(">div").text(); if(settings.multiSelect) { if(!modyfyingSelected) { modyfyingSelected=true; $(selectedLI).addClass("selected"); // add new lozenge to list of lozenges selected var newLozenge=t.find("> .selectedItems > ul").prepend('
  • '+lozengeContent+'
  • '); newLozenge=t.find("> .selectedItems > ul").find('li[data-selected-option='+$(selectedLI).data("selected-option")+']'); // we dont want to display this lozenge yet as we don't know if it will fit in with all the other lozenges in space provided by max-width in css // so we make new lozenge position absolute and make it invisible so its width can be measured without actually outputting it to the screen newLozenge.css({ position: "absolute", opacity: 0 }); // add width of new lozenge to total width of lozenges and check how much horizontal space all the lozenges take and if more than available display message how many is selected displayedLozengesWidth=displayedLozengesWidth+newLozenge.width(); $.data(t[0],"displayedLozengesWidth",displayedLozengesWidth); // check if this lozenge once added to sum of all lozenges width will be more than space available for our list of lozenges if(displayedLozengesWidth>parseInt(t.find("> .selectedItems > ul").css("max-width"))) { // all lozenges would take too much space so hide lizt of lozenges and add one in front of the list with message total // lozenges are hidden now so add new one with number and message // adjust the size of input field where user can type in stuff // this will haoppen simultaneously as animation for loznges plays so its mooth effect t.find("> .selectedItems > .inputField").animate({width: t.find("> .selectedItems").width()-t.find("> .selectedItems > ul").width()},300); // check if total message is already displayed, if so change message only but not animate it anymore if(t.find("> .selectedItems > ul > .totalLozenges").length) { // here we simply change message t.find("> .selectedItems > ul > .totalLozenges").html('
    Total selected '+(t.find("> .selectedItems > ul > li").length-1)+'
    '); // -1 as there is still total lozenge in a list // adjust the size of input field where user can type in stuff // this will haoppen simultaneously as animation for loznges plays so its mooth effect t.find("> .selectedItems > .inputField").animate({width: t.find("> .selectedItems").width()-t.find("> .selectedItems > ul").width()},300); modyfyingSelected=false; } else { // hide all current lozenges t.find("> .selectedItems > ul > li").hide(300); // add new item to front of the list with total message t.find("> .selectedItems > ul").prepend('
  • Total selected '+(t.find("> .selectedItems > ul > li").length)+"
  • "); // adjust the size of input field where user can type in stuff // this will haoppen simultaneously as animation for loznges plays so its mooth effect t.find("> .selectedItems > .inputField").animate({width: t.find("> .selectedItems").width()-t.find("> .selectedItems > ul> .totalLozenges").width()},300); t.find("> .selectedItems > ul > .totalLozenges").hide().show(300, function() { modyfyingSelected=false; }); } // t.find("> .selectedItems > ul > .totalLozenges").text(t.find("> .selectedItems > ul > li").length-1).show(300); } else { // we need it to display now so make it static again and later animate in newLozenge.css({ position: "static", opacity: 1 }); // adjust the size of input field where user can type in stuff // this will haoppen simultaneously as animation for loznges plays so its mooth effect t.find("> .selectedItems > .inputField").animate({width: t.find("> .selectedItems").width()-t.find("> .selectedItems > ul").width()},300); // animate new lozenge in t.find("> .selectedItems > ul > li:first-child").hide().show(300,function() { modyfyingSelected=false; }); } //////console.log(displayedLozengesWidth); } } else { $(allLI).removeClass("selected"); $(selectedLI).addClass("selected"); settings.onSelect.call(selectedLI); editableInput.val(lozengeContent); } } } // if input is in focus put class on selectEditable main container so it can be styled accordingly if needed t.find("> .selectedItems > .inputField > input").focus(function(e) { t.addClass("focus"); }); t.find("> .selectedItems > .inputField > input").blur(function(e) { t.removeClass("focus"); }); // making selection from the list allLI.click(function(e) { updateSelectedValue(this); closeDropDown(); c.find("> div > ul > li.hover").removeClass("hover"); e.stopPropagation(); }); // function copied from http://flightschool.acylt.com/devnotes/caret-position-woes/ function doGetCaretPosition(oField) { // Initialize var iCaretPos = 0; // IE Support if (document.selection) { // Set focus on the element oField.focus (); // To get cursor position, get empty selection range var oSel = document.selection.createRange (); // Move selection start to 0 position oSel.moveStart ('character', -oField.value.length); // The caret position is selection length iCaretPos = oSel.text.length; } // Firefox support else if (oField.selectionStart || oField.selectionStart == '0') iCaretPos = oField.selectionStart; // Return results return (iCaretPos); } // function displays results matching the string function displayResultsFor(inputVal,e) { // check if argument matches any items in the list, if not then close dropdown if(c.find("> div > ul > li:not(:contains('"+inputVal+"'))").length==c.find("> div > ul > li").length) { closeDropDown(); } else { // there is a match so hide all the items that do not match what user typed in c.find("> div > ul > li:not(:contains('"+inputVal+"'))").slideUp(300); // unfold current set of items var a=c; moveToEndOfBody(); a.slideDown(300); a.promise().done(function() { // remove hover from highighted item as when use press space we dont want it to trigger selection but we rather want user to type in space into a field //c.find("> div > ul > li.hover").removeClass("hover"); c.find("> div > ul > li:contains('"+inputVal+"')").slideDown(300); }); editableInput.focus(); // attach listener so we know if click outside of drop down occures and we can close it // dont you bubble anywhere e.stopPropagation(); $("html").on("click.dropDowns",function(e) { // if clicked anywhere it should bubble to html then we need to hide dropdowns closeDropDown(); }); t.addClass("open"); } } // function displays options for lozenge currently in focus function displayResultsForLozenge(e) { // user might have pressed something that dosent affect what appears in input field e.g. arrow right/left, tab etc but this might affect which lozenge is selected and so should affect options listed that user see // first we check if any of the selected lozenges is in focus if so then we display only matcched elements options if(t.find(".selectedItems > ul > li.inFocus").length) { // is only total displayed rather than individual lozenges for items? if(t.find(".selectedItems > ul > li.totalLozenges").length) { // display only options selected // hide all the items that are not selected c.find("> div > ul > li:not(.selected)").slideUp(300); // unfold current set of items var a=c; moveToEndOfBody(); a.slideDown(300); a.promise().done(function() { // remove hover from highighted item as when user press space we dont want it to trigger selection but we rather want user to type in space into a field c.find("> div > ul > li.hover").removeClass("hover"); c.find("> div > ul > li.selected").slideDown(300); }); // attach listener so we know if click outside of drop down occures and we can close it // dont you bubble anywhere e.stopPropagation(); $("html").on("click.dropDowns",function(e) { // if clicked anywhere it should bubble to html then we need to hide dropdowns closeDropDown(); }); t.addClass("open"); } else { // hide all the items that do not match what lozenge content is var inputVal = t.find(".selectedItems > ul > li.inFocus").text(); c.find("> div > ul > li:not(:contains('"+inputVal+"'))").slideUp(300); // unfold current set of items var a=c; moveToEndOfBody(); a.slideDown(300); a.promise().done(function() { // remove hover from highighted item as when use press space we dont want it to trigger selection but we rather want user to type in space into a field c.find("> div > ul > li.hover").removeClass("hover"); c.find("> div > ul > li:contains('"+inputVal+"')").slideDown(300); }); if(settings.editable) editableInput.focus(); // attach listener so we know if click outside of drop down occures and we can close it // dont you bubble anywhere e.stopPropagation(); $("html").on("click.dropDowns",function(e) { // if clicked anywhere it should bubble to html then we need to hide dropdowns closeDropDown(); }); t.addClass("open"); } } } editableInput.keyup(function(e) { console.log(e.keyCode) // if any animation plays ignore the keys otherwise in extreme cases it might become messy when user folds and unfolds options list, navigates via lozenges and so on if(c.is(":animated")) return; // by now if user typed in anything and its allowed by keyDown callback where we check all key presses this should appear in input field. So the idea is to check again if anything like enter, tab, arrows etc was pressed and act only on important keys that change what appears in input field var inputVal=t.find(".selectedItems > .inputField > input").val(); // now we took care of lozenges and its resulting options by now so we need to check if user simply tries to type something in and if so we need to display results if( settings.editable && e.keyCode!=40 // down arrow && e.keyCode!=38 // up arrow && e.keyCode!=39 // right arrow && e.keyCode!=37 // left arrow && e.keyCode!=13 // enter && e.keyCode!=17 // ctrl && e.keyCode!=16 // shift && e.keyCode!=9 // tab && e.keyCode!=32 // space ){ // it might be the case that user typed something in and we need to dispay results or that user hits backspace or delete and delete field contnt. If this happens we need to display all the options, otherwise just the ones matching input field value if(!inputVal) { // input field is empty and backspace or delete key was pressed so we need to display make all LI's visible and then check if the dropdown should be unfolded c.find("> div > ul > li").slideDown(300); // check if already open and not enter/tab/arrow up etc then unfold dropdown if( !t.hasClass("open")) { openDropDown(e); } } else // here we just need to display results that match what user typed in input field displayResultsFor(inputVal,e); }/* else if(e.keyCode==40) { moveToEndOfBody(); // simply open the dropdown and hide items that do not match selectpr and show items that do match if(inputVal!="" && settings.editable) { c.find("> div > ul > li:not(:contains('"+inputVal+"'))").hide(); c.find("> div > ul > li:contains('"+inputVal+"')").show(); } else if(inputVal=="") c.find("> div > ul > li").show(); c.slideDown(300, function() { // if dropdown is open and there is no selection hoveredLI then highlight first visible item var hoveredLI=c.find("> div > ul > li.hover"); if(!hoveredLI.length) hoveredLI=$(c.find("> div > ul > li").filter(":visible")[0]).addClass("hover"); }); // attach listener so we know if click outside of drop down occures and we can close it // dont you bubble anywhere e.stopPropagation(); $("html").on("click.dropDowns",function(e) { // if clicked anywhere it should bubble to html then we need to hide dropdowns closeDropDown(); }); t.addClass("open"); }*/ // now above condition checks if user types something in and eliminates common keys but now if user en up pressing backspace or delete and the field becomes empty we need to display all results e.stopPropagation(); }) function getPositionOnScreen() { return { x: t.offset().left-t.scrollLeft(), y: t.offset().top-t.scrollTop()+t.height() } } editableInput.keydown(function(e) { if(c.is(":animated")) return; // get currently highlighted with keyboard item var hoveredLI=c.find("> div > ul > li.hover"); if(e.keyCode==40) // down arrow { // if lozenge selected is in focus just deselect it so drop down with options can get focus if(t.find(".selectedItems > ul > li.inFocus").length) { // when user hits arrow down we want all the options to show for all the lozenges c.find("> div > ul > li").slideDown(300); openDropDown(e); // and remove focus from lozenge as arrow down will focus on dropdown with options now t.find(".selectedItems > ul > li.inFocus").removeClass("inFocus"); } // if we not pressing down when lozenge is focused so pressing down simply from ediatable field then check if anything on the list matches what user typed in and if not then return and ignore the rest else if(c.find("> div > ul > li:not(:contains('"+editableInput.val()+"'))").length==c.find("> div > ul > li").length && editableInput.val()!="" && !t.hasClass("open")) return false; // so no lozenge selected and we know that there are some options to display so now check if input contains somethng and if not display all options // we also need always to display all the options if the field is not editable and not multiselect. otherwise user will end up with limited options set once anything chosen from the dropdown else if(editableInput.val()=="" || (!settings.editable && !settings.multiSelect)) { if(!t.hasClass("open")) { c.find("> div > ul > li").show(); openDropDown(e); } } // if input contains something and no option is highlighted (selected via keyboard) then display matching options only else if(editableInput.val()!="" && !hoveredLI.length) displayResultsFor(editableInput.val(),e); // on arrow down navigate over currently visible items. if(!hoveredLI.length) hoveredLI=$(c.find("> div > ul > li").filter(":visible")[0]).addClass("hover"); else if(hoveredLI.nextAll(":visible").length) { c.find("> div > ul > li.hover") .removeClass("hover") .nextAll(":visible").first() .addClass("hover"); } e.preventDefault(); } else if(e.keyCode==8 && !modyfyingSelected) // backspace { // check if lozenge is not in focus if(!t.find(".selectedItems > ul > li.inFocus").length) { // if its not editable field and none of the lozenges has focus then ignore the key if(!settings.editable) { e.preventDefault(); return; } // otherwise if(!doGetCaretPosition(t.find(".selectedItems > .inputField > input")[0])){ // nothing is typed in front of the cursor and its in position 0 so put first lozenge in focus t.find(".selectedItems > ul > li:visible").last().addClass("inFocus"); } } else { // check if totallozenges s displayed and if so remove all selected lozenges if(t.find(".selectedItems > ul > li.totalLozenges").length) { // remove all selected items modyfyingSelected=true; t.find(".selectedItems > ul > li").hide(300,function() { t.find(".selectedItems > ul > li").remove(); } ); c.find("> div > ul > li.selected").removeClass("selected"); displayedLozengesWidth=0; $.data(t[0],"displayedLozengesWidth",0); t.find("> .selectedItems > .inputField").animate({width: t.find("> .selectedItems").width()},300,function() { modyfyingSelected=false; }); } else { // remove only one selected lozenge updateSelectedValue(c.find("> div > ul > li[data-selected-option="+t.find(".selectedItems > ul > li.inFocus").data("selected-option")+"]")); } // also remove focus from the one deleted // NEED TO CHECK IF THIS IS NECESSARY t.find(".selectedItems > ul > li.inFocus").removeClass("inFocus"); } } else if(e.keyCode==39) // right arrow { // check if any of the selected lozenges has focus, if so then left/right arrow will navigate between these if(t.find(".selectedItems > ul > li.inFocus").length && t.find(".selectedItems > ul > li.inFocus").next("li:visible").length) { t.find(".selectedItems > ul > li.inFocus").removeClass("inFocus").next("li:visible").addClass("inFocus"); // in here as we navigated to another lozenge display new set of matching options displayResultsForLozenge(e); e.preventDefault(); } else if(t.find(".selectedItems > ul > li.inFocus").length && !t.find(".selectedItems > ul > li.inFocus").next("li:visible").length) { t.find(".selectedItems > ul > li.inFocus").removeClass("inFocus"); // no lozenge is selected and so we need to take care of displaying results for whatever is typed in the field displayResultsFor(editableInput.val(),e); e.preventDefault(); } } else if(e.keyCode==37) // left arrow { // check if any of the selected lozenges has focus, if so then left/right arrow will navigate between these if(t.find(".selectedItems > ul > li.inFocus").length && t.find(".selectedItems > ul > li.inFocus").prev("li:visible").length) { // nothing is typed in so put fiirst lozenge in focus t.find(".selectedItems > ul > li.inFocus").removeClass("inFocus").prev("li:visible").addClass("inFocus"); } else if(!t.find(".selectedItems > ul > li.inFocus").length && !doGetCaretPosition(t.find(".selectedItems > .inputField > input")[0])) { t.find(".selectedItems > ul > li:visible").last().addClass("inFocus"); } displayResultsForLozenge(e); } else if(e.keyCode==38) // up arrow { // check if any visible options are above currrently selected one if(hoveredLI.prevAll(":visible").length) c.find("> div > ul > li.hover") .removeClass("hover") .prevAll(":visible").first() .addClass("hover"); else { // if thats the top item then close the drop down box closeDropDown(); c.find("> div > ul > li.hover").removeClass("hover"); } e.preventDefault(); } else if(e.keyCode==13 && !e.ctrlKey && hoveredLI.length) // enter { // if the dropdown is not open then igonore key as otherwise the currently "hover"ed item will be selected if(!t.hasClass("open")) return false; updateSelectedValue(hoveredLI); closeDropDown(); hoveredLI.removeClass("hover"); e.preventDefault(); e.stopPropagation(); } // check if new item is being added via typing so user hits enter and this should add lozenge else if(settings.editable && settings.multiSelect && e.keyCode==13 // enter && !hoveredLI.length // nothing hovered with keyboard ) { // if user hits enter on empty field ignore it if(editableInput.val()=="") return false; // check for duplicated items if(!settings.duplicatesAllowed) { if(c.find("> div > ul > li:contains('"+editableInput.val()+"')").length) return; } // end TODO: check for duplicated items // add new value to the list of options var randomId=parseInt(Math.random(1)*100000000); c.find("> div > ul").append('
  • '+t.find(".selectedItems > .inputField > input").val()+"
  • "); // hide and nicely slide down // THIS LOGIC BELOW NEEDS TO BE DOUBLE CHECK HERE c.find("> div > ul > li:last-child").hide().slideDown(300, function() { // if options list is open then its all good // if its not open however slide down options menu ith new item added if(!t.hasClass("open")) { c.find("> div > ul > li:not(:contains('"+t.find(".selectedItems > .inputField > input").val()+"'))").hide(); openDropDown(e); } }); // end THIS LOGIC BELOW NEEDS TO BE DOUBLE CHECK HERE // add this value as selected and add it to the list of lozenges updateSelectedValue(c.find("> div > ul > li[data-selected-option="+randomId+"]")); e.stopPropagation(); } else if(e.keyCode==27) // escape { // if the dropdown is not open then igonore key as otherwise the currently "hover"ed item will be selected if(!t.hasClass("open")) return false; closeDropDown(); c.find("> div > ul > li.hover").removeClass("hover"); t.find(".selectedItems > ul > li.inFocus").removeClass("inFocus"); e.stopPropagation(); // do we need preventDefault here?? need to check } // user press space and some option is hovered with keyboard else if(e.keyCode==32 && hoveredLI.length) // space { // if the dropdown is not open then igonore key as otherwise the currently "hover"ed item will be selected if(!t.hasClass("open")) return false; // if no multipleselect then close dropdown if(!settings.multiSelect) { closeDropDown(); hoveredLI.removeClass("hover"); } // this is needed so the actual space character won't be inserted into field e.preventDefault(); updateSelectedValue(hoveredLI); } // no matter tab or shift+tab they do the same else if(e.keyCode==9) // tab { if(t.hasClass("open")) closeDropDown(); c.find("> div > ul > li.hover").removeClass("hover"); t.find(".selectedItems > ul > li.inFocus").removeClass("inFocus"); } else { // everything else is processed here. By now we took care of important keys so here is probabbly only what user can type in // user typed something so remove focus from lozenge selected if any t.find(".selectedItems > ul > li.inFocus").removeClass("inFocus"); // if field is not editable then ignore all the other keys //return; // do we even need this below?? if(!settings.editable) { // field is not editable we still want user to be able to copy content of the field, highlight with shift+arrows etc so do all the checks here and anything else ignore if(!(e.shiftKey && (e.keyCode==37 || e.keyCode==39)) // not shift+left/right arrow && e.keyCode!=37 && e.keyCode!=39 && !(e.ctrlKey && e.keyCode==67) // ctrl+c ) e.preventDefault(); } } }); // when mouse over any of the elements in a list then remove highlight set by arrow up/down if any allLI.mouseover(function(e) { allLI.removeClass("hover"); }); }); }; })(jQuery); $(document).ready(function() { //$("#someField").myPlugin({'editable':true}); //$("#someField").myPlugin("set10"); // framework module used across all the pages var framework=(function() { var loadingPage=null; // position global messages $(".globalMessage").css("left",$(window).width()/2-$(".globalMessage").width()/2); console.log("global"); var isShowingGlobalMessage=null; // on tab should underline, on click it shouldnt // THIS IS QUICK AND TEMP SOLUTION, NEEDS TO BE LOOKED AT PROPERLY var radioFromClick=null; $("input[type=radio]+label,input[type=checkbox]+label").click(function() { console.log("a"); radioFromClick=this; $(this).parent().find(">input").focus(); }); $("input[type=radio]").focus(function() { console.log("b"+radioFromClick); $("input[type=radio]+label").removeClass("noUnderlineText"); if(radioFromClick) $(this).parent().find(">label").addClass("noUnderlineText"); radioFromClick=false; }); $("input[type=checkbox]").focus(function() { console.log("b"+radioFromClick); $("input[type=checkbox]+label").removeClass("noUnderlineText"); if(radioFromClick) $(this).parent().find(">label").addClass("noUnderlineText"); radioFromClick=false; }); // attach listeners for menu items so unfolding panels can be open/close $(".search").click(function(e) { // search is same form for all the pages if($(".unfoldingPanel #searchPanel").is(":visible")) { $(".search").parent().removeClass("highlightItem"); framework.closeUnfoldingPanel(); } else if($(".unfoldingPanel").is(":visible")) { $(".tools li").removeClass("highlightItem"); $(".search").parent().addClass("highlightItem"); framework.closeAndOpenUnfoldingPanel("search",e); } else { framework.openUnfoldingPanel("search",e); // resize widtth of search panel wto take the whole width of the window framework.resizeSearchPanel(); $(".search").parent().addClass("highlightItem"); $("#searchPanel .searchBox input").val("").focus(); } e.preventDefault(); }); // make pointrer in search field to change color when searchbox is in focus $(".unfoldingPanel > section#searchPanel .panelWrapper .fields > .searchBox > input").focus(function(e) { $(".unfoldingPanel > section#searchPanel .panelWrapper .fields > .searchBox").removeClass("error"); $(".unfoldingPanel > section#searchPanel .panelWrapper .fields > i").removeClass("error"); $(".unfoldingPanel > section#searchPanel .panelWrapper .fields > .searchBox").addClass("focus"); if($(".unfoldingPanel > section#searchPanel .panelWrapper .fields > .searchBox > input").val()==framework.searchBoxWatermarkText) $(".unfoldingPanel > section#searchPanel .panelWrapper .fields > .searchBox > input").val(""); }); $(".unfoldingPanel > section#searchPanel .panelWrapper .fields > .searchBox > input").blur(function(e) { $(".unfoldingPanel > section#searchPanel .panelWrapper .fields > .searchBox").removeClass("focus"); if($(".unfoldingPanel > section#searchPanel .panelWrapper .fields > .searchBox > input").val()=="") { $(".unfoldingPanel > section#searchPanel .panelWrapper .fields > .searchBox > input").val(framework.searchBoxWatermarkText); } }); // click on search submit arrow $("a.submitArrow").click(function(e) { $(".unfoldingPanel > section#searchPanel .panelWrapper .fields > .submitArrow").removeClass("focus"); if($(".unfoldingPanel > section#searchPanel .panelWrapper .fields > .searchBox > input").val()==framework.searchBoxWatermarkText) { $(".unfoldingPanel > section#searchPanel .panelWrapper .fields > .searchBox").addClass("error").val(framework.searchBoxWatermarkText); $(".unfoldingPanel > section#searchPanel .panelWrapper .fields > i").addClass("error").val(framework.searchBoxWatermarkText); } $("a.submitArrow").blur(); }); $(".lozenges").on("click","li a",function(e) { $("ul.lozenges li[data-associated-filter="+$(this).parent().attr("data-associated-filter")+"]").animate({opacity: 0},300,function() { $(this).hide(300,function() { $(this).remove(); }); }); $($("#documentsFilterPanel input[name="+$(this).parent().attr("data-associated-filter")+"]")[0]).prop("checked",true); e.preventDefault(); }); $(".lozenges").on("mouseover","li a",function(e) { $(this).parent().addClass("hover"); }); $(".lozenges").on("mouseout","li a",function(e) { $(this).parent().removeClass("hover"); }); $(".filter").click(function(e) { // filters are different for documents, reports and maybe others sow ehn filter is clicked we need to check which page we on and open apropriate panel if($(".unfoldingPanel #"+$("body").attr("data-current-page")+"FilterPanel").is(":visible")) { $(".filter").parent().removeClass("highlightItem"); framework.closeUnfoldingPanel(); } else if($(".unfoldingPanel").is(":visible")) { $(".tools li").removeClass("highlightItem"); $(".filter").parent().addClass("highlightItem"); framework.closeAndOpenUnfoldingPanel($("body").attr("data-current-page")+"Filter",e); } else { $(".filter").parent().addClass("highlightItem"); framework.openUnfoldingPanel($("body").attr("data-current-page")+"Filter",e); } e.preventDefault(); }); $(".closePanel").click(function(e) { framework.closeUnfoldingPanel(); e.preventDefault(); }); // this assigns listeners to key presses. Need to be carefull later on what we trigerring and so here we check if the dialog is open and assign actions only when dialog is open $(document).keyup(function(e) { // check if shortcuts should be detected if($("body").hasClass("noShortcuts")) return false; // here we assigning listeners and logic to shortcuts. Might need to organise it better at some point // rather then checking all this manually one by one if(!$("section#searchPanel").is(":visible")) { if(e.keyCode == 83) // s { $('a.search').click(); } } // we checking only for filters for documents page here. Need to check for othetr pages later on too if(!$("section#documentsFilterPanel").is(":visible") && !($("section#searchPanel input").is(":focus"))) { if(e.keyCode == 70) { $('a.filter ').click(); } // f } if($("section#documentsFilterPanel").is(":visible")) if (e.keyCode == 27) { $('a.filter ').click(); } // esc if(($("section#searchPanel").is(":visible"))) if (e.keyCode == 27) { $('a.search ').click(); } // esc }); // top navigation dropdowns in tools menu var active=null; // may need more unique c;lass name later on, osmethign less generic than dropdown $(".dropdown").each(function() { $(this).click(function(e) { if(active!=null) clearTimeout(active); $(".dropdown.selected").removeClass("selected").find("> div").fadeOut(300); if(!$(this).find(">div").is(":visible")) { $(this).find("> div").slideDown(500); $(this).addClass("selected"); } e.preventDefault(); }); $(this).mouseout(function() { active=setTimeout(closeDropDown, 500); }); $(this).find("ul").mouseover(function() { if(active!=null) clearTimeout(active); }); $(this).mouseover(function() { if(active!=null) clearTimeout(active); }); function closeDropDown() { // now we need to fade out the menu dropdown. The highlighted menu item will change color abruptyl;y so we need to fade it out as well. However the value is based on distance ogf the item from mouse cursor and so we need to fake it a bit and read value of other surrounding menu item and assign similar/ For now we simply read value of filter item menu. Need to think about this further cause there maybe be a better solution $(".dropdown.selected").animate({ opacity: $($(".tools > li")[1]).css("opacity")},700); $(".dropdown.selected").find("> div").fadeOut(300, function() { $(".dropdown").removeClass("selected"); }); } }); function addAction(positionFromTop,itemClass, itemText) { $('
  • '+itemText+'
  • ').insertAfter($("#topActionsMenu > li")[positionFromTop]); } function removeAction(positionFromTop) { $($("#topActionsMenu > li")[positionFromTop]).remove(); } // lava functionality $(".lava").after('
    ') .find("li").each(function() { var lava=$(this).parent().next(); $(this).click(function(e) { // check if selected item is clicked and if so dont animate frog if(!$(this).hasClass("selected")) { // highlight clicked menu $(this).addClass("highlightItem"); var l=$(this).position().left; var r=$(this).position().right; var t=$(this).position().top; var w=$(this).width(); // currently selected item position var ls=$(".main .selected").position().left; var rs=$(".main .selected").position().right; var ws=$(".main .selected").width(); // for catepilar effect var half=l+w/2; // get pading var p=parseInt($(this).css("padding-left")); if(ls151) { // nav is not fully visible // if so we need to make nav as well as open unfolded panels, scrollbar etc stick to the top of the screen // make both navigation panel and unfolding panels sticky, will be position fixed framework.adjustMainPanelTopPadding(); // as these are fixed now we need to adjust width manually $("nav#top").addClass("sticky").css("width",$(".wrapper").width()); $(".unfoldingPanel").addClass("sticky").css("width",$(".wrapper").width()); $("nav#top").css("width",$(".wrapper").width()-30); // and as scrollbar might be showing we need to put it under the unfolding panel and stick there. Unfolding panel might have different heights hence we need to get its dimensions and position scrollbar accordingly under it // check if unfolding panel is unfolded if($(".unfoldingPanel").height()) $(".myScrollBar").css("top",$(".unfoldingPanel").position().top+$(".unfoldingPanel").height()); else $(".myScrollBar").css("top","39px"); // exact height of menu bar when its sticky //$(".myScrollBar").css("left","10px"); // user might scroll horizontally so we need to reposition elements so these do not stay in fixed place $(".sticky").css("left",-$(window).scrollLeft()+"px"); // also make sure that is user scrolls horizontally the scrollbar will move rather than stay in fixed place $(".myScrollBar").addClass("sticky"); $(".myScrollBar").width($(".myScrollBar").width()); $(".myScrollBar").css("left",-$(window).scrollLeft()+10+"px"); } else { // remove padding from top of the mainPanel $("#mainPanel").css("padding-top","0px"); $(".unfoldingPanel").removeClass("sticky"); $(".unfoldingPanel").css("left","0px"); $("nav#top").removeClass("sticky"); $("nav#top").css("width","auto"); $("nav#top").css("left","0px"); $(".myScrollBar").css("top","0px"); $(".myScrollBar").css("left","0px"); $(".myScrollBar").removeClass("sticky"); } // if #top is sticky all the rest (unfoldingPanels are also sticky) /* if($(".sticky").length) { // and once again for scrollbar if attached to framework //$(".myScrollBar.sticky").css("left",(-$(window).scrollLeft()+10)+"px"); } else { }*/ // if any scrolling happens also get rid of properties popup // at some point might need to recode this parent so propertiesOpen class dosent have to be searched for thru all dom tree, maybe create some private variable holding reference to currently open popup or something $(".wrapper > .properties").fadeOut(100,function() { $(".wrapper > .properties,.wrapper > .propertiesArrowRight").remove(); $(".propertiesOpen").removeClass("propertiesOpen") }); }); $(window).resize(function(e) { framework.adjustWrapper(); // position global messages if($(window).width()>980) $(".globalMessage").css("left",$(window).width()/2-$(".globalMessage").width()/2); // sticky navigation bar and unfolding panels // on resize adjust width of sticky elements // check if there are any sticky elements if($(window).scrollTop()>151) { $(".sticky").css("width",$(".wrapper").width()); $("nav#top").css("width",$(".wrapper").width()-30); // when user resizes window we need to check if any unfolding panel is showing and if so need to position scrollbar under it if($(".unfoldingPanel").height()) $(".myScrollBar").css("top",$(".unfoldingPanel").position().top+$(".unfoldingPanel").height()); else $(".myScrollBar").css("top","39px"); } else { // as by now unfolding panel might have width specified we need to make it to resize to width of container again $(".unfoldingPanel").css("width","auto"); } // sticky or not resize scrollbar but check if unfolding panel is unfolded. THi shelps in situations when user has very wide window and scrollbar is not showing as the content is not large enough. Then user shrins the window and scrollbar shows $(".myScrollBar").customScrollBar("setSize",$(".wrapper").width()); // make sure width of the searchPanel takes all width available framework.resizeSearchPanel(); }); $(window).bind('hashchange', function() { loadPage(window.location.hash.replace("#","")) $("nav#top li:not(.selected)").animate({ opacity: 0.4 },300); }); // setting up opacity for navigation, this is via javascript rather then via css // to avoid inconsistencies and hacks for IE7 $("nav#top li").css({ opacity: 0.35 }); $(".logoContainer").css("top","0"); // setting up transparency of menu items based on mouse distance from the item itself $("nav#top > ul > li").approach({ "opacity": "0.75" }, 200); // load page as specified by # part of url $("#mainPanel > section").not(":first-child").hide(); loadPage(window.location.hash.replace("#","")); adjustWrapper(); // set currently selected menu item so it will be underlined function setSelectedMenuItem(itemName) { $(".main.lava > li").removeClass("selected"); $("#main_"+itemName.toLowerCase()).addClass("selected").css({ opacity: "1"}); } function loadPage(anchor) { $("html, body").animate({ scrollTop: 0 }, 300,function() { }); // remove highlight from clicked menu $(".main .selected").removeClass("highlightItem"); // remove green highlight from menu $(".main .selected").removeClass("selected"); // anchor "#" part specifies what page to display. Then page might be puled out from server by ajax call and whatever content the page has it need to be populated into htmlFromAjaxCall variable and then inserted into DOM. Now its hardcoded and function is for demonstration only and so AJAX communication needs to be coded // fake populating variable with HTML that will be injected into DOM. If no anchor is specified then open // default page which is documents var anchor=anchor || "documents"; setSelectedMenuItem(anchor); // display loading message // check if page is being loaded and if so cancel the request and process latest request // this will happen when e.g. user clicks reports and then before the reports page loads user clicks questiuons (or other item) if(loadingPage!=="undefined") { clearTimeout(loadingPage); $("#mainPanel>section:visible > .loading").remove(); } $("#mainPanel>section:visible").prepend('

    Loading '+anchor+' page

    '); //$(".loadingMessage").addClass("visible"); //$(".loadingMessage").css("left",($(window).width()/2-$(".loadingMessage").width()+300)); // in here would be ajax success callback, for now we hold on for 3 secs and then call the function that imitates callback loadingPage=setTimeout(function() { // once ajax call comes back with success hide the loading message $("#mainPanel>section:visible > .loading").remove(); // to avoid jumping of the bottom edge store the height of the wrapper for split second. THis will basically make bottom edge to stick to the bottom when the cuurrent page is slid up. Otherwise bottom edge will slide up, not cool $(".wrapper").css("height",$(".wrapper").height()+"px"); // slide up currently visible page $("#mainPanel>section:visible").slideUp(300); // hide unfolding panel e.g. search, filters etc if this is currently unfolded $(".unfoldingPanel").slideUp(100,function(e) { $(".unfoldingPanelPointer .inner").removeClass("visible"); $(".unfoldingPanelPointer .outer").removeClass("visible"); $("#mainPanel").css("padding-top","0px"); $(".tools li").removeClass("highlightItem"); }); // hide scrollbar if its showing $(".myScrollBar").slideUp(100); // now we have no loading indicator and we need to populate content from ajax call into DOM // ajax call might return json or anything really. Once its processed and already as HTML we put this content into htmlFromAjax ready to nbe injected into DOM var htmlFromAjaxCall=""; // put data-current-page on body so later we can easily find out what page is open by simply checking data on body tag $("body").attr("data-current-page",anchor); // if there is anything there already get rid of it //$("section#"+(anchor || "documents")).html(""); // and insert new content $("section#page_"+anchor.toLowerCase()).slideDown(300,function() { // here is all that needs to be modified after specific page is unfolded and displayed. E.g. for reports dashboard we might want to fade boxes in nicely or do something else we fancy framework.adjustWrapper(); if(anchor=="reports") reportsDashboard.showBoxes(); // at this point we might also need to display scrollbar for some elements. this scrollbar might rely on width of the wrapper body etc. This might be changed if after loading page content window native scrollbar is displayed on the right side. so we need to recalculate the size of our scrollbar now and resize it as it might be displayed with wrong width if this happens //if($(".myScrollBar").is(":visible")) // $(".myScrollBar").customScrollBar("setSize",$(".wrapper").width()); }); $("section#page_"+anchor.toLowerCase()+" .pageMainContent").prepend(htmlFromAjaxCall); // now as this is simulated loading only we simply check what page was loaded as we might need to execute some javascript code when e.g. reports load. This shooudld be as simple as calling init public method of specific module. E.g. in here we call init method as we need to make modification to classes and dom elements in reports dashboard when it loads. here is all that needs to be modified before the page is displayed. e.g. reports dashboard needs no search/filter/actions items in tool menu so inside of init method we remove these switch(anchor) { case "reports": reportsDashboard.init(); break; case "reports-documents": reportsDocuments.init(); break; case "reports-popular-items": reportsPopularItems.init(); break; case "reports-users": reportsUsers.init(); break; case "reports-questions": reportsQuestions.init(); break; case "reports-security": reportsSecurity.init(); } /* this is sort of redundant and might be recoded at some point for now however as webkits, mainly safari gets the width of menu items wrong till the website is laid out properly, we neeed to do it here so its nicely underlined when the site is loaded for the very first time */ // if page dosent exist load documents page if(!$(".main.lava .selected").length) { location.hash="documents"; } else { var lava=$(".main.lava").next(); var l=$(".main.lava .selected").position().left; var t=$(".main.lava .selected").position().top; var w=$(".main.lava .selected").width(); // get pading var p=parseInt($(".main.lava .selected").css("padding-left")); lava.css("top",t+27); lava.stop().animate({ "left":$(".main.lava .selected").position().left, "top":t+27, "width": w},300); } },3000); } function adjustWrapper() { // if the content in main Panel is shorter than size of the window, this function sticks bottom edge to the bottom of the window // it also should be called when the content of the mainPanel is modified so the bototm edge will adjust to its size $(".wrapper").css("height","auto"); if($(".wrapper").height()<$(window).height()-20) $(".wrapper").height($(window).height()-20); } function attachScrollBar() { $(".myScrollBar").customScrollBar({ scrollPane: $(".scroll-pane"), width: $(".wrapper").width() }); } return { attachScrollBar : attachScrollBar, addAction : addAction, removeAction : removeAction, setSelectedMenuItem : setSelectedMenuItem, // filter lozenges for documentsFilterPanel templateLozenges: Handlebars.compile($("#handlebarsLozenges").html()), hideLozenge: function(currentlyShowing) { currentlyShowing.animate({opacity: 0},300,function() { $(this).hide(300,function() { currentlyShowing.remove(); }); }) }, // placeholder for search box searchBoxWatermarkText: "Please enter a search term", // LOADING PAGES VIA AJAX CALLS (IMITATING ONLY FOR NOW) loadPage: loadPage, // end LOADING PAGES VIA AJAX CALLS (IMITATING ONLY FOR NOW) showMessage: function(t,isError) // isError=true then is (error) { // check if message is currently displayed if(isShowingGlobalMessage) { clearTimeout(isShowingGlobalMessage); } if(isError) $(".globalMessage").addClass("visible bad").find("p").html(t); else $(".globalMessage").addClass("visible").find("p").html(t); isShowingGlobalMessage=setTimeout(function() { $(".globalMessage").fadeOut(300,function() { $(".globalMessage").removeClass("bad visible").css("display","block"); isShowingGlobalMessage=null; }) }, isError ? 50000 : 3000); }, closeUnfoldingPanel: function() { var showScrollbarAgain=false; // if scrollbar is showing and is sticky hide it temporarily so we can show it in new position later after unfolding panel is resized if($(".myScrollBar.sticky").length) { $(".myScrollBar").fadeOut(300); // also set flag so in slideUp/Down callback we will iknow we need to show scrollbar again // this is quick workaround, puttin scrollbar in separate plugin should encapsulate this sort of stuff showScrollbarAgain=true; } $(".unfoldingPanel").slideUp(100,function(e) { $(".unfoldingPanelPointer .inner").removeClass("visible"); $(".unfoldingPanelPointer .outer").removeClass("visible"); $("#mainPanel").css("padding-top","0px"); framework.adjustWrapper(); if($(".myScrollBar.sticky").length && showScrollbarAgain) { // show scrollbar again $(".myScrollBar").css("top","39px").fadeIn(300); // exact height of menu bar when its sticky showScrollbarAgain=false; } $(".unfoldingPanel > section").hide(); }); }, adjustWrapper: adjustWrapper, closeAndOpenUnfoldingPanel: function(el,e) { // to avoid confusion with heights etc dont execute function if panel is animating if($(".unfoldingPanel > section:animated").length) return; // we need tomove pointer later under the clicked item in tools menu var c=$(e.currentTarget).attr("class"); // when someone switches between panels we want arrow to animate $(".unfoldingPanelPointer").removeClass("dontAnimate"); // as unfoldingPanel might be sticky by now and top padding adde to mainPanel to cater for // space unfoldingPanel covers we need to remove this padding if($(".unfoldingPanel.sticky").length) { $("#mainPanel").css("padding-top","0px"); } // the unfolding panel animation is very specific so panel initially slides down, then when user switches between search and filter (or vice versa) then the content of the panel needs to fade out and the whole panel needs to slide up or down depending if the content is more or less whet was previously showing // get height of new panel the one we want to show var previousPanelHeight=$(".unfoldingPanel > section:visible").height(); // if scrollbar is showing and is sticky hide it temporarily so we can show it in new position later after unfolding panel is resized var showScrollbarAgain=false; if($(".myScrollBar.sticky").length) { $(".myScrollBar").fadeOut(300); showScrollbarAgain=true; } $(".unfoldingPanel > section:visible").fadeOut(300,function(e) { // get height of new panel the one we want to show var newPanelHeight=$(".unfoldingPanel #"+el+"Panel").css({ "position":"absolute", "visibility":"hidden"} ).height(); // its not showing now, simply invisible so we animate it from 0 height to the height we just stored above $(".unfoldingPanel #"+el+"Panel") .css("position","static") .css("display","block") .css({ "opacity": 0, "visibility":"visible" }) .height(previousPanelHeight) .animate({ height: newPanelHeight, opacity: 1 },300,function() { // this is a bit redundant in this function but for now will do. Might need to move it to other place $("#searchPanel .searchBox input").val("").focus(); framework.resizeSearchPanel(); if($(".unfoldingPanel.sticky").length && showScrollbarAgain) { framework.adjustMainPanelTopPadding(); // if scrollbar needs to show then show in new position $(".myScrollBar").css("top",$(".unfoldingPanel").position().top+$(".unfoldingPanel").height()).fadeIn(300); showScrollbarAgain=false; } framework.adjustWrapper(); // also try to update all dropdown boxes as this might be needed // this might need to be passed somehow here into framework rather then calling it directly as below $(".selectEditable").multiDropDown("refresh"); }); }); $(".unfoldingPanelPointer").css("right",(framework.getOffsetRight($("nav li > a."+c))+$("nav li > a."+c).width()+17)+"px"); $(".unfoldingPanelPointer .inner").addClass("visible"); $(".unfoldingPanelPointer .outer").addClass("visible"); }, openUnfoldingPanel: function(el,e) { // we need tomove pointer later under the clicked item in tools menu var c=$(e.currentTarget).attr("class"); $(".unfoldingPanel > section").hide(); $(".unfoldingPanel #"+el+"Panel").show(); // if scrollbar is showing and is sticky hide it temporarily so we can show it in new position later after unfolding panel is resized var showScrollbarAgain=false; // manually check if scrollbar should be showing for this element. // NOT OPTIMAL SOLUTION WILL DO FOR NOW HOWEVER AS THATS ALL WE NEED FOR THE MOMENT. Later might implement public method into scrollbar plugin that would return if it shoul dbe displayed or not if($(".myScrollBar").data("scrollPane")) //if($(".myScrollBar").data("scrollPane").width()<$(".myScrollBar").data("scrollPane").find("> .scroll-content").width()) if($(".myScrollBar").customScrollBar("getProportion")<1) if($(".myScrollBar.sticky").length) { $(".myScrollBar").fadeOut(300); showScrollbarAgain=true; } $(".unfoldingPanel").slideDown(300,function() { if($(".unfoldingPanel.sticky").length) { framework.adjustMainPanelTopPadding(); // if scrollbar needs to show then show in new position if(showScrollbarAgain) $(".myScrollBar").css("top",$(".unfoldingPanel").position().top+$(".unfoldingPanel").height()).fadeIn(300); showScrollbarAgain=false; } framework.adjustWrapper(); // also try to update all dropdown boxes as this might be needed // this might need to be passed somehow here into framework rather then calling it directly as below $(".selectEditable").multiDropDown("refresh"); }); $(".unfoldingPanelPointer") // dont animate position when initially opening .addClass("dontAnimate") .css("right",(framework.getOffsetRight($("nav li > a."+c))+$("nav li > a."+c).width()+17)+"px"); $(".unfoldingPanelPointer .inner").addClass("visible"); $(".unfoldingPanelPointer .outer").addClass("visible"); }, adjustMainPanelTopPadding: function() { // if unfolded panel is open then we need to add that much space at the top of mainPanel // so its content will not be suddenly covered by unfoldingPanel which now will be // positioned as fixed and taken out of layout flow // get size of open unfoldingPanel and add it as padding to mainPanel // this is to avoid main panel content be covered when unfoldingPanel is unfolded and made sticky so position fixed // CALL THIS FUNCTION WHEN UNFOLDINGPANELS content (and probably the size) changes so mainPanel padding will be adjusted $("#mainPanel").css("padding-top",$(".unfoldingPanel:visible").height()+"px"); }, getOffsetRight: function(el) { // function returns distance from right edge as this cannot be read simply by jQuerys offset().right return ($("body").width() - ($(el).offset().left + $(el).outerWidth())); }, resizeSearchPanel: function() { /* temporary to resize searchbox, will do for now as the panel layout will not change and it has to be pixel perfect and need to adjust to the width of the window. If additional features are to be added this might also need to be recoded when layout changes are needed later on */ var spw=$("#searchPanel").width(); $("#searchPanel > .panelWrapper > .fields").width(spw-177); $(".searchBox").width(spw-256); $(".searchBox > input").width(spw-234); } } })(); var documentTree=(function() { // folding tree functionality // click/doubleclick when attahced to same element is very inconsistent in between browsers so we need to do it as below rather than relying on standard implementation // IE8 doesn't detect double click var timeout=false; var delay = 500; // Delay in milliseconds $(".tree").on("click","h2",function(e) { var clickedH2=this; // closing has to be fast and as we dont care about double click we simply close it without delay if(!$(clickedH2).parent().hasClass("closed")) { $(clickedH2).parent().find(">ul").slideUp(100,function() { framework.adjustWrapper(); $(clickedH2).parent().addClass("closed"); }); } else if(!timeout) { timeout=setTimeout(function() { // single click if($(clickedH2).parent().hasClass("closed")) { $(clickedH2).parent().removeClass("closed"); $(clickedH2).parent().find(">ul") .addClass("transparent") .slideDown(100,function() { // set numbers width to longest var longest=0; $(this).find(">li").each(function() { var w=$(this).find("> div > .number").width()+2; if(w>longest) longest=w; }) $(this).find(">li > div > .number").width(longest); $(this).removeClass("transparent").css({opacity: 0}).animate({opacity: 1},300); framework.adjustWrapper(); }); } else { $(clickedH2).parent().find(">ul").slideUp(100,function() { framework.adjustWrapper(); $(clickedH2).parent().addClass("closed"); }); } timeout=false; },delay); } else { // double click // load whole tree here via ajax from server and once loaded call code below so it can unfold all tree if($(clickedH2).parent().hasClass("closed")) { $(clickedH2).parent().removeClass("closed"); $(clickedH2).parent().find("li").removeClass("closed"); $(clickedH2).parent().find("ul").slideDown(100,function() { framework.adjustWrapper(); }); } clearTimeout(timeout); timeout=false; } }); // attach events to to display properties window attachTreeDatesEvents($(".tree .today,.tree .lastWeek,.tree .yesterday,.tree .fiveMonthsAgo")); function attachTreeDatesEvents(el) { /* function attaches events to date (oday, yesterday etc) in documwent index so these will open popup with properties when clicked, will dissapear after certain time etc. */ (function() { var active=null; var test=0; el.each(function() { // show properties window function attachPropertiesPopUp(t) { // get position of clicked date and position properties popup next to it var ol=$(t).offset().left var ot=$(t).offset().top $(".properties").css("left",ol+4).css("top",ot-6); // properties window has to animate in certain way so it has to unfold with content already positioned inside of it. Content cannot reflow as the width changes when properties popup unfolds. SO do do this we need to grab dimensions of inside container, and animate only outside as the inside container stays still being positioned absolutely // we need to clone properties window outside of LI and append it directly to body var propertiesHeight=$(t).find(".properties").clone().appendTo(".wrapper").addClass("visible").height(); // specify its height as the container must not reflow when properties animation plays var containerHeight=$(".wrapper > .properties > .container").height(); var containerWidth=$(".wrapper > .properties > .container").width(); $(".wrapper > .properties > .container").css("position","absolute").height(containerHeight); $(".wrapper > .properties > .container").css("position","absolute").width(containerWidth); // now we have to make sure the arrow is shown from the very beginning as the properties window starts to unfold // so we remove it from properties window and position in the same place on the screen so it looks exactly same $(".wrapper > .properties > .propertiesArrowRight").appendTo(".wrapper") .css("left",ol+18) .css("top",ot+2); $(".wrapper > .properties > .container").hide().fadeIn(500); // now arrow is appended to body and shown in the right place so we can start animate the properties window itself $(".wrapper > .properties").width(0).animate({opacity: 1, left: ol-290, width: 293, height: propertiesHeight},500,function() { // as we inserted new element in dom we need to attach events to it if(active!=null) clearTimeout(active); $(".wrapper > .properties").mouseover(function() { if(active!=null) clearTimeout(active); }); $(".wrapper > .properties").mouseout(function() { active=setTimeout(function() { $(".wrapper > .properties").fadeOut(100,function() { $(".wrapper > .properties,.wrapper > .propertiesArrowRight").remove(); }); }, 700); }); }); // mark this properties popup as being open so later when user clicks same date we can simply close it rather then close and open $(t).addClass("propertiesOpen"); } $(this).click(function(e) { if(active!=null) clearTimeout(active); // ############################### var t=this; // check if properties popup is already open if($(".wrapper > .properties").length) { // now check if thats the currently clicked date that is already open, if so simply close it and dont open new popup again if($(t).hasClass("propertiesOpen")) { $(".wrapper > .properties").fadeOut(100,function() { // remove flag $(t).removeClass("propertiesOpen"); // now its clossd $(".wrapper > .properties,.wrapper > .propertiesArrowRight").remove(); }); } // if its other date then close popup and open another one else { // now check if properties popup for this date is open, if not then open new popup as previous one was closed $(".wrapper > .properties").fadeOut(100,function() { // remove flag // this might be redone later as if the tree has many nodes it will search the whole thing for .propertiesOpen and with size of 20000 it might not be the fastes solution // one option would be storing some temporary variable specifying which one is the last open popup $(".propertiesOpen").removeClass("propertiesOpen"); $(".wrapper > .properties,.wrapper > .propertiesArrowRight").remove(); attachPropertiesPopUp(t); }); } } else { // display popup attachPropertiesPopUp(this); } //####################################### e.preventDefault(); }); $(this).mouseout(function() { active=setTimeout(closeDropDown, 700); test=active; }); $(this).mouseover(function() { if(active!=null) clearTimeout(active); }); function closeDropDown() { var t=this; $(".wrapper > .properties").fadeOut(100,function() { $(".wrapper > .properties,.wrapper > .propertiesArrowRight").remove(); $(t).removeClass("propertiesOpen") }); } }); })() } /* no need for public interface here return { attachTreeDatesEvents: attachTreeDatesEvents }*/ })(); var reportsDashboard=(function() { // init function init() { $(".reportsDashboardBoxes").css({opacity: 0}); framework.setSelectedMenuItem("reports"); // hide some of tools menu $($(".tools > li")[0]).fadeOut(300); $($(".tools > li")[1]).fadeOut(300); $($(".tools > li")[2]).fadeOut(300); // assign links to boxes so each will load pages on click // remove click event as this might be already buinded and every time will be called more and more times if we bind it again // later might use namespacing event if necessary $("a[data-page]").off("click"); $("a[data-page]").on("click",function(e) { var anchor=$(this).data("page"); window.location.hash=anchor; e.preventDefault(); }); } function showBoxes() { $(".reportsDashboardBoxes").animate({opacity:1},700); } return { init: init, showBoxes: showBoxes }; }()); var reportsDocuments=(function() { function init() { // set reports item in main menu as selected as this is subpage framework.setSelectedMenuItem("reports"); framework.removeAction(3); framework.addAction(4,"exportExcel","New window"); // for this page attach scrollbar provided by framework framework.attachScrollBar(); // show scrollbar as this will be hidden before any page loads // manually check if scrollbar should be showing for this element. // NOT OPTIMAL SOLUTION WILL DO FOR NOW HOWEVER AS THATS ALL WE NEED FOR THE MOMENT. Later might implement public method into scrollbar plugin that would return if it shoul dbe displayed or not //if($(".myScrollBar").data("scrollPane").width()<$(".myScrollBar").data("scrollPane").find("> .scroll-content").width()) if($(".myScrollBar").customScrollBar("getProportion")<1) $(".myScrollBar").fadeIn(300); // show all tools menu and then unfold panel $(".tools > li").show(300).promise().done(function() { // open filter panel $('a.filter').click(); }); } return { init:init }; }()); var reportsPopularItems=(function() { function init() { // set reports item in main menu as selected as this is subpage framework.setSelectedMenuItem("reports"); framework.removeAction(3); framework.addAction(4,"exportExcel","New window"); // show all tools menu and then unfold panel $(".tools > li").show(300).promise().done(function() { // open filter panel $('a.filter').click(); }); } return { init:init }; }()); var reportsUsers=(function() { function init() { // set reports item in main menu as selected as this is subpage framework.setSelectedMenuItem("reports"); framework.removeAction(3); framework.addAction(4,"exportExcel","New window"); // show all tools menu and then unfold panel $(".tools > li").show(300).promise().done(function() { // open filter panel $('a.filter').click(); }); } return { init:init }; }()); var reportsQuestions=(function() { function init() { // set reports item in main menu as selected as this is subpage framework.setSelectedMenuItem("reports"); framework.removeAction(3); framework.addAction(4,"exportExcel","New window"); // show all tools menu and then unfold panel $(".tools > li").show(300).promise().done(function() { // open filter panel $('a.filter').click(); }); } return { init:init }; }()); var reportsSecurity=(function() { function init() { // set reports item in main menu as selected as this is subpage framework.setSelectedMenuItem("reports"); framework.removeAction(3); framework.addAction(4,"exportExcel","New window"); // show all tools menu and then unfold panel $(".tools > li").show(300).promise().done(function() { // open filter panel $('a.filter').click(); }); } return { init:init }; }()); $(".someGroup,.someform1").tabGuard(); $("#dialog").tabGuard(); $("#fileChoser").multiDropDown( { multiSelect: true, editable: true, scrollingContainer: $("#dialog .mainContent")}); $(".selectEditable.documentsDropdown1").multiDropDown( { multiSelect: false, editable: false }); $(".selectEditable.documentsDropdown2").multiDropDown( { multiSelect: false, editable: false }); $(".selectEditable.documentsDropdown3").multiDropDown( { multiSelect: false, editable: false, onSelect: function() { alert("calendar goes here or something"); }}); $(".selectEditable.popularItemsDropdown1").multiDropDown( { multiSelect: false, editable: false }); $(".selectEditable.popularItemsDropdown2").multiDropDown( { multiSelect: false, editable: false }); $(".selectEditable.popularItemsDropdown3").multiDropDown( { multiSelect: false, editable: false, onSelect: function() { alert("calendar goes here or something"); }}); $(".selectEditable.questionsDropdown1").multiDropDown( { multiSelect: false, editable: false }); $(".selectEditable.questionsDropdown2").multiDropDown( { multiSelect: false, editable: false }); $(".selectEditable.questionsDropdown3").multiDropDown( { multiSelect: false, editable: false, onSelect: function() { alert("calendar goes here or something"); }}); $(".selectEditable.usersDropdown1").multiDropDown( { multiSelect: false, editable: false }); $(".selectEditable.usersDropdown2").multiDropDown( { multiSelect: false, editable: false }); $(".selectEditable.usersDropdown3").multiDropDown( { multiSelect: false, editable: false, onSelect: function() { alert("calendar goes here or something"); }}); $(".selectEditable.securityDropdown1").multiDropDown( { multiSelect: true, editable: false }); $(".selectEditable.securityDropdown2").multiDropDown( { multiSelect: true, editable: false }); // demo dropdowns only on administration page $(".selectEditable.demoDropdown1").multiDropDown( { multiSelect: false, editable: false }); $(".selectEditable.demoDropdown2").multiDropDown( { multiSelect: true, editable: false }); $(".selectEditable.demoDropdown3").multiDropDown( { multiSelect: true, editable: true }); $(".selectEditable.demoDropdown4").multiDropDown( { multiSelect: false, editable: true }); var documentsFilterPanel=(function() { // documents filter panel for things like opening lozenges from panel etc $("#documentsFilterPanel input[name=viewed]").change(function(e) { var currentlyShowing=$("ul.lozenges li[data-associated-filter=viewed]"); if($(this).val()=="viewed2") { $("ul.lozenges").append(framework.templateLozenges({ associatedFilter: "viewed", iconClass: "neverViewedOrModified", content: "Never viewed or modified since last viewed" })); $("ul.lozenges li").last().hide().show(300, function() { framework.hideLozenge(currentlyShowing); }); } else if($(this).val()=="viewed3") { $("ul.lozenges").append(framework.templateLozenges({ associatedFilter: "viewed", iconClass: "sinceLastLogin", content: "Added or modified since last login" })); $("ul.lozenges li").last().hide().show(300,function() { framework.hideLozenge(currentlyShowing); }); } else framework.hideLozenge(currentlyShowing); }); $("#documentsFilterPanel input[name=save]").change(function(e) { var currentlyShowing=$("ul.lozenges li[data-associated-filter=save]"); if($(this).val()=="save2") { $("ul.lozenges").append(framework.templateLozenges({ associatedFilter: "save", iconClass: "none", content: "Save allowed" })); $("ul.lozenges li").last().hide().show(300,function() { framework.hideLozenge(currentlyShowing); }); } else if($(this).val()=="save3") { $("ul.lozenges").append(framework.templateLozenges({ associatedFilter: "save", iconClass: "none", content: "Save denied" })); $("ul.lozenges li").last().hide().show(300,function() { framework.hideLozenge(currentlyShowing); }); } else framework.hideLozenge(currentlyShowing); }); $("#documentsFilterPanel input[name=print]").change(function(e) { var currentlyShowing=$("ul.lozenges li[data-associated-filter=print]"); if($(this).val()=="print2") { $("ul.lozenges").append(framework.templateLozenges({ associatedFilter: "print", iconClass: "none", content: "Print allowed" })); $("ul.lozenges li").last().hide().show(300,function() { framework.hideLozenge(currentlyShowing); }); } else if($(this).val()=="print3") { $("ul.lozenges").append(framework.templateLozenges({ associatedFilter: "print", iconClass: "none", content: "Print denied" })); $("ul.lozenges li").last().hide().show(300,function() { framework.hideLozenge(currentlyShowing); }); } else framework.hideLozenge(currentlyShowing); }); })(); // show global messages var a=0; $(".clickme1").click(function(e) { framework.showMessage("

    Your download is processing. We’ll email you a link when it is ready.

    "); return; }); $(".clickme2").click(function(e) { framework.showMessage("

    Ouch! There was a problem with your download. Please try again.",true); return; }); // attach dialog to elements // on advanced search click open dialog $(".advanced").dialog({ "onOpen" : function() { // when advanced search open copy content of basic searchbox into open dialog field $("#advancedSearch > input").val($("#searchPanel .searchBox > input").val()); // dont detect shortcuts for when the dialog is open $("body").addClass("noShortcuts"); }, //"onInsertHTML" : function() { $("#fileChoser").multiDropDown("refresh"); }, "id" : "#advancedSearch", "title" : "ADVANCED SEARCH", "okButton" : "SEARCH", "onOK" : function() { // again detect shortcuts when the dialog is close //////////console.log("OK advanced!!!"); $("body").removeClass("noShortcuts"); }, "onCancel" : function() { // again detect shortcuts when the dialog is close //////////console.log("advanced!!!"); $("body").removeClass("noShortcuts"); } }); $("a.preventDefault").on("click",function(e) { e.preventDefault(); }); $(".help").dialog({ "id" : "#dummyContent", "title" : "HELP", "okButton" : "OK", "onOK" : function() { // again detect shortcuts when the dialog is close //////////console.log(" OK help!!!"); $("body").removeClass("noShortcuts"); }, "onCancel" : function() { // again detect shortcuts when the dialog is close //////////console.log("help!!!"); $("body").removeClass("noShortcuts"); } }); // adding single item to document tree /* var templateDocumentIndexItem=Handlebars.compile($("#handlebarsDocumentIndexItem").html()); var diItem={ id: "someUniqueId", // id so we can attach event to this particular element later format: "ppt", number: "99.99", date: "today", title: "This is document inserted via handlebars", added: "8 June 2012 at 1.34", updated: "1 July 2012 at 13:47" }; $($(".tree > ul > li > ul > li > ul")[0]).append(templateDocumentIndexItem(diItem)); attachTreeDatesEvents($(".tree #"+diItem.id+" .today, .tree #"+diItem.id+" .lastWeek, .tree #"+diItem.id+" .yesterday, .tree #"+diItem.id+" .fiveMonthsAgo")); */ }); /* as IE10 dosent use conditional coments we apply special class when IE10 is detected and target it from css */ if (Function('/*@cc_on return document.documentMode===10@*/')()){ document.documentElement.className+=' ie10'; }