diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 47cd5a8e9..dc4e66cd3 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -240,7 +240,7 @@ abstract class cmdbAbstractObject extends CMDBObject 'object_id' => $this->GetKey(), 'target_attr' => $oAttDef->GetExtKeyToRemote(), 'view_link' => false, - 'menu' => false, + 'menu' => true, 'display_limit' => true, // By default limit the list to speed up the initial load & display ); } @@ -393,6 +393,7 @@ abstract class cmdbAbstractObject extends CMDBObject } } $bDisplayMenu = isset($aExtraParams['menu']) ? $aExtraParams['menu'] == true : true; + $bTruncated = isset($aExtraParams['truncated']) ? $aExtraParams['truncated'] == true : true; $bSelectMode = isset($aExtraParams['selection_mode']) ? $aExtraParams['selection_mode'] == true : false; $bSingleSelectMode = isset($aExtraParams['selection_type']) ? ($aExtraParams['selection_type'] == 'single') : false; $aExtraFields = isset($aExtraParams['extra_fields']) ? explode(',', trim($aExtraParams['extra_fields'])) : array(); @@ -460,7 +461,7 @@ abstract class cmdbAbstractObject extends CMDBObject $aValues = array(); $bDisplayLimit = isset($aExtraParams['display_limit']) ? $aExtraParams['display_limit'] : true; $iMaxObjects = -1; - if ($bDisplayLimit) + if ($bDisplayLimit && $bTruncated) { if ($oSet->Count() > utils::GetConfig()->GetMaxDisplayLimit()) { @@ -501,26 +502,67 @@ abstract class cmdbAbstractObject extends CMDBObject } $sHtml .= ''; $sColspan = ''; - if ($bDisplayLimit && ($oSet->Count() > utils::GetConfig()->GetMaxDisplayLimit())) + $divId = $aExtraParams['block_id']; + $sFilter = $oSet->GetFilter()->serialize(); + $iMinDisplayLimit = utils::GetConfig()->GetMinDisplayLimit(); + $sCollapsedLabel = Dict::Format('UI:TruncatedResults', $iMinDisplayLimit, $oSet->Count()); + $sLinkLabel = Dict::S('UI:DisplayAll'); + foreach($oSet->GetFilter()->GetInternalParams() as $sName => $sValue) + { + $aExtraParams['query_params'][$sName] = $sValue; + } + if ($bDisplayLimit && $bTruncated && ($oSet->Count() > utils::GetConfig()->GetMaxDisplayLimit())) { // list truncated - $divId = $aExtraParams['block_id']; - $sFilter = $oSet->GetFilter()->serialize(); - $aExtraParams['display_limit'] = false; // To expand the full list - foreach($oSet->GetFilter()->GetInternalParams() as $sName => $sValue) - { - $aExtraParams['query_params'][$sName] = $sValue; - } - $sExtraParams = addslashes(str_replace('"', "'", json_encode($aExtraParams))); // JSON encode, change the style of the quotes and escape them - $sHtml .= '
'.Dict::Format('UI:TruncatedResults', utils::GetConfig()->GetMinDisplayLimit(), $oSet->Count()).'  '.Dict::S('UI:DisplayAll').''; - $oPage->add_ready_script("$('#{$divId} table.listResults').addClass('truncated');"); - $oPage->add_ready_script("$('#{$divId} table.listResults tr:last td').addClass('truncated');"); + $aExtraParams['display_limit'] = true; + $sHtml .= '
'.$sCollapsedLabel.'  '.$sLinkLabel.''; + $oPage->add_ready_script( +<<Count() > utils::GetConfig()->GetMaxDisplayLimit())) + { + // Collapsible list + $aExtraParams['display_limit'] = true; + $sHtml .= '
'.Dict::Format('UI:CountOfResults', $oSet->Count()).''.Dict::S('UI:CollapseList').''; + } + $aExtraParams['truncated'] = false; // To expand the full list when clicked + $sExtraParamsExpand = addslashes(str_replace('"', "'", json_encode($aExtraParams))); // JSON encode, change the style of the quotes and escape them + $oPage->add_ready_script( +<<Count()).''; + state[ this.id ] = 'close'; } + $.bbq.pushState( state ); + $(this).trigger(state[this.id]); + }); + + $('#trc_$divId').bind('open', function() + { + ReloadTruncatedList('$divId', '$sFilter', '$sExtraParamsExpand'); + }); + + $('#trc_$divId').bind('close', function() + { + TruncateList('$divId', $iMinDisplayLimit, '$sCollapsedLabel', '$sLinkLabel'); + }); +EOF +); if ($bDisplayMenu) { $oMenuBlock = new MenuBlock($oSet->GetFilter()); diff --git a/application/itopwebpage.class.inc.php b/application/itopwebpage.class.inc.php index 18d656f07..396443134 100644 --- a/application/itopwebpage.class.inc.php +++ b/application/itopwebpage.class.inc.php @@ -53,7 +53,7 @@ class iTopWebPage extends NiceWebPage $this->add_linked_stylesheet("../css/jquery.autocomplete.css"); // $this->add_linked_stylesheet("../css/date.picker.css"); $this->add_linked_script('../js/jquery.layout.min.js'); - $this->add_linked_script('../js/jquery.history.js'); + $this->add_linked_script('../js/jquery.ba-bbq.min.js'); // $this->add_linked_script("../js/jquery.dimensions.js"); $this->add_linked_script("../js/jquery.tablehover.js"); $this->add_linked_script("../js/jquery.treeview.js"); @@ -148,12 +148,73 @@ class iTopWebPage extends NiceWebPage $("tbody tr.orange:even",table).removeClass('orange').removeClass('even').addClass('orange_even'); $("tbody tr.green:even",table).removeClass('green').removeClass('even').addClass('green_even'); } - }); + }); - // tabs - $("div[id^=tabbedContent]").tabs( { show: function(event, ui) { - window.location.href = ui.tab.href; // So that history can keep track of the tabs - } }); + // Tabs, using JQuery BBQ to store the history + // The "tab widgets" to handle. + var tabs = $('div[id^=tabbedContent]'); + + // This selector will be reused when selecting actual tab widget A elements. + var tab_a_selector = 'ul.ui-tabs-nav a'; + + // Enable tabs on all tab widgets. The `event` property must be overridden so + // that the tabs aren't changed on click, and any custom event name can be + // specified. Note that if you define a callback for the 'select' event, it + // will be executed for the selected tab whenever the hash changes. + tabs.tabs({ event: 'change' }); + + // Define our own click handler for the tabs, overriding the default. + tabs.find( tab_a_selector ).click(function() + { + var state = {}; + + // Get the id of this tab widget. + var id = $(this).closest( 'div[id^=tabbedContent]' ).attr( 'id' ); + + // Get the index of this tab. + var idx = $(this).parent().prevAll().length; + + // Set the state! + state[ id ] = idx; + $.bbq.pushState( state ); + }); + + // Bind an event to window.onhashchange that, when the history state changes, + // iterates over all tab widgets, changing the current tab as necessary. + $(window).bind( 'hashchange', function(e) + { + // Iterate over all tab widgets. + tabs.each(function() + { + // Get the index for this tab widget from the hash, based on the + // appropriate id property. In jQuery 1.4, you should use e.getState() + // instead of $.bbq.getState(). The second, 'true' argument coerces the + // string value to a number. + var idx = $.bbq.getState( this.id, true ) || 0; + + // Select the appropriate tab for this tab widget by triggering the custom + // event specified in the .tabs() init above (you could keep track of what + // tab each widget is on using .data, and only select a tab if it has + // changed). + $(this).find( tab_a_selector ).eq( idx ).triggerHandler( 'change' ); + }); + + // Iterate over all truncated lists to find whether they are expanded or not + $('a.truncated').each(function() + { + var state = $.bbq.getState( this.id, true ) || 'close'; + if (state == 'open') + { + $(this).trigger('open'); + } + else + { + $(this).trigger('close'); + } + }); + }); + + // End of Tabs handling $("table.listResults").tableHover(); // hover tables $(".listResults").tablesorter( { headers: { 0:{sorter: false }}, widgets: ['myZebra', 'truncatedList']} ); // sortable and zebra tables $(".date-pick").datepicker({ @@ -170,12 +231,12 @@ class iTopWebPage extends NiceWebPage $('#ModalDlg').dialog({ autoOpen: false, modal: true, width: 0.8*docWidth }); // JQuery UI dialogs ShowDebug(); $('#logOffBtn>ul').popupmenu(); - $.history.init(history_callback); - $("a[rel='history']").click(function() - { - $.history.load(this.href.replace(/^.*#/, '')); - return false; - }); +// $.history.init(history_callback); +// $("a[rel='history']").click(function() +// { +// $.history.load(this.href.replace(/^.*#/, '')); +// return false; +// }); } catch(err) { @@ -189,17 +250,17 @@ EOF $sUserPrefs = appUserPreferences::GetAsJSON(); $this->add_script( <<DisplayMenu(); // Compute the menu + + // Put here the 'ready scripts' that must be executed after all others + $this->add_ready_script( +<<a_headers as $s_header) { header($s_header); diff --git a/dictionaries/dictionary.itop.ui.php b/dictionaries/dictionary.itop.ui.php index d7fb9d5ea..e33fdcd50 100644 --- a/dictionaries/dictionary.itop.ui.php +++ b/dictionaries/dictionary.itop.ui.php @@ -412,6 +412,7 @@ Dict::Add('EN US', 'English', 'English', array( 'UI:SelectAllToggle+' => 'Select / Deselect All', 'UI:TruncatedResults' => '%1$d objects displayed out of %2$d', 'UI:DisplayAll' => 'Display All', + 'UI:CollapseList' => 'Collapse', 'UI:CountOfResults' => '%1$d object(s)', 'UI:ChangesLogTitle' => 'Changes log (%1$d):', 'UI:EmptyChangesLogTitle' => 'Changes log is empty', diff --git a/dictionaries/fr.dictionary.itop.ui.php b/dictionaries/fr.dictionary.itop.ui.php index 182737318..5440a196d 100644 --- a/dictionaries/fr.dictionary.itop.ui.php +++ b/dictionaries/fr.dictionary.itop.ui.php @@ -412,6 +412,7 @@ Dict::Add('FR FR', 'French', 'Français', array( 'UI:SelectAllToggle+' => 'Tout Sélectionner / Tout Désélectionner', 'UI:TruncatedResults' => '%1$d objets affichés sur %2$d', 'UI:DisplayAll' => 'Tout afficher', + 'UI:CollapseList' => 'Refermer', 'UI:CountOfResults' => '%1$d objet(s)', 'UI:ChangesLogTitle' => 'Liste de modifications (%1$d):', 'UI:EmptyChangesLogTitle' => 'Aucune modification', diff --git a/js/jquery.ba-bbq.min.js b/js/jquery.ba-bbq.min.js new file mode 100644 index 000000000..bcbf24834 --- /dev/null +++ b/js/jquery.ba-bbq.min.js @@ -0,0 +1,18 @@ +/* + * jQuery BBQ: Back Button & Query Library - v1.2.1 - 2/17/2010 + * http://benalman.com/projects/jquery-bbq-plugin/ + * + * Copyright (c) 2010 "Cowboy" Ben Alman + * Dual licensed under the MIT and GPL licenses. + * http://benalman.com/about/license/ + */ +(function($,p){var i,m=Array.prototype.slice,r=decodeURIComponent,a=$.param,c,l,v,b=$.bbq=$.bbq||{},q,u,j,e=$.event.special,d="hashchange",A="querystring",D="fragment",y="elemUrlAttr",g="location",k="href",t="src",x=/^.*\?|#.*$/g,w=/^.*\#/,h,C={};function E(F){return typeof F==="string"}function B(G){var F=m.call(arguments,1);return function(){return G.apply(this,F.concat(m.call(arguments)))}}function n(F){return F.replace(/^[^#]*#?(.*)$/,"$1")}function o(F){return F.replace(/(?:^[^?#]*\?([^#]*).*$)?.*/,"$1")}function f(H,M,F,I,G){var O,L,K,N,J;if(I!==i){K=F.match(H?/^([^#]*)\#?(.*)$/:/^([^#?]*)\??([^#]*)(#?.*)/);J=K[3]||"";if(G===2&&E(I)){L=I.replace(H?w:x,"")}else{N=l(K[2]);I=E(I)?l[H?D:A](I):I;L=G===2?I:G===1?$.extend({},I,N):$.extend({},N,I);L=a(L);if(H){L=L.replace(h,r)}}O=K[1]+(H?"#":L||!K[1]?"?":"")+L+J}else{O=M(F!==i?F:p[g][k])}return O}a[A]=B(f,0,o);a[D]=c=B(f,1,n);c.noEscape=function(G){G=G||"";var F=$.map(G.split(""),encodeURIComponent);h=new RegExp(F.join("|"),"g")};c.noEscape(",/");$.deparam=l=function(I,F){var H={},G={"true":!0,"false":!1,"null":null};$.each(I.replace(/\+/g," ").split("&"),function(L,Q){var K=Q.split("="),P=r(K[0]),J,O=H,M=0,R=P.split("]["),N=R.length-1;if(/\[/.test(R[0])&&/\]$/.test(R[N])){R[N]=R[N].replace(/\]$/,"");R=R.shift().split("[").concat(R);N=R.length-1}else{N=0}if(K.length===2){J=r(K[1]);if(F){J=J&&!isNaN(J)?+J:J==="undefined"?i:G[J]!==i?G[J]:J}if(N){for(;M<=N;M++){P=R[M]===""?O.length:R[M];O=O[P]=M').hide().insertAfter("body")[0].contentWindow;q=function(){return a(n.document[c][l])};o=function(u,s){if(u!==s){var t=n.document;t.open().close();t[c].hash="#"+u}};o(a())}}m.start=function(){if(r){return}var t=a();o||p();(function s(){var v=a(),u=q(t);if(v!==t){o(t=v,u);$(i).trigger(d)}else{if(u!==t){i[c][l]=i[c][l].replace(/#.*/,"")+"#"+u}}r=setTimeout(s,$[d+"Delay"])})()};m.stop=function(){if(!n){r&&clearTimeout(r);r=0}};return m})()})(jQuery,this); \ No newline at end of file diff --git a/js/utils.js b/js/utils.js index ccb8f0784..d7dd9344d 100644 --- a/js/utils.js +++ b/js/utils.js @@ -18,6 +18,24 @@ function ReloadTruncatedList(divId, sSerializedFilter, sExtraParams) } ); } +/** + * Truncate a previously expanded list ! + */ +function TruncateList(divId, iLimit, sNewLabel, sLinkLabel) +{ + var iCount = 0; + $('#'+divId+' table.listResults tr').each( function(){ + if (iCount > iLimit) + { + $(this).remove(); + } + iCount++; + }); + $('#lbl_'+divId).html(sNewLabel); + $('#'+divId+' table.listResults tr:last td').addClass('truncated'); + $('#'+divId+' table.listResults').addClass('truncated'); + $('#trc_'+divId).html(sLinkLabel); +} /** * Reload any block -- used for periodic auto-reload */