// Licensed to the Apache Software Foundation (ASF) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The ASF licenses this file // to you under the Apache License, Version 2.0 (the // "License"); you may not use this file except in compliance // with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. (function($, cloudStack) { cloudStack.ui.widgets.browser = {}; /** * Breadcrumb-related functions */ var _breadcrumb = cloudStack.ui.widgets.browser.breadcrumb = { /** * Generate new breadcrumb */ create: function($panel, title) { // Attach panel as ref for breadcrumb return cloudStack.ui.event.elem( 'cloudBrowser', 'breadcrumb', $('
') .append( $('
  • ') .attr({ title: title }) .append( $('').html(title) ) ) .append($('
    ').addClass('end')) .children(), { panel: $panel } ); }, /** * Get breadcrumbs matching specified panels */ filter: function($panels) { var $breadcrumbs = $('#breadcrumbs ul li'); var $result = $([]); $panels.each(function() { var $panel = $(this); $.merge( $result, $.merge( $breadcrumbs.filter(function() { return $(this).index('#breadcrumbs ul li') == $panel.index(); }), // Also include ends $breadcrumbs.siblings('div.end').filter(function() { return $(this).index('div.end') == $panel.index() + 1; }) ) ); }); return $result; } }; /** * Container-related functions */ var _container = cloudStack.ui.widgets.browser.container = { /** * Get all panels from container */ panels: function($container) { return $container.find('div.panel'); } }; /** * Panel-related functions */ var _panel = cloudStack.ui.widgets.browser.panel = { /** * Compute width of panel, relative to container */ width: function($container, options) { options = options ? options : {}; var width = $container.find('div.panel').size() < 1 || options.maximized == true ? $container.width() : $container.width() - $container.width() / 4; return width; }, /** * Get left position */ position: function($container, options) { return $container.find('div.panel').size() <= 1 || options.maximized == true ? 0 : _panel.width($container, options) - _panel.width($container, options) / 1.5; }, /** * Get the top panel z-index, for proper stacking */ topIndex: function($container) { var base = 50; // Minimum z-index return Math.max.apply( null, $.map( $container.find('div.panel'), function(elem) { return parseInt($(elem).css('z-index')) || base; } ) ) + 1; }, /** * State when panel is outside container */ initialState: function($container) { return { left: $container.width() }; }, /** * Get panel and breadcrumb behind specific panel */ lower: function($container, $panel) { return _container.panels($container).filter(function() { return $(this).index() < $panel.index(); }); }, /** * Get panel and breadcrumb stacked above specific panel */ higher: function($container, $panel) { return _container.panels($container).filter(function() { return $(this).index() > $panel.index(); }); }, /** * Generate new panel */ create: function($container, options) { var $panel = $('
    ').addClass('panel').css( { position: 'absolute', width: _panel.width($container, { maximized: options.maximized }), zIndex: _panel.topIndex($container) } ).append( // Shadow $('
    ').addClass('shadow') ).append(options.data); if (options.maximized) $panel.addClass('always-maximized'); return $panel; } }; /** * Browser -- jQuery widget */ $.widget('cloudStack.cloudBrowser', { _init: function() { this.element.addClass('cloudStack-widget cloudBrowser'); $('#breadcrumbs').append( $('
      ') ); }, /** * Make target panel the top-most */ selectPanel: function(args) { var $panel = args.panel; var $container = this.element; var $toShow = _panel.lower($container, $panel); var $toRemove = _panel.higher($container, $panel); var complete = args.complete; if ($panel.hasClass('maximized')) return false; _breadcrumb.filter($toRemove).remove(); _breadcrumb.filter($panel.siblings()).removeClass('active'); _breadcrumb.filter($panel).addClass('active'); _breadcrumb.filter($('div.panel')).find('span').animate({ opacity: 1 }); _breadcrumb.filter( $('div.panel.maximized') .removeClass('maximized') .addClass('reduced') ).removeClass('active maximized'); $toRemove.animate( _panel.initialState($container), { duration: 500, complete: function() { $(this).remove(); if (complete) complete($toShow); } } ); $toShow.show(); $panel.animate({ left: _panel.position($container, { maximized: $panel.hasClass('always-maximized') }) }); $panel.show().removeClass('reduced'); }, /** * Toggle selected panel as fully expanded, hiding/showing other panels */ toggleMaximizePanel: function(args) { var $panel = args.panel; var $container = this.element; var $toHide = $panel.siblings(':not(.always-maximized)'); var $shadow = $toHide.find('div.shadow'); if (args.panel.hasClass('maximized')) { _breadcrumb.filter($panel).removeClass('maximized'); $panel.removeClass('maximized'); $panel.addClass('reduced'); _breadcrumb.filter($panel.siblings()).find('span').animate({ opacity: 1 }); $toHide.animate({ left: _panel.position($container, {}) }, { duration: 500 }); $shadow.show(); } else { _breadcrumb.filter($panel).addClass('maximized'); $panel.removeClass('reduced'); $panel.addClass('maximized'); _breadcrumb.filter($panel.siblings()).find('span').animate({ opacity: 0.5 }); $toHide.animate(_panel.initialState($container), { duration: 500 }); $shadow.hide(); } }, /** * Append new panel to end of container */ addPanel: function(args) { var duration = args.duration ? args.duration : 500; var $container = this.element; var $parent = args.parent; var $panel, $reduced, targetPosition; // Create panel $panel = _panel.create(this.element, { maximized: args.maximizeIfSelected, data: args.data }); // Remove existing panels from parent if ($parent) { // Cleanup transitioning panels -- prevent old complete actions from running $parent.siblings().stop(); _breadcrumb.filter( $('div.panel.maximized') .removeClass('maximized') .addClass('reduced') ).removeClass('active maximized'); $parent.removeClass('maximized'); _breadcrumb.filter($parent.next()).remove(); $container.find($parent.next()).remove(); } // Append panel $panel.appendTo($container); _breadcrumb.filter($panel.siblings()).removeClass('active'); _breadcrumb.create($panel, args.title) .addClass('active') .appendTo('#breadcrumbs ul'); // Reduced appearance for previous panels $panel.siblings().filter(function() { return $(this).index() < $panel.index(); }).addClass('reduced'); // Panel initial state if ($panel.index() == 0) $panel.addClass('always-maximized'); $panel.css( _panel.initialState($container, $panel) ); // Panel slide-in targetPosition = _panel.position($container, { maximized: args.maximizeIfSelected }); if (!$panel.index()) { // Just show immediately if this is the first panel $panel.css( { left: targetPosition } ); if (args.complete) args.complete($panel, _breadcrumb.filter($panel)); } else { // Animate slide-in $panel.animate({ left: targetPosition }, { duration: duration, easing: 'easeOutCirc', complete: function() { // Hide panels $panel.siblings().filter(function() { return $(this).width() == $panel.width(); }); if ($panel.is(':visible') && args.complete) args.complete($panel); } }); }; return $panel; }, /** * Clear all panels */ removeAllPanels: function(args) { $('div.panel').stop(); // Prevent destroyed panels from animating this.element.find('div.panel').remove(); $('#breadcrumbs').find('ul li').remove(); $('#breadcrumbs').find('ul div.end').remove(); } }); $('#breadcrumbs li').live('click', cloudStack.ui.event.bind( 'cloudBrowser', { 'breadcrumb': function($target, $browser, data) { if ($ ('#browser').hasClass('panel-highlight')) { return false; } $browser.cloudBrowser('selectPanel', { panel: data.panel }); } } )); // Breadcrumb hovering $('#breadcrumbs li').live('mouseover', cloudStack.ui.event.bind( 'cloudBrowser', { 'breadcrumb': function($target, $browser, data) { var $hiddenPanels = data.panel.siblings().filter(function(){ return $(this).index() > data.panel.index(); }); var $targetPanel = data.panel.filter(':first'); var $targetBreadcrumb = _breadcrumb.filter($targetPanel); var $panelWrapper = $('
      ').addClass('panel panel-highlight-wrapper'); $hiddenPanels.addClass('mouseover-hidden'); $browser.data('browser-panel-highlight-timer', setTimeout(function() { $('#browser').addClass('panel-highlight'); $('.overlay').remove(); // Setup panel and wrapper positioning $panelWrapper .css({ left: $targetPanel.position().left }) .width($targetPanel.width()); $targetPanel .wrap($panelWrapper); $panelWrapper .zIndex(10000) .overlay(); $targetPanel.filter(':last').addClass('highlighted'); // Setup breadcrumbs $targetBreadcrumb.each(function() { $(this).data('breadcrumb-original-zindex', $(this).zIndex()); }); $targetBreadcrumb.zIndex(10001); $hiddenPanels.hide(); }, 1000)); } } )); $('#breadcrumbs li').live('mouseout',cloudStack.ui.event.bind( 'cloudBrowser', { 'breadcrumb': function($target, $browser, data) { var $getHiddenPanels = $browser.find('.panel.mouseover-hidden'); var $visiblePanels = $getHiddenPanels.siblings(); var $visibleBreadcrumbs = _breadcrumb.filter($visiblePanels); clearTimeout($browser.data('browser-panel-highlight-timer')); $('#browser').removeClass('panel-highlight'); $('#browser .panel').removeClass('highlighted'); $('#browser .panel.panel-highlight-wrapper').each(function() { var $wrapper = $(this); var $panel = $wrapper.find('.panel'); $wrapper.after($panel); $wrapper.remove(); }); $getHiddenPanels.removeClass('mouseover-hidden').show(); $visibleBreadcrumbs.each(function() { $(this).zIndex($(this).data('breadcrumb-original-zindex')); }); $('.overlay').remove(); $('#browser .panel > .highlight-arrow').remove(); } } )); })(jQuery, cloudStack);