/* ----------------------------------------------------------------- Script: mocha.js version 0.8 Copyright: Copyright (c) 2007-2008 Greg Houston, License: MIT-style license Contributors: Scott F. Frederick Joel Lindau ----------------------------------------------------------------- */ var MochaUI = new Class({ options: { // Global options for windows: // Some of these options can be overriden for individual windows in newWindow() resizable: true, draggable: true, minimizable: true, // Requires dock maximizable: true, // Requires desktop closable: true, effects: true, // Toggles the majority of window fade and move effects minWidth: 250, // Minimum width of windows when resized maxWidth: 2500, // Maximum width of windows when resized minHeight: 100, // Minimum height of windows when resized maxHeight: 2000, // Maximum height of windows when resized // Style options: headerHeight: 25, // Height of window titlebar footerHeight: 26, cornerRadius: 9, bodyBgColor: '#fff', // Body background color - Hex headerStartColor: [250, 250, 250], // Header gradient's top color - RGB headerStopColor: [228, 228, 228], // Header gradient's bottom color footerBgColor: [246, 246, 246], // Background color of the main canvas shape minimizeColor: [231, 231, 209], // Minimize button color maximizeColor: [217, 229, 217], // Maximize button color closeColor: [229, 217, 217], // Close button color resizableColor: [209, 209, 209], // Resizable icon color // Cascade options: desktopTopOffset: 20, // Use a negative number if neccessary to place first window where you want it desktopLeftOffset: 290, mochaTopOffset: 70, // Initial vertical spacing of each window mochaLeftOffset: 70, // Initial horizontal spacing of each window // Naming options: // If you change the IDs of the Mocha Desktop containers in your HTML, you need to change them here as well. desktop: 'mochaDesktop', desktopHeader: 'mochaDesktopHeader', desktopNavBar: 'mochaDesktopNavbar', pageWrapper: 'mochaPageWrapper', dock: 'mochaDock' }, initialize: function(options){ this.setOptions(options); // Private properties this.ieSupport = 'excanvas' // Makes it easier to switch between Excanvas and Moocanvas for testing this.indexLevel = 1; // Used for z-Index this.windowIDCount = 0; this.myTimer = ''; // Used with accordian this.iconAnimation = ''; // Used with loading icon this.mochaControlsWidth = 0; this.minimizebuttonX = 0; // Minimize button horizontal position this.maximizebuttonX = 0; // Maximize button horizontal position this.closebuttonX = 0; // Close button horizontal position this.shadowWidth = 3; this.shadowOffset = this.shadowWidth * 2; this.HeaderFooterShadow = this.options.headerHeight + this.options.footerHeight + this.shadowOffset; this.desktop = $(this.options.desktop); this.desktopHeader = $(this.options.desktopHeader); this.desktopNavBar = $(this.options.desktopNavBar); this.pageWrapper = $(this.options.pageWrapper); this.dock = $(this.options.dock); this.dockVisible = this.dock ? true : false; // True when dock is visible, false when not this.dockAutoHide = false; // True when dock autohide is set to on, false if set to off if ( this.dock ) { this.initializeDock(); } this.setDesktopSize(); this.newWindowsFromXHTML(); this.modalInitialize(); this.menuInitialize(); // Resize desktop, page wrapper, modal overlay, and maximized windows when browser window is resized window.onresize = function(){ this.onBrowserResize(); }.bind(this); }, menuInitialize: function(){ // Fix for dropdown menus in IE6 if (Browser.Engine.trident4 && this.desktopNavBar){ this.desktopNavBar.getElements('li').each(function(element) { element.addEvent('mouseenter', function(){ this.addClass('ieHover'); }) element.addEvent('mouseleave', function(){ this.removeClass('ieHover'); }) }) }; }, modalInitialize: function(){ var modalOverlay = new Element('div', { 'id': 'mochaModalOverlay', 'styles': { 'height': document.getCoordinates().height } }); modalOverlay.injectInside(this.desktop ? this.desktop : document.body); modalOverlay.setStyle('opacity', .4); this.modalOpenMorph = new Fx.Morph($('mochaModalOverlay'), { 'duration': 200 }); this.modalCloseMorph = new Fx.Morph($('mochaModalOverlay'), { 'duration': 200, onComplete: function(){ $('mochaModalOverlay').setStyle('display', 'none'); }.bind(this) }); }, onBrowserResize: function(){ this.setDesktopSize(); this.setModalSize(); // Resize maximized windows to fit new browser window size setTimeout( function(){ $$('div.mocha').each(function(el){ if (el.isMaximized) { var iframe = this.getSubElement(el, 'iframe'); if ( iframe ) { iframe.setStyle('visibility', 'hidden'); } var windowDimensions = document.getCoordinates(); var contentWrapper = this.getSubElement(el, 'contentWrapper'); contentWrapper.setStyles({ 'height': (windowDimensions.height - this.options.headerHeight - this.options.footerHeight), 'width': windowDimensions.width }); this.drawWindow(el); if ( iframe ) { iframe.setStyles({ 'height': contentWrapper.getStyle('height') }); iframe.setStyle('visibility', 'visible'); } } }.bind(this)); }.bind(this), 100); }, newWindowsFromXHTML: function(properties, cascade){ $$('div.mocha').each(function(el, i) { // Get the window title and destroy that element, so it does not end up in window content if ( Browser.Engine.presto || Browser.Engine.trident5 ) el.setStyle('display','block'); // Required by Opera, and probably IE7 var title = el.getElement('h3.mochaTitle'); var elDimensions = el.getStyles('height', 'width'); var properties = { id: el.getProperty('id'), height: elDimensions.height.toInt(), width: elDimensions.width.toInt() } // If there is a title element, set title and destroy the element so it does not end up in window content if ( title ) { properties.title = title.innerHTML; title.destroy(); } /* // Make sure there are no null values for(var key in properties) { if ( !properties[key] ) delete properties[key]; } */ // Get content and destroy the element properties.content = el.innerHTML; el.destroy(); // Create window this.newWindow(properties, true); }.bind(this)); this.arrangeCascade(); }, /* Method: newWindowsFromJSON Description: Create one or more windows from JSON data. You can define all the same properties as you can for newWindow. Undefined properties are set to their defaults. */ newWindowsFromJSON: function(properties){ properties.each(function(properties) { this.newWindow(properties); }.bind(this)); }, /* Method: newWindow Arguments: properties cascade - boolean - this is set to true for windows parsed from the original XHTML */ newWindow: function(properties, cascade){ var windowProperties = $extend({ id: null, title: 'New Window', loadMethod: 'html', // html, xhr, or iframe content: '', // used if loadMethod is set to 'html' contentURL: 'pages/lipsum.html', // used if loadMethod is set to 'xhr' or 'iframe' modal: false, width: 300, height: 125, x: null, y: null, scrollbars: true, draggable: this.options.draggable, resizable: this.options.resizable, minimizable: this.options.minimizable, maximizable: this.options.maximizable, closable: this.options.closable, // Styling paddingVertical: 10, paddingHorizontal: 12, bodyBgColor: this.options.bodyBgColor, headerStartColor: this.options.headerStartColor, // Header gradient's top color headerStopColor: this.options.headerStopColor, // Header gradient's bottom color footerBgColor: this.options.footerBgColor, // Background color of the main canvas shape minimizeColor: this.options.minimizeColor, // Minimize button color maximizeColor: this.options.maximizeColor, // Maximize button color closeColor: this.options.closeColor, // Close button color resizableColor: this.options.resizableColor, // Resizable icon color // Events onContentLoaded: $empty, // Event, fired when content is successfully loaded via XHR onFocus: $empty, // Event, fired when the window is focused onResize: $empty, // Event, fired when the window is resized onMinimize: $empty, // Event, fired when the window is minimized onMaximize: $empty, // Event, fired when the window is maximized onClose: $empty, // Event, fired just before the window is closed onCloseComplete: $empty // Event, fired after the window is closed }, properties || {}); var windowEl = $(windowProperties.id); // Check if window already exists and is not in progress of closing down if ( windowEl && !windowEl.isClosing ) { if ( windowEl.isMinimized ) // If minimized -> restore this.restoreMinimized(windowEl); else // else focus setTimeout(function(){ this.focusWindow(windowEl); }.bind(this),10); return; } // Create window div var windowEl = new Element('div', { 'class': 'mocha', 'id': windowProperties.id && windowProperties.id != null ? windowProperties.id : 'win' + (++this.windowIDCount), 'styles': { 'width': windowProperties.width, 'height': windowProperties.height, 'display': 'block' } }); // Part of fix for scrollbar issues in Mac FF2 if (Browser.Platform.mac && Browser.Engine.gecko){ windowEl.setStyle('position', 'fixed'); } if (windowProperties.loadMethod == 'iframe') { // Iframes have their own scrollbars and padding. windowProperties.scrollbars = false; windowProperties.paddingVertical = 0; windowProperties.paddingHorizontal = 0; } // Extend our window element windowEl = $extend(windowEl, { // Custom properties id: windowProperties.id, oldTop: 0, oldLeft: 0, oldWidth: 0, // Using this? oldHeight: 0, iconAnimation: $empty, modal: windowProperties.modal, scrollbars: windowProperties.scrollbars, contentBorder: null, // Always use close buttons for modal windows closable: windowProperties.closable || windowProperties.modal, resizable: windowProperties.resizable && !windowProperties.modal, draggable: windowProperties.draggable && !windowProperties.modal, // Minimizable, dock is required and window cannot be modal minimizable: this.dock && windowProperties.minimizable && !windowProperties.modal, // Maximizable, desktop is required maximizable: this.desktop && windowProperties.maximizable && !windowProperties.modal, iframe: windowProperties.loadMethod == 'iframe' ? true : false, isMaximized: false, isMinimized: false, // Custom styling headerStartColor: windowProperties.headerStartColor, // Header gradient's top color headerStopColor: windowProperties.headerStopColor, // Header gradient's bottom color footerBgColor: windowProperties.footerBgColor, // Background color of the main canvas shape minimizeColor: windowProperties.minimizeColor, // Minimize button color maximizeColor: windowProperties.maximizeColor, // Maximize button color closeColor: windowProperties.closeColor, // Close button color resizableColor: windowProperties.resizableColor, // Resizable icon color // Custom events onFocus: windowProperties.onFocus, onResize: windowProperties.onResize, onMinimize: windowProperties.onMinimize, onMaximize: windowProperties.onMaximize, onClose: windowProperties.onClose, onCloseComplete: windowProperties.onCloseComplete }); // Insert sub elements inside windowEl and cache them locally while creating the new window var subElements = this.insertWindowElements(windowEl, windowProperties.height, windowProperties.width); // Set title subElements.title.set('html', windowProperties.title); // Add content to window switch(windowProperties.loadMethod) { case 'xhr': new Request({ url: windowProperties.contentURL, onRequest: function(){ this.showLoadingIcon(subElements.canvasIcon); }.bind(this), onFailure: function(){ subElements.content.set('html', '

Error Loading XMLHttpRequest

Make sure all of your content is uploaded to your server, and that you are attempting to load a document from the same domain as this page. XMLHttpRequests will not work on your local machine.

'); this.hideLoadingIcon.delay(150, this, subElements.canvasIcon); }.bind(this), onSuccess: function(response) { subElements.content.set('html', response); this.hideLoadingIcon.delay(150, this, subElements.canvasIcon); windowProperties.onContentLoaded(); }.bind(this) }).get(); break; case 'iframe': if ( windowProperties.contentURL == '') { break; } subElements.iframe = new Element('iframe', { 'id': windowEl.id + '_iframe', 'class': 'mochaIframe', 'src': windowProperties.contentURL, 'marginwidth': 0, 'marginheight': 0, 'frameBorder': 0, 'scrolling': 'auto' }).injectInside(subElements.content); // Add onload event to iframe so we can stop the loading icon and run onContentLoaded() subElements.iframe.addEvent('load', function(e) { this.hideLoadingIcon.delay(150, this, subElements.canvasIcon); windowProperties.onContentLoaded(); }.bind(this)); this.showLoadingIcon(subElements.canvasIcon); break; case 'html': default: subElements.content.set('html', windowProperties.content); windowProperties.onContentLoaded(); break; } // Set scrollbars, always use 'hidden' for iframe windows subElements.contentWrapper.setStyles({ 'overflow': windowProperties.scrollbars && !windowProperties.iframe ? 'auto' : 'hidden', 'background': windowProperties.bodyBgColor }); // Set content padding subElements.content.setStyles({ 'padding-top': windowProperties.paddingVertical, 'padding-bottom': windowProperties.paddingVertical, 'padding-left': windowProperties.paddingHorizontal, 'padding-right': windowProperties.paddingHorizontal }); // Attach events to the window this.attachResizable(windowEl, subElements); this.setupEvents(windowEl, subElements); // Move new window into position. If position not specified by user then center the window on the page var dimensions = document.getCoordinates(); if (!windowProperties.y) { var windowPosTop = (dimensions.height * .5) - ((windowProperties.height + this.HeaderFooterShadow) * .5); } else { var windowPosTop = windowProperties.y } if (!windowProperties.x) { var windowPosLeft = (dimensions.width * .5) - (windowProperties.width * .5); } else { var windowPosLeft = windowProperties.x } if (windowEl.modal) { $('mochaModalOverlay').setStyle('display', 'block'); if (this.options.effects == false){ $('mochaModalOverlay').setStyle('opacity', .55); } else { this.modalCloseMorph.cancel(); this.modalOpenMorph.start({ 'opacity': .55 }); } windowEl.setStyles({ 'top': windowPosTop, 'left': windowPosLeft, 'zIndex': 11000 }); } else if (cascade == true) { // do nothing } else if (this.options.effects == false){ windowEl.setStyles({ 'top': windowPosTop, 'left': windowPosLeft }); } else { windowEl.positionMorph = new Fx.Morph(windowEl, { 'duration': 300 }); windowEl.positionMorph.start({ 'top': windowPosTop, 'left': windowPosLeft }); setTimeout(function(){ this.focusWindow(windowEl); }.bind(this), 10); } // Inject window into DOM windowEl.injectInside(this.desktop ? this.desktop : document.body); this.drawWindow(windowEl, subElements); // Drag.Move() does not work in IE until element has been injected, thus setting here this.attachDraggable(windowEl, subElements.titleBar); }, /* Method: closeWindow Arguments: el: the $(window) to be closed Returns: true: the window was closed false: the window was not closed */ closeWindow: function(windowEl) { // Does window exist and is not already in process of closing ? if ( !(windowEl = $(windowEl)) || windowEl.isClosing ) return; windowEl.isClosing = true; windowEl.onClose(); if (this.options.effects == false){ if (windowEl.modal) { $('mochaModalOverlay').setStyle('opacity', 0); } windowEl.destroy(); windowEl.onCloseComplete(); } else { // Redraws IE windows without shadows since IE messes up canvas alpha when you change element opacity if (Browser.Engine.trident) this.drawWindow(windowEl, null, false); if (windowEl.modal) { this.modalCloseMorph.start({ 'opacity': 0 }); } var closeMorph = new Fx.Morph(windowEl, { duration: 250, onComplete: function(){ windowEl.destroy(); windowEl.onCloseComplete(); }.bind(this) }); closeMorph.start({ 'opacity': .4 }); } return true; }, /* Method: closeAll Notes: This closes all the windows Returns: true: the windows were closed false: the windows were not closed */ closeAll: function() { $$('div.mocha').each(function(el) { this.closeWindow(el); $$('button.mochaDockButton').destroy(); }.bind(this)); return true; }, focusWindow: function(windowEl){ if ( !(windowEl = $(windowEl)) ) return; // Only focus when needed if ( windowEl.getStyle('zIndex').toInt() == this.indexLevel ) return; this.indexLevel++; windowEl.setStyle('zIndex', this.indexLevel); windowEl.onFocus(); }, maximizeWindow: function(windowEl) { // If window no longer exists or is maximized, stop if ( !(windowEl = $(windowEl)) || windowEl.isMaximized ) return; var contentWrapper = this.getSubElement(windowEl, 'contentWrapper'); windowEl.onMaximize(); // Save original position, width and height windowEl.oldTop = windowEl.getStyle('top'); windowEl.oldLeft = windowEl.getStyle('left'); contentWrapper.oldWidth = contentWrapper.getStyle('width'); contentWrapper.oldHeight = contentWrapper.getStyle('height'); // Hide iframe // Iframe should be hidden when minimizing, maximizing, and moving for performance and Flash issues if ( windowEl.iframe ) { this.getSubElement(windowEl, 'iframe').setStyle('visibility', 'hidden'); } var windowDimensions = document.getCoordinates(); if (this.options.effects == false){ windowEl.setStyles({ 'top': -this.shadowWidth, 'left': -this.shadowWidth }); contentWrapper.setStyles({ 'height': windowDimensions.height - this.options.headerHeight - this.options.footerHeight, 'width': windowDimensions.width }); this.drawWindow(windowEl); // Show iframe if ( windowEl.iframe ) { this.getSubElement(windowEl, 'iframe').setStyle('visibility', 'visible'); } } else { var maximizeMorph = new Fx.Morph(windowEl, { 'duration': 200, 'onComplete': function(windowEl){ contentWrapper.setStyles({ 'height': (windowDimensions.height - this.options.headerHeight - this.options.footerHeight), 'width': windowDimensions.width }); this.drawWindow(windowEl); // Show iframe if ( windowEl.iframe ) { this.getSubElement(windowEl, 'iframe').setStyle('visibility', 'visible'); } }.bind(this) }); maximizeMorph.start({ 'top': -this.shadowWidth, // Takes shadow width into account 'left': -this.shadowWidth // Takes shadow width into account }); } windowEl.isMaximized = true; }, restoreWindow: function(windowEl) { // Window exists and is maximized ? if ( !(windowEl = $(windowEl)) || !windowEl.isMaximized ) return; // Hide iframe // Iframe should be hidden when minimizing, maximizing, and moving for performance and Flash issues if ( windowEl.iframe ) { this.getSubElement(windowEl, 'iframe').setStyle('visibility', 'hidden'); } var contentWrapper = this.getSubElement(windowEl, 'contentWrapper'); contentWrapper.setStyles({ 'width': contentWrapper.oldWidth, 'height': contentWrapper.oldHeight }); windowEl.isMaximized = false; this.drawWindow(windowEl); if (this.options.effects == false){ windowEl.setStyles({ 'top': windowEl.oldTop, 'left': windowEl.oldLeft }); } else { var mochaMorph = new Fx.Morph(windowEl, { 'duration': 150, 'onComplete': function(el){ if ( windowEl.iframe ) { this.getSubElement(windowEl, 'iframe').setStyle('visibility', 'visible'); } }.bind(this) }); mochaMorph.start({ 'top': windowEl.oldTop, 'left': windowEl.oldLeft }); } }, minimizeWindow: function(windowEl) { // What if there is no dock, react how ?? ignore request? if ( !(windowEl = $(windowEl)) || !this.dock) return; // Hide iframe // Iframe should be hidden when minimizing, maximizing, and moving for performance and Flash issues if ( windowEl.iframe ) { this.getSubElement(windowEl, 'iframe').setStyle('visibility', 'hidden'); } var title = this.getSubElement(windowEl, 'title'); var mochaContentWrapper = windowEl.getElement('.mochaContentWrapper'); var titleText = title.innerHTML; windowEl.onMinimize(); // Hide window and add to dock windowEl.setStyle('visibility', 'hidden'); // Fixes a scrollbar issue in Mac FF2 if (Browser.Platform.mac && Browser.Engine.gecko){ this.getSubElement(windowEl, 'contentWrapper').setStyle('overflow', 'hidden'); } windowEl.isMinimized = true; var dockButton = new Element('button', { 'id': windowEl.id + '_dockButton', 'class': 'mochaDockButton', 'title': titleText }).set('html', titleText.substring(0,13) + (titleText.length > 13 ? '...' : '')).injectInside($(this.dock)); dockButton.addEvent('click', function(event) { this.restoreMinimized(windowEl); }.bind(this)); // Fixes a scrollbar issue in Mac FF2. // Have to use timeout because window gets focused when you click on the minimize button setTimeout(function(){ windowEl.setStyle('zIndex', 1); }.bind(this),100); }, restoreMinimized: function(windowEl) { // Part of Mac FF2 scrollbar fix if (windowEl.scrollbars == true && windowEl.iframe == false){ this.getSubElement(windowEl, 'contentWrapper').setStyle('overflow', 'auto'); } windowEl.setStyle('visibility', 'visible'); // Show iframe if ( windowEl.iframe ) { this.getSubElement(windowEl, 'iframe').setStyle('visibility', 'visible'); } windowEl.isMinimized = false; this.focusWindow(windowEl); this.dock.getElementById(windowEl.id + '_dockButton').destroy(); }, /* -- START Private Methods -- */ /* Method: getSubElement() Description: Get a single subElement within windowEl. Subelements have IDs that are made up of the windowEl ID plus an element key. e.g., myWindow_content or myWindow_iframe. Might rename these parentWindow and childElements in the future. Arguments: windowEl, subElementKey Returns: subElement */ getSubElement: function(windowEl, subElementKey) { return windowEl.getElementById((windowEl.id + '_' + subElementKey)); }, /* Method: getSubElements() Description: Get subElements within windowEl referenced in array subElementsKeys Arguments: windowEl, subElementKeys Returns: Object, where elements are object.key */ getSubElements: function(windowEl, subElementKeys) { var subElements = {}; subElementKeys.each(function(key) { subElements[key] = this.getSubElement(windowEl, key); }.bind(this)); return subElements; }, /* Method: setupControlEvents() Usage: internal Arguments: windowEl */ setupEvents: function(windowEl, subElements) { /*if ( !subElements ) subElements = this.getSubElements(windowEl, ['closeButton','minimizeButton','maximizeButton']);*/ // Set events // Note: if a button does not exist, its due to properties passed to newWindow() stating otherwice if ( subElements.closeButton ) subElements.closeButton.addEvent('click', function() { this.closeWindow(windowEl); }.bind(this)); if ( !windowEl.modal ) windowEl.addEvent('click', function() { this.focusWindow(windowEl); }.bind(this)); if ( subElements.minimizeButton ) subElements.minimizeButton.addEvent('click', function() { this.minimizeWindow(windowEl); }.bind(this)); if ( subElements.maximizeButton ) { subElements.maximizeButton.addEvent('click', function() { if ( windowEl.isMaximized ) { this.restoreWindow(windowEl); subElements.maximizeButton.setProperty('title', 'Maximize'); } else { this.maximizeWindow(windowEl); subElements.maximizeButton.setProperty('title', 'Restore'); } }.bind(this)); } }, /* Method: attachDraggable() Description: make window draggable Usage: internal Arguments: windowEl */ attachDraggable: function(windowEl, handleEl){ if ( !windowEl.draggable ) return; new Drag.Move(windowEl, { handle: handleEl, onStart: function() { this.focusWindow(windowEl); if ( windowEl.iframe ) this.getSubElement(windowEl, 'iframe').setStyle('visibility', 'hidden'); }.bind(this), onComplete: function() { if ( windowEl.iframe ) this.getSubElement(windowEl, 'iframe').setStyle('visibility', 'visible'); }.bind(this) }); }, /* Method: attachResizable() Description: make window resizable Usage: internal Arguments: windowEl */ attachResizable: function(windowEl, subElements){ if ( !windowEl.resizable ) return; subElements.contentWrapper.makeResizable({ handle: subElements.resizeHandle, modifiers: { x: 'width', y: 'height' }, limit: { x: [this.options.minWidth, this.options.maxWidth], y: [this.options.minHeight, this.options.maxHeight] }, onStart: function() { this.cacheSubElements = this.getSubElements(windowEl, ['title', 'content', 'canvas', 'contentWrapper', 'overlay', 'titleBar', 'iframe', 'zIndexFix']); if ( this.cacheSubElements.iframe ) this.cacheSubElements.iframe.setStyle('visibility', 'hidden'); }.bind(this), onDrag: function() { this.drawWindow(windowEl, this.cacheSubElements); }.bind(this), onComplete: function() { if ( this.cacheSubElements.iframe ) this.cacheSubElements.iframe.setStyle('visibility', 'visible'); delete this.cacheSubElements; this.cacheSubElements = null; windowEl.onResize(); }.bind(this) }); }, setDesktopSize: function(){ var windowDimensions = document.getCoordinates(); if ( this.desktop ){ this.desktop.setStyle('height', windowDimensions.height); } // Set pageWrapper height so the dock doesn't cover the pageWrapper scrollbars. if ( this.pageWrapper && this.desktopHeader) { var pageWrapperHeight = (windowDimensions.height - this.desktopHeader.offsetHeight - (this.dockVisible ? this.dock.offsetHeight : 0)); if ( pageWrapperHeight < 0 ) { pageWrapperHeight = 0; } this.pageWrapper.setStyle('height', pageWrapperHeight + 'px'); } }, setModalSize: function(){ $('mochaModalOverlay').setStyle('height', document.getCoordinates().height); }, /* Method: insertWindowElements Arguments: windowEl Returns: object containing all elements created within [windowEl] */ insertWindowElements: function(windowEl, height, width){ var subElements = {}; if (Browser.Engine.trident4){ subElements.zIndexFix = new Element('iframe', { 'class': 'zIndexFix', 'scrolling': 'no', 'marginWidth': 0, 'marginHeight': 0, 'src': '', 'id': windowEl.id + '_zIndexFix' }).injectInside(windowEl); } subElements.overlay = new Element('div', { 'class': 'mochaOverlay', 'id': windowEl.id + '_overlay' }).injectInside(windowEl); //Insert mochaTitlebar subElements.titleBar = new Element('div', { 'class': 'mochaTitlebar', 'id': windowEl.id + '_titleBar', 'styles': { 'cursor': windowEl.draggable ? 'move' : 'default' } }).injectTop(subElements.overlay); // Create window header subElements.title = new Element('h3', { 'class': 'mochaTitle', 'id': windowEl.id + '_title' }).injectInside(subElements.titleBar); windowEl.contentBorder = new Element('div', { 'class': 'mochaContentBorder', 'id': this.options.id + '_contentBorder' }).injectInside(subElements.overlay); subElements.contentWrapper = new Element('div', { 'class': 'mochaContentWrapper', 'id': windowEl.id + '_contentWrapper', 'styles': { 'width': width + 'px', 'height': height + 'px' } }).injectInside(windowEl.contentBorder); subElements.content = new Element('div', { 'class': 'mochaContent', 'id': windowEl.id + '_content' }).injectInside(subElements.contentWrapper); //Insert canvas subElements.canvas = new Element('canvas', { 'class': 'mochaCanvas', 'width': 1, 'height': 1, 'id': windowEl.id + '_canvas' }).injectInside(windowEl); // Dynamically initialize canvas using excanvas. This is only required by IE if ( Browser.Engine.trident && this.ieSupport == 'excanvas' ) { G_vmlCanvasManager.initElement(subElements.canvas); // This is odd, .getContext() method does not exist before retrieving the // element via getElement subElements.canvas = windowEl.getElement('.mochaCanvas'); } //Insert resize handles if (windowEl.resizable){ subElements.resizeHandle = new Element('div', { 'class': 'resizeHandle', 'id': windowEl.id + '_resizeHandle' }).injectAfter(subElements.overlay); if ( Browser.Engine.trident ) subElements.resizeHandle.setStyle('zIndex', 2); } //Insert mochaTitlebar controls subElements.controls = new Element('div', { 'class': 'mochaControls', 'id': windowEl.id + '_controls' }).injectAfter(subElements.overlay); //Insert close button if (windowEl.closable){ subElements.closeButton = new Element('div', { 'class': 'mochaClose', 'title': 'Close Window', 'id': windowEl.id + '_closeButton' }).injectInside(subElements.controls); } //Insert maximize button if (windowEl.maximizable){ subElements.maximizeButton = new Element('div', { 'class': 'maximizeToggle', 'title': 'Maximize', 'id': windowEl.id + '_maximizeButton' }).injectInside(subElements.controls); } //Insert minimize button if (windowEl.minimizable){ subElements.minimizeButton = new Element('div', { 'class': 'minimizeToggle', 'title': 'Minimize', 'id': windowEl.id + '_minimizeButton' }).injectInside(subElements.controls); } //Insert canvas subElements.canvasIcon = new Element('canvas', { 'class': 'mochaLoadingIcon', 'width': 18, 'height': 18, 'id': windowEl.id + '_canvasIcon' }).injectBottom(windowEl); // Dynamically initialize canvas using excanvas. This is only required by IE if (Browser.Engine.trident && this.ieSupport == 'excanvas') { G_vmlCanvasManager.initElement(subElements.canvasIcon); // This is odd, .getContext() method does not exist before retrieving the // element via getElement subElements.canvasIcon = windowEl.getElement('.mochaLoadingIcon'); } if ( Browser.Engine.trident ) { subElements.controls.setStyle('zIndex', 2) subElements.overlay.setStyle('zIndex', 2) } // For Mac Firefox 2 to help reduce scrollbar bugs in that browser if (Browser.Platform.mac && Browser.Engine.gecko) subElements.overlay.setStyle('overflow', 'auto'); this.setMochaControlsWidth(windowEl, subElements); return subElements; }, /* Method: drawWindow Arguments: windowEl: the $(window) subElements: children of $(window) shadows: (boolean) false will draw a window without shadows Notes: This is where we create the canvas GUI */ drawWindow: function(windowEl, subElements, shadows) { if ( !subElements ) { subElements = this.getSubElements(windowEl, ['title', 'content', 'canvas', 'contentWrapper', 'overlay', 'titleBar', 'iframe', 'zIndexFix']); } windowEl.contentBorder.setStyles({ 'width': subElements.contentWrapper.offsetWidth }); // Resize iframe when window is resized if ( windowEl.iframe ) { subElements.iframe.setStyles({ 'height': subElements.contentWrapper.offsetHeight }); } var mochaHeight = subElements.contentWrapper.getStyle('height').toInt() + this.HeaderFooterShadow; var mochaWidth = subElements.contentWrapper.getStyle('width').toInt() + this.shadowOffset; subElements.overlay.setStyle('height', mochaHeight); windowEl.setStyle('height', mochaHeight); // If opera height and width must be set like this, when resizing: subElements.canvas.height = Browser.Engine.webkit ? 4000 : mochaHeight; subElements.canvas.width = Browser.Engine.webkit ? 2000 : mochaWidth; // Part of the fix for IE6 select z-index bug and FF on Mac scrollbar z-index bug if ( Browser.Engine.trident4 ){ subElements.zIndexFix.setStyles({ 'width': mochaWidth, 'height': mochaHeight }) } // Set width windowEl.setStyle('width', mochaWidth); subElements.overlay.setStyle('width', mochaWidth); subElements.titleBar.setStyles({ 'width': mochaWidth - this.shadowOffset, 'height': this.options.headerHeight }); // Draw shapes var ctx = subElements.canvas.getContext('2d'); var dimensions = document.getCoordinates(); ctx.clearRect(0, 0, dimensions.width, dimensions.height); // This is the drop shadow. It is created onion style with three layers if ( shadows != false ) { this.roundedRect(ctx, 0, 0, mochaWidth, mochaHeight, this.options.cornerRadius, [0, 0, 0], 0.06); this.roundedRect(ctx, 1, 1, mochaWidth - 2, mochaHeight - 2, this.options.cornerRadius, [0, 0, 0], 0.08); this.roundedRect(ctx, 2, 2, mochaWidth - 4, mochaHeight - 4, this.options.cornerRadius, [0, 0, 0], 0.3); } // Mocha body this.bodyRoundedRect( ctx, // context 3, // x 2, // y mochaWidth - this.shadowOffset, // width mochaHeight - this.shadowOffset, // height this.options.cornerRadius, // corner radius windowEl.footerBgColor // Footer color ); // Mocha header this.topRoundedRect( ctx, // context 3, // x 2, // y mochaWidth - this.shadowOffset, // width this.options.headerHeight, // height this.options.cornerRadius, // corner radius windowEl.headerStartColor, // Header gradient's top color windowEl.headerStopColor // Header gradient's bottom color ); // Calculate X position for controlbuttons this.closebuttonX = mochaWidth - (windowEl.closable ? 15 : -4); this.maximizebuttonX = this.closebuttonX - (windowEl.maximizable ? 19 : 0); this.minimizebuttonX = this.maximizebuttonX - (windowEl.minimizable ? 19 : 0); if ( windowEl.closable ) this.closebutton(ctx, this.closebuttonX, 15, windowEl.closeColor, 1.0); if ( windowEl.maximizable ) this.maximizebutton(ctx, this.maximizebuttonX, 15, windowEl.maximizeColor, 1.0); if ( windowEl.minimizable ) this.minimizebutton(ctx, this.minimizebuttonX, 15, windowEl.minimizeColor, 1.0); // Minimize if ( windowEl.resizable ) this.triangle(ctx, mochaWidth - 20, mochaHeight - 20, 12, 12, windowEl.resizableColor, 1.0); // Resize handle // Invisible dummy object. The last element drawn is not rendered consistently while resizing in IE6 and IE7. this.triangle(ctx, 0, 0, 10, 10, windowEl.resizableColor, 0); }, // Window body bodyRoundedRect: function(ctx, x, y, width, height, radius, rgb){ ctx.fillStyle = 'rgba(' + rgb.join(',') + ', 100)'; ctx.beginPath(); ctx.moveTo(x, y + radius); ctx.lineTo(x, y + height - radius); ctx.quadraticCurveTo(x, y + height, x + radius, y + height); ctx.lineTo(x + width - radius, y + height); ctx.quadraticCurveTo(x + width, y + height, x + width, y + height - radius); ctx.lineTo(x + width, y + radius); ctx.quadraticCurveTo(x + width, y, x + width - radius, y); ctx.lineTo(x + radius, y); ctx.quadraticCurveTo(x, y, x, y + radius); ctx.fill(); }, roundedRect: function(ctx, x, y, width, height, radius, rgb, a){ ctx.fillStyle = 'rgba(' + rgb.join(',') + ',' + a + ')'; ctx.beginPath(); ctx.moveTo(x, y + radius); ctx.lineTo(x, y + height - radius); ctx.quadraticCurveTo(x, y + height, x + radius, y + height); ctx.lineTo(x + width - radius, y + height); ctx.quadraticCurveTo(x + width, y + height, x + width, y + height - radius); ctx.lineTo(x + width, y + radius); ctx.quadraticCurveTo(x + width, y, x + width - radius, y); ctx.lineTo(x + radius, y); ctx.quadraticCurveTo(x, y, x, y + radius); ctx.fill(); }, // Window header with gradient background topRoundedRect: function(ctx, x, y, width, height, radius, headerStartColor, headerStopColor){ // Create gradient if (Browser.Engine.presto != null ){ var lingrad = ctx.createLinearGradient(0, 0, 0, this.options.headerHeight + 2); } else { var lingrad = ctx.createLinearGradient(0, 0, 0, this.options.headerHeight); } lingrad.addColorStop(0, 'rgba(' + headerStartColor.join(',') + ', 100)'); lingrad.addColorStop(1, 'rgba(' + headerStopColor.join(',') + ', 100)'); ctx.fillStyle = lingrad; // Draw header ctx.beginPath(); ctx.moveTo(x, y); ctx.lineTo(x, y + height); ctx.lineTo(x + width, y + height); ctx.lineTo(x + width, y + radius); ctx.quadraticCurveTo(x + width, y, x + width - radius, y); ctx.lineTo(x + radius, y); ctx.quadraticCurveTo(x, y, x, y + radius); ctx.fill(); }, // Resize handle triangle: function(ctx, x, y, width, height, rgb, a){ ctx.beginPath(); ctx.moveTo(x + width, y); ctx.lineTo(x, y + height); ctx.lineTo(x + width, y + height); ctx.closePath(); ctx.fillStyle = 'rgba(' + rgb.join(',') + ',' + a + ')'; ctx.fill(); }, drawCircle: function(ctx, x, y, diameter, rgb, a){ // Circle ctx.beginPath(); ctx.moveTo(x, y); ctx.arc(x, y, diameter, 0, Math.PI*2, true); ctx.fillStyle = 'rgba(' + rgb.join(',') + ',' + a + ')'; ctx.fill(); }, maximizebutton: function(ctx, x, y, rgb, a){ // This could reuse the drawCircle method above // Circle ctx.beginPath(); ctx.moveTo(x, y); ctx.arc(x, y, 7, 0, Math.PI*2, true); ctx.fillStyle = 'rgba(' + rgb.join(',') + ',' + a + ')'; ctx.fill(); // X sign ctx.beginPath(); ctx.moveTo(x, y - 4); ctx.lineTo(x, y + 4); ctx.stroke(); ctx.beginPath(); ctx.moveTo(x - 4, y); ctx.lineTo(x + 4, y); ctx.stroke(); }, closebutton: function(ctx, x, y, rgb, a){ // This could reuse the drawCircle method above // Circle ctx.beginPath(); ctx.moveTo(x, y); ctx.arc(x, y, 7, 0, Math.PI*2, true); ctx.fillStyle = 'rgba(' + rgb.join(',') + ',' + a + ')'; ctx.fill(); // Plus sign ctx.beginPath(); ctx.moveTo(x - 3, y - 3); ctx.lineTo(x + 3, y + 3); ctx.stroke(); ctx.beginPath(); ctx.moveTo(x + 3, y - 3); ctx.lineTo(x - 3, y + 3); ctx.stroke(); }, minimizebutton: function(ctx, x, y, rgb, a){ // This could reuse the drawCircle method above // Circle ctx.beginPath(); ctx.moveTo(x,y); ctx.arc(x,y,7,0,Math.PI*2,true); ctx.fillStyle = 'rgba(' + rgb.join(',') + ',' + a + ')'; ctx.fill(); // Minus sign ctx.beginPath(); ctx.moveTo(x - 4, y); ctx.lineTo(x + 4, y); ctx.stroke(); }, hideLoadingIcon: function(canvas) { $(canvas).setStyle('display', 'none'); $clear(canvas.iconAnimation); }, /* Method: showLoadingIcon */ showLoadingIcon: function(canvas) { $(canvas).setStyles({ 'display': 'block' }); var t = 1; var iconAnimation = function(canvas){ var ctx = $(canvas).getContext('2d'); ctx.clearRect(0, 0, 18, 18); // Clear canvas ctx.save(); ctx.translate(9, 9); ctx.rotate(t*(Math.PI / 8)); var color = 0; for (i=0; i < 8; i++){ // Draw individual dots color = Math.floor(255 / 8 * i); ctx.fillStyle = "rgb(" + color + "," + color + "," + color + ")"; ctx.rotate(-Math.PI / 4); ctx.beginPath(); ctx.arc(0, 7, 2, 0, Math.PI*2, true); ctx.fill(); } ctx.restore(); t++; }.bind(this); canvas.iconAnimation = iconAnimation.periodical(125, this, canvas); }, setMochaControlsWidth: function(windowEl, subElements){ var controlWidth = 14; var marginWidth = 5; this.mochaControlsWidth = 0; if ( windowEl.minimizable ) this.mochaControlsWidth += (marginWidth + controlWidth); if ( windowEl.maximizable ) { this.mochaControlsWidth += (marginWidth + controlWidth); subElements.maximizeButton.setStyle('margin-left', marginWidth); } if ( windowEl.closable ) { this.mochaControlsWidth += (marginWidth + controlWidth); subElements.closeButton.setStyle('margin-left', marginWidth); } subElements.controls.setStyle('width', this.mochaControlsWidth); }, initializeDock: function (){ this.dock.setStyles({ 'display': 'block', 'position': 'absolute', 'top': null, 'bottom': 0, 'left': 0 }); // Probably: this event should be added/removed when toggling AutoHide, since we dont need it when AutoHide is turned off // this.dockVisible tracks the status of the dock, so that showing/hiding is not done when not needed document.addEvent('mousemove', function(event) { if ( !this.dockAutoHide ) return; var ev = new Event(event); if ( ev.client.y > (document.getCoordinates().height - 25) ) { if ( !this.dockVisible ) { this.dock.setStyle('display', 'block'); this.dockVisible = true; this.setDesktopSize(); } } else { if ( this.dockVisible ) { this.dock.setStyle('display', 'none'); this.dockVisible = false; this.setDesktopSize(); } } }.bind(this)); // Insert canvas var canvas = new Element('canvas', { 'class': 'mochaCanvas', 'id': 'dockCanvas', 'width': '15', 'height': '18' }).injectInside(this.dock).setStyles({ position: 'absolute', top: '4px', left: '2px', zIndex: 2 }); // Dynamically initialize canvas using excanvas. This is only required by IE if (Browser.Engine.trident && this.ieSupport == 'excanvas') { G_vmlCanvasManager.initElement(canvas); } // Position top or bottom selector $('mochaDockPlacement').setProperty('title','Position Dock Top'); // Auto Hide toggle switch $('mochaDockAutoHide').setProperty('title','Turn Auto Hide On'); // Attach event $('mochaDockPlacement').addEvent('click', function(event){ var ctx = this.dock.getElement('.mochaCanvas').getContext('2d'); // Move dock to top position if (this.dock.getStyle('position') != 'relative'){ this.dock.setStyles({ 'position': 'relative', 'bottom': null, 'border-top': '1px solid #fff', 'border-bottom': '1px solid #bbb' }) this.setDesktopSize(); this.dock.setProperty('dockPosition','Top'); this.drawCircle(ctx, 5, 4, 3, [0, 255, 0], 1.0); // green this.drawCircle(ctx, 5, 14, 3, [212, 208, 200], 1.0); // gray $('mochaDockPlacement').setProperty('title', 'Position Dock Bottom'); $('mochaDockAutoHide').setProperty('title', 'Auto Hide Disabled in Top Dock Position'); this.dockAutoHide = false; } // Move dock to bottom position else { this.dock.setStyles({ 'position': 'absolute', 'bottom': 0, 'border-top': '1px solid #bbb', 'border-bottom': '1px solid #fff' }) this.setDesktopSize(); this.dock.setProperty('dockPosition','Bottom'); this.drawCircle(ctx, 5, 4, 3, [241, 102, 116], 1.0); // orange this.drawCircle(ctx, 5 , 14, 3, [241, 102, 116], 1.0); // orange $('mochaDockPlacement').setProperty('title', 'Position Dock Top'); $('mochaDockAutoHide').setProperty('title', 'Turn Auto Hide On'); } }.bind(this)); // Attach event Auto Hide $('mochaDockAutoHide').addEvent('click', function(event){ if ( this.dock.getProperty('dockPosition') == 'Top' ) return false; var ctx = this.dock.getElement('.mochaCanvas').getContext('2d'); this.dockAutoHide = !this.dockAutoHide; // Toggle if ( this.dockAutoHide ) { $('mochaDockAutoHide').setProperty('title', 'Turn Auto Hide Off'); this.drawCircle(ctx, 5 , 14, 3, [0, 255, 0], 1.0); // green } else { $('mochaDockAutoHide').setProperty('title', 'Turn Auto Hide On'); this.drawCircle(ctx, 5 , 14, 3, [241, 102, 116], 1.0); // orange } }.bind(this)); this.drawDock(this.dock); }, drawDock: function (el){ var ctx = el.getElement('.mochaCanvas').getContext('2d'); this.drawCircle(ctx, 5 , 4, 3, [241, 102, 116], 1.0); // orange this.drawCircle(ctx, 5 , 14, 3, [241, 102, 116], 1.0); // orange }, dynamicResize: function (windowEl){ this.getSubElement(windowEl, 'contentWrapper').setStyle('height', this.getSubElement(windowEl, 'content').offsetHeight); this.drawWindow(windowEl); }, /* Method: arrangeCascade */ arrangeCascade: function(){ var x = this.options.desktopLeftOffset var y = this.options.desktopTopOffset; $$('div.mocha').each(function(windowEl){ if (!windowEl.isMinimized && !windowEl.isMaximized){ this.focusWindow(windowEl); x += this.options.mochaLeftOffset; y += this.options.mochaTopOffset; if (this.options.effects == false){ windowEl.setStyles({ 'top': y, 'left': x }); } else { var cascadeMorph = new Fx.Morph(windowEl, { 'duration': 550 }); cascadeMorph.start({ 'top': y, 'left': x }); } } }.bind(this)); }, /* Method: garbageCleanup Notes: Empties an all windows of their children, removes and garbages the windows. It is does not trigger onClose() or onCloseComplete(). This is useful to clear memory before the pageUnload. */ garbageCleanUp: function() { $$('div.mocha').each(function(el) { el.destroy(); }.bind(this)); } }); MochaUI.implement(new Options); /* ----------------------------------------------------------------- MOCHA SCREENS Notes: This class can be removed if you are not creating multiple screens/workspaces. ----------------------------------------------------------------- */ var MochaScreens = new Class({ options: { defaultScreen: 0 // Default screen }, initialize: function(options){ this.setOptions(options); this.setScreen(this.options.defaultScreen); }, setScreen: function(index) { if ( !$('mochaScreens') ) return; $$('#mochaScreens div.screen').each(function(el,i) { el.setStyle('display', i == index ? 'block' : 'none'); }); } }); MochaScreens.implement(new Options); /* ----------------------------------------------------------------- MOCHA WINDOW FROM FORM Notes: This class can be removed if you are not creating new windows dynamically from a form. ----------------------------------------------------------------- */ var MochaWindowForm = new Class({ options: { id: null, title: 'New Window', loadMethod: 'html', // html, xhr, or iframe content: '', // used if loadMethod is set to 'html' contentURL: 'pages/lipsum.html', // used if loadMethod is set to 'xhr' or 'iframe' modal: false, width: 300, height: 125, scrollbars: true, // true sets the overflow to auto and false sets it to hidden x: null, // if x or y is null or modal is false the new window is centered in the browser window y: null, paddingVertical: 10, paddingHorizontal: 12 }, initialize: function(options){ this.setOptions(options); this.options.id = 'win' + (++document.mochaUI.windowIDCount); this.options.title = $('mochaNewWindowHeaderTitle').value; if ($('htmlLoadMethod').checked){ this.options.loadMethod = 'html'; } if ($('xhrLoadMethod').checked){ this.options.loadMethod = 'xhr'; } if ($('iframeLoadMethod').checked){ this.options.loadMethod = 'iframe'; } this.options.content = $('mochaNewWindowContent').value; if ($('mochaNewWindowContentURL').value){ this.options.contentURL = $('mochaNewWindowContentURL').value; } if ($('mochaNewWindowModal').checked) { this.options.modal = true; } this.options.width = $('mochaNewWindowWidth').value.toInt(); this.options.height = $('mochaNewWindowHeight').value.toInt(); this.options.x = $('mochaNewWindowX').value.toInt(); this.options.y = $('mochaNewWindowY').value.toInt(); this.options.paddingVertical = $('mochaNewWindowPaddingVertical').value.toInt(); this.options.paddingHorizontal = $('mochaNewWindowPaddingHorizontal').value.toInt(); document.mochaUI.newWindow(this.options); } }); MochaWindowForm.implement(new Options); /* ----------------------------------------------------------------- Corner Radius Slider Notes: Remove this function and it's reference in onload if you are not using the example corner radius slider ----------------------------------------------------------------- */ function addSlider(){ if ($('sliderarea')) { mochaSlide = new Slider($('sliderarea'), $('sliderknob'), { steps: 20, offset: 5, onChange: function(pos){ $('updatevalue').set('html', pos); document.mochaUI.options.cornerRadius = pos; $$('div.mocha').each(function(windowEl, i) { document.mochaUI.drawWindow(windowEl); }); document.mochaUI.indexLevel++; } }).set(document.mochaUI.options.cornerRadius); } } /* ----------------------------------------------------------------- Initialize Everything onLoad ----------------------------------------------------------------- */ window.addEvent('domready', function(){ document.mochaScreens = new MochaScreens(); document.mochaUI = new MochaUI(); attachMochaLinkEvents(); // See mocha-events.js addSlider(); // remove this if you remove the example corner radius slider }); // This runs when a person leaves your page. window.addEvent('unload', function(){ if (document.mochaUI) document.mochaUI.garbageCleanUp(); });