Interactions
From Frontal Wiki
An implementation of a tabbed navigation interface. Tabs (and their corresponding views) can be easily added or removed from the page by following these steps:
1. Add a new marker. Give it a name and set the elemNdx.<text>Tab Name</text>
2. Add a new view. Add a div inside of the tabViews container with its name set to the value from the marker above: Content goes here
The shrink-wrapping animation triggered when changing tabs is achieved through the onSelect interaction and style tweens. See the tabViews style class and the fitHeight function below for more details:
<include rel="assets" type="application/x-shockwave-flash" src="assets/paper/ShowcasePaper.swf" blocking="true" /> <script><![CDATA[ /* Function: fitHeight This function sizes a parent container with respect to one of its children. Parameters: node - the child container to which the parent's height should be matched */ fitHeight = function (node) { // We need to set the style to the value to tween from. node.parent.setStyle ('height', node.parent.contentHeight); // We're in an onSelect so node hasn't been rendered. Do it now. node.render ( ); node.parent.prepStyleTween(); // Styles set after prepStyleTween are target values. node.parent.setStyle ('height', node.containerHeight); node.parent.tweenStyles(); // After the tween is done, clear the height style so that it flows with // its contents. com.frontalcode.Scheduler.gI ( ).removeTask ( node.parent.dynamic.timerId ); node.parent.dynamic.timerId = com.frontalcode.Scheduler.setTimeoutInFrames ( function ( ) { node.parent.sS ( "height", undefined ); }, node.parent.gS ( "style-tween-duration" ) + 2 ); } ]]></script> <style><![CDATA[ document { background-color: #e6e6e6; font-family: Swift LT Std; flash-text-anti-alias-type: advanced; } .tabContainer { width: 100%; height: 100%; scroll: auto; } .tabContainer * { style-tween-ease: fl.transitions.easing.Strong.easeOut; style-tween-duration: 5; style-tween-use-secs: false; } #pageTitle { left: 36px; top: 30px; font-size: 28px; color: #000000; } .tabMarkers { top: 84px; padding-left: 22px; height: 33px; width: auto; overflow: hidden; z-index: 2; } .tabMarker { float: left; height: 25px; width: 1px; z-index: 1; color: #e5e5e5; background-color: #999999; margin-right: 6px; margin-top: 10px; font-size: 16px; text-align: center; multiline: false; word-wrap: false; shadow-angle: 90; shadow-strength: .3; shadow-distance: 1; shadow-blur: 10; wants-reset: true; /* The reset signal is sent out when the site loads, so it is a convenient place to trigger the intro animation that expands the tabs from their initial 1px width to their final width. The call to prepStyleTween() sets the initial values of the animation to the current style values. The tweenStyles() call will create the expanding tab animation, seeing that only the width changed after the styles were prepared. */ @onReset { prepStyleTween(); setStyle ('width', 150); tweenStyles(); } } .tabMarker:link { color: #e5e5e5; } .tabMarker:visited { color: #e5e5e5; } .tabMarker:focus, .tabMarker:hover { color: #333333; } .tabMarker:active { color: #000000; } .tabMarker:selected { color: #000000; background-color: #ffffff; z-index: 10; } .tabViews { font-size: 14px; width: 100% leftover; background-color: #ffffff; padding: 6px; z-index: 1; layout: stack; shadow-angle: 90; shadow-strength: .2; shadow-distance: 1; shadow-blur: 10; style-tween-ease: fl.transitions.easing.Strong.easeOut; style-tween-duration: 10; style-tween-use-secs: false; } .tabViews > manager { hide-unselected: true; } .tabViews > div { width: 100%; /* Whenever a user selects a tab, call a function defined in the script tag at the top of this file to shrink the background to the visible content height. */ @onSelect { fitHeight (this); } } .tabViews > div > div:not([class~=resizeGrabber]) { margin-top: 34px; margin-left: 34px; margin-bottom: 34px; float: left; } .tabViews > div > div:not(.legend) { background-color: #e6e6e6; } .tabViews > div > div text:not([class~=character]) { color: #000000; } .legend { margin-left: 34px; border-color: #f55100; border-width: 1px; padding: 19px; background-color: #ffffff; width: 338px; wants-reset: true; shadow-angle: 90; shadow-strength: .4; shadow-distance: 1; shadow-blur: 10; style-tween-ease: fl.transitions.easing.Strong.easeOut; style-tween-duration: 45; style-tween-use-secs: false; /* Animate the border and drop shadows on this container when it gets the reset signal, e.g. when its tab is selected. */ @onReset { sS ('border-color', '#ffffff', true); sS ('shadow-strength', 0); prepStyleTween ( ); sS ('border-color', '#f55100', true); sS ('shadow-strength', .4); tweenStyles ( ); } } .legendArrow { rotation: -45; float: left; margin-top: 10px; margin-left: -30px; width: 15px; height: 15px; border-top-width: 1px; border-left-width: 1px; border-color: #f55100; background-color: #ffffff; style-tween-duration: 45; wants-reset: true; /* This selector has the style-tween values set, but we want to skip any tweens from the initial values to the styles set here. */ @onFirstRender { fforwardStyleTween(); } @onReset { sS ('border-color', '#ffffff', true); prepStyleTween ( ); sS ('border-color', '#f55100', true); tweenStyles ( ); } } .legendTitle { font-size: 20px; padding-bottom: 6px; margin-bottom: 25px; border-bottom-width: 1px; border-color: #cccccc; width: 100%; } *:link:hover, *:visited:hover { color: #ff5523; } ]]></style> <div id="tabContainer" class="tabContainer"> <text id="pageTitle">Interactions</text> <!-- The following div is a container for all of the tabs. The frMarker style class on each div inside adds an onClick interaction that tells a manager (given in the standard mgrId attribute) to switch to a view (given in the elemNdx attribute) when the mouse is clicked over the div. --> <div id="tabMarkers" class="tabMarkers"> <text class="frMarker tabMarker" mgrId="tabMgr" elemNdx="Intro">Interactions</text> <text class="frMarker tabMarker" mgrId="tabMgr" elemNdx="Autocomplete">Autocomplete</text> <text class="frMarker tabMarker" mgrId="tabMgr" elemNdx="Resize">Resize</text> <text class="frMarker tabMarker" mgrId="tabMgr" elemNdx="Keyboard & Mouse"><![CDATA[Keyboard & Mouse]]></text> <text class="frMarker tabMarker" mgrId="tabMgr" elemNdx="Broadcasting">Broadcasting</text> <text class="frLink" href="http://frontalcode.com" target="_blank" style="float: right; margin-top: 15px; margin-right: 5px;">Created w/ Frontal</text> </div> <!-- Each div inside this container is a view that can be selected by the manager. Since tabMgr has the hide-unselected style set to true above, only the selected view will be visible. --> <div id="tabViews" class="tabViews"> <manager id="tabMgr" /> <!-- Introduction to Interactions --> <div name="Intro"> <div class="legend" style="width: 600px;"> <text class="legendTitle">Getting Things Done</text> <text style="condense-white: true; leading: 5px;"><![CDATA[ <p>With Frontal, adding rollover states, keyboard control or even events triggered by video cue points can all be accomplished right inside your site definition. No need to republish!</p> <br> <p>If fact, almost all of the interactivity in this showcase is implemented with a Frontal feature called Interactions. Take a look at the list on the right to get an idea of what's possible.</p> <br> <p>If you've worked with interactive content in Flash, you probably see some familiar names over there. Many of the Frontal's interactions are drawn from the bevy of events native to Flash. You'll also find interactions triggered by events unique to Frontal, such as the selection of a slide in a slideshow or deep-linking into a site.</p> <br> <p>Here's a simple one to get you started. Click on the orange square to see it in action.</p> ]]></text> <div style="background-color: #f55100; width: 40px; height: 40px; movie-registration-x: 50%; movie-registration-y: 50%; float: right; margin-right: 100px; margin-top: 60px;" onClick="sS ( 'rotation', gS ( 'rotation' ) + 15 );" /> <text style="leading: 5px;"> <![CDATA[ <style><![CDATA[ div { background-color: #f55100; width: 40px; height: 40px; @onClick { sS ( "rotation", gS ( "rotation" ) + 15 ); } } ]]></style> <div /> ]]></text> <text style="condense-white: true; leading: 5px;"><![CDATA[ <p>The other tabs show some ways to liven up a page with Interactions.</p> ]]></text> </div> <div class="legend" style="width: 200px; height: 400px;"> <div style="width: 100% leftover; height: 100% leftover; overflow: hidden; scroll: auto;"> <text style="font-family: Swift LT Std Bold; leading: 10px; condense-white: true;"><![CDATA[ <p>onClick</p> <p>onDoubleClick</p> <p>onFocusIn</p> <p>onFocusOut</p> <p>onKeyDown</p> <p>onKeyFocusChange</p> <p>onKeyUp</p> <p>onMouseDown</p> <p>onMouseFocusChange</p> <p>onMouseMove</p> <p>onMouseOut</p> <p>onMouseOver</p> <p>onMouseUp</p> <p>onMouseWheel</p> <p>onMouseRollOut</p> <p>onMouseRollOver</p> <p>onMouseUpAnywhere<p> <p>onConstruct</p> <p>onDestroy</p> <p>onApplyStyles</p> <p>onCustomRender</p> <p>onRenderBackground</p> <p>onRender</p> <p>onInitialReset</p> <p>onReset</p> <p>onSelect</p> <p>onDeselect</p> <p>onSelectTransitionEnd</p> <p>onDeselectTransitionEnd</p> <p>onWillSelect</p> <p>onProgress</p> <p>onTransitionEnd</p> <p>onPause</p> <p>onResume</p> <p>onHoldProgress</p> <p>onInit</p> <p>onStart</p> <p>onFastForward</p> <p>onStop</p> <p>onResume</p> <p>onTransitionDone</p> <p>onTabChildrenChange</p> <p>onTabEnabledChange</p> <p>onTabIndexChange</p> <p>onEnterFrame</p> <p>onAddedToStage</p> <p>onRemovedFromStage</p> <p>onLoaded</p> <p>onFailed</p> <p>onSubmit</p> <p>onAction</p> <p>onFormData</p> <p>onPartialSuccess</p> <p>onSuccess</p> <p>onError</p> <p>onSelectorNotification</p> ]]></text> </div> </div> </div> <!-- Autocomplete Demo --> <div name="Autocomplete"> <script> /* Function: loadEventHandler When the server returns a response, interpret it as XML data and write out the string representation to the results box. The responses look like this: <ResultSet xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:yahoo:srch" xsi:schemaLocation="urn:yahoo:srch http://search.yahooapis.com/WebSearchService/V1/WebSearchRelatedResponse.xsd"> <Result>Madonna lyrics</Result> <Result>Madonna pictures</Result> </ResultSet> Parameters: event - the load event dispatched by the loader */ loadEventHandler = function ( event ) { var loader = event.target; loader.removeEventListener ( flash.events.Event.COMPLETE, loadEventHandler ); loader.removeEventListener ( flash.events.IOErrorEvent.IO_ERROR, loadEventHandler ); loader.removeEventListener ( flash.events.SecurityErrorEvent.SECURITY_ERROR, loadEventHandler ); if ( event.type == flash.events.Event.COMPLETE ) { gE ( 'resultsBox' ).text = new XML (loader.data).toXMLString(); } else { com.frontalcode.Debugger.logMessage ( com.frontalcode.Debugger.ERROR, "Network Error", event.text ); } } </script> <style><![CDATA[ .textBox { condense-white: false; border-color: #ffffff; border-width: 1px; width: 300px; padding: 4px; float: left; background-color: #ffffff; wants-reset: true; shadow-angle: 90; shadow-strength: .4; shadow-distance: 1; shadow-blur: 10; style-tween-ease: fl.transitions.easing.Strong.easeOut; style-tween-duration: 45; style-tween-use-secs: false; @onReset { // Clear out the search and results boxes text = ""; sS ('border-color', '#ffffff', true); sS ('shadow-strength', 0); prepStyleTween ( ); sS ('border-color', '#333333', true); sS ('shadow-strength', .4); tweenStyles ( ); } } #searchBox { font-size: 18px; flash-text-type: input; height: 20px; multiline: false; float: left; /* After every key press, send the contents of the search box off to the keyword suggestion service. The response is an XML file. Add a listener to receive the asynchronous response and then update the contents of the results box (a <text> container) by setting its text property. The listener function, loadEventHandler, is defined in the script tag above. */ @onKeyUpRaw { var query = gE ( 'searchBox' ).text; if ( query ) { var request = new flash.net.URLRequest ( "http://search.yahooapis.com/WebSearchService/V1/relatedSuggestion" ); var variables = new flash.net.URLVariables ( ); variables.appid = 'l_csvSrV34EbCBEZYvt5B3MAM.QhRDyfpbbN6huHhmTR1LXhIvwjbRIPT6CQJtiFg_w9'; variables.query = query; request.data = variables; var loader = new flash.net.URLLoader ( request ); loader.addEventListener ( flash.events.Event.COMPLETE, loadEventHandler ); loader.addEventListener ( flash.events.IOErrorEvent.IO_ERROR, loadEventHandler ); loader.addEventListener ( flash.events.SecurityErrorEvent.SECURITY_ERROR , loadEventHandler ); } } } #resultsBox { font-size: 14px; multiline: true; height: 200px; clear: left; margin-top: 10px; } ]]></style> <div style="padding: 19px;"> <text id="searchBox" class="textBox" /> <text id="resultsBox" class="textBox"></text> </div> <div class="legend"> <div class="legendArrow" /> <text class="legendTitle">Autocomplete</text> <text>Type some letters into the text box to view search keyword suggestions from the Yahoo search engine.</text> </div> </div> <!-- Resize Demo --> <div name="Resize"> <style><![CDATA[ div.resizable { width: 640px; height: 480px; padding: 0px; } div.resizable > img { top: 0px; left: 0px; width: 100%; height: 100%; allow-smoothing: true; resize-scale: showmost; overflow: hidden; } .resizeGrabber { width: 20px; height: 20px; padding: 0px; background-color: #ffffff; background-alpha: 0; resize-anchors: bottom right; /* After the background has been filled with the chosen color, draw some diaganol lines inside this box to indicate that it is a resize handle for the image. */ @onRenderBackground { if ( state == "afterRender" ) { movie.graphics.lineStyle(1, 0xcccccc, .75); movie.graphics.moveTo(1,18); movie.graphics.lineTo(18,1); movie.graphics.moveTo(7,18); movie.graphics.lineTo(18,7); movie.graphics.moveTo(13,18); movie.graphics.lineTo(18,13); } } /* When the user clicks on this box, add a listener to the frame clock and set a resizing flag so the listener can be removed. */ @onMouseDownRaw { dynamic.resizing = true; applyInteraction ( com.frontalcode.FrontalEvent.ENTER_FRAME, movie ); } /* This interaction is applied to the resize button, so 'parent' in this context refers to the bounding container. If the image is not currently being resized, remove the enter frame listener to free up resources. Otherwise, set the width and height of the image to the mouse's distance from the upper left corner of the bounding countainer. Finally, set the height of the tabViews container as the image is resized. The containerHeight is the bounding container's height plus any padding, margins and borders. */ @onEnterFrame { if (! dynamic.resizing) { removeInteraction ( com.frontalcode.FrontalEvent.ENTER_FRAME, movie ); } else { parent.setStyle ('width', parent.movie.mouseX, false); parent.setStyle ('height', parent.movie.mouseY, false); } } /* When the user releases the mouse button, set the flag so the container will stop resizing. */ @onMouseUpAnywhere { dynamic.resizing = false; } } ]]></style> <div class="resizable"> <img src="assets/images/image_3.jpg" /> <div class="resizeGrabber" /> </div> <div class="legend"> <div class="legendArrow" /> <text class="legendTitle">Resize</text> <text>Drag the lower right corner of the image to resize it.</text> </div> </div> <!-- Keyboard & Mouse Demo --> <div name="Keyboard & Mouse"> <style><![CDATA[ .frontalLogo { width: 600px; height: 500px; padding: 0px; is-marker: true; button-mode: false; /* The onKeyDown and onKeyUp interactions are used to listen to keyboard events. The predefined variable 'event' is simply the event object provided by the Flash KeyboardEvent listener, with all of the usual properties: charCode, keyCode, shiftKey, etc. In this example, charCode is used to distinguish the pressed keys. Also, a dynamic variable is created for each key to track accelerate the movement of the frontal logo while the key is held, up to a certain extent. The actual movement of the logo is handled by the Papervision3D API. */ @onKeyDown { switch (event.charCode) { case 97: // pressed "A" dynamic.aDown++; frontalLogoScene.getLogo().moveLeft (Math.min (20, dynamic.aDown)); break; case 115: // pressed "S" dynamic.sDown++; frontalLogoScene.getLogo().moveDown (Math.min (20, dynamic.sDown)); break; case 119: // pressed "W" dynamic.wDown++; frontalLogoScene.getLogo().moveUp (Math.min (20, dynamic.wDown)); break; case 100: // pressed "D" dynamic.dDown++; frontalLogoScene.getLogo().moveRight (Math.min (20, dynamic.dDown)); break; } } @onKeyUp { switch (event.charCode) { case 97: dynamic.aDown = 1; break; case 115: dynamic.sDown = 1; break; case 119: dynamic.wDown = 1; break; case 100: dynamic.dDown = 1; break; } } /* With the 'is-marker' style set to true above, this container gets select events from the tab manager. If this container is currently selected, create the Papervision3D scene and set the focus in the Flash Player to direct keyboard events to this container. Also, add a mouse movement listener that will face the logo towards the mouse cursor. We use the traditional Flash listener here because we want to listen for mouse movement across the entire document, but we don't want the listener to be present when the document loads. When this container is deselected, remove the Papervision scene to conserve resources. */ @onSelect { if (selected) { if ( frontalLogoScene == null ) { frontalLogoScene = new FrontalLogoScene ('assets/images/frontal_logo.gif', gS('width'), gS('height')); } if ( ! movie.contains (frontalLogoScene.getView())) movie.addChildAt (frontalLogoScene.getView(), 0); // Tell the Flash Player to send keyboard events to the logo. document.movie.stage.focus = movie; // Make the logoCube available as a global variable so the mouse listener can access it. logoCube = frontalLogoScene.getLogo(); // Add the mouse movement listener to the entire document. document.movie.addEventListener (flash.events.MouseEvent.MOUSE_MOVE, faceLogoTowardsMouse); // Initialize the key repeat rate dynamic.aDown = 1; dynamic.sDown = 1; dynamic.wDown = 1; dynamic.dDown = 1; } else { // Remove the mouse movement listener when we get deselected to conserve resources. document.movie.removeEventListener (flash.events.MouseEvent.MOUSE_MOVE, faceLogoTowardsMouse); // Remove the papervision scene from the display list to conserve resources. if (frontalLogoScene && movie.contains (frontalLogoScene.getView())) movie.removeChild (frontalLogoScene.getView()); } } } ]]></style> <script><![CDATA[ /* Function: faceLogoTowardsMouse Rotates the logo cube based on the mouse position */ function faceLogoTowardsMouse () { var xDist = frontalLogoScene.getView().mouseX - frontalLogoScene.getView().width * 0.5; var yDist = frontalLogoScene.getView().mouseY - frontalLogoScene.getView().height * 0.5; logoCube.rotationY = -xDist * 0.1; logoCube.rotationX = -yDist * 0.1; } ]]></script> <div id="frontalLogo" class="frontalLogo" mgrId="tabMgr" elemNdx="Keyboard & Mouse" /> <div class="legend"> <div class="legendArrow" /> <text class="legendTitle"><![CDATA[Keyboard & Mouse]]></text> <img src="assets/images/wasd.png" style="margin-left: auto; margin-right: auto;"/> <text style="margin-top: 25px;"><![CDATA[Press and hold a key to slide the Frontal logo or move your mouse cursor to rotate it.]]></text> </div> </div> <!-- Broadcast Demo Shows how to use ruleset IDs to send signals to all containers that match a particular class. The text containers in this demo are separated into numbers and letters by applying the number or letter class. Common styles are applied with the character class. Clicking one of the buttons first calls getStylesheetSelectors, passing the rulesetId of interest as an argument. This call returns the selector(s) associated with the ruleset Id, in this case either the 'letter' or 'number' class. Then, a notification event is manually dispatched and a custom property 'grow' is set on the event object. The notification triggers the onSelectorNotification interaction on all of the characters that match the selector (letters or numbers), causing the characters to grow or shrink in size. --> <div name="Broadcasting"> <style><![CDATA[ #alphaNumera { width: 640px; height: 480px; } .character { color: #ffffff; font-size: 100px; width: 80px; float: left; shadow-angle: 270; shadow-strength: .1; shadow-distance: -1; shadow-blur: 3; } .number { +rulesetId: number @onSelectorNotification { prepStyleTween(); if (event.details.grow) { setStyle ('font-size', '160px', true); setStyle ('width', '95px', true); setStyle ('color', '#f55100', true); } else { setStyle ('font-size', '100px', true); setStyle ('width', '80px', true); setStyle ('color', '#ffffff', true); } tweenStyles(); } } .letter { +rulesetId: letter @onSelectorNotification { prepStyleTween(); if (event.details.grow) { setStyle ('font-size', '160px', true); setStyle ('width', '95px', true); setStyle ('color', '#f55100', true); } else { setStyle ('font-size', '100px', true); setStyle ('width', '80px', true); setStyle ('color', '#ffffff', true); } tweenStyles(); } } .btn { color: #f55100; } .btn:hover { color: #e5e5e5; } #numbersBtn { @onClick { // numbers button clicked, so grow numbers ... var selector = document.stylesManager.getStylesheetSelectors ("number") [ 0 ]; selector.dispatchEvent (new com.frontalcode.FrontalEvent (com.frontalcode.FrontalEvent.NOTIFICATION, { grow: true })); // ... and shrink letters selector = document.stylesManager.getStylesheetSelectors ("letter") [ 0 ]; selector.dispatchEvent (new com.frontalcode.FrontalEvent (com.frontalcode.FrontalEvent.NOTIFICATION, { grow: false })); } } #lettersBtn { @onClick { // letters button clicked, so grow letters ... var selector = document.stylesManager.getStylesheetSelectors ("letter") [ 0 ]; selector.dispatchEvent (new com.frontalcode.FrontalEvent (com.frontalcode.FrontalEvent.NOTIFICATION, { grow: true })); // ... and shrink numbers selector = document.stylesManager.getStylesheetSelectors ("number") [ 0 ]; selector.dispatchEvent (new com.frontalcode.FrontalEvent (com.frontalcode.FrontalEvent.NOTIFICATION, { grow: false })); } } #resetBtn { @onClick { // reset button clicked, so shrink all characters var selector = document.stylesManager.getStylesheetSelectors ("number") [ 0 ]; selector.dispatchEvent (new com.frontalcode.FrontalEvent (com.frontalcode.FrontalEvent.NOTIFICATION, { grow: false })); selector = document.stylesManager.getStylesheetSelectors ("letter") [ 0 ]; selector.dispatchEvent (new com.frontalcode.FrontalEvent (com.frontalcode.FrontalEvent.NOTIFICATION, { grow: false })); } } ]]></style> <div id="alphaNumera"> <text class="character number">0</text> <text class="character letter">F</text> <text class="character number">7</text> <text class="character letter">R</text> <text class="character number">2</text> <text class="character letter">O</text> <text class="character number">0</text> <text class="character letter">N</text> <text class="character letter">T</text> <text class="character number">0</text> <text class="character letter">A</text> <text class="character number">9</text> <text class="character letter">L</text> </div> <div class="legend"> <div class="legendArrow" /> <text class="legendTitle">Broadcasting</text> <text id="lettersBtn" class="btn">Show Letters</text> <text id="numbersBtn" class="btn">Show Numbers</text> <text id="resetBtn" class="btn">Reset</text> </div> </div> </div> </div>