Skip to content

Instantly share code, notes, and snippets.

@AZaviruha
Forked from bennadel/code-1.txt
Last active August 29, 2015 14:25
Show Gist options
  • Save AZaviruha/464a2a213d41e5b69a32 to your computer and use it in GitHub Desktop.
Save AZaviruha/464a2a213d41e5b69a32 to your computer and use it in GitHub Desktop.
Treating Complex User Interface (UI) Widgets Like Finite State Machines
function gotoState( newState ){ .. }
function gotoState( newState /* [, data ] */ ){ .. }
<!DOCTYPE html>
<html>
<head>
<title>Treating Complex UI Widgets Like State Machines</title>
<style type="text/css">
ol.menuBar {
border: 1px solid #999999 ;
border-left-width: 0px ;
height: 30px ;
list-style-type: none ;
margin: 0px 0px 0px 0px ;
padding: 0px 0px 0px 0px ;
width: 400px ;
}
ol.menuBar > li.menu {
float: left ;
height: 30px ;
margin: 0px 0px 0px 0px ;
padding: 0px 0px 0px 0px ;
position: relative ;
width: 200px ;
}
ol.menuBar a.header {
background-color: #F0F0F0 ;
border-left: 1px solid #999999 ;
color: #333333 ;
display: block ;
height: 30px ;
line-height: 30px ;
padding: 0px 10px 0px 10px ;
width: 179px ;
}
ol.menuBar ol.items {
border: 1px solid #999999 ;
border-width: 0px 1px 1px 1px ;
display: none ;
left: 0px ;
list-style-type: none ;
margin: 0px 0px 0px 0px ;
padding: 0px 0px 0px 0px ;
position: absolute ;
top: 30px ;
width: 199px ;
}
ol.menuBar li.item {
border-top: 1px solid #999999 ;
cursor: pointer ;
height: 30px ;
line-height: 30px ;
margin: 0px 0px 0px 0px ;
padding: 0px 10px 0px 10px ;
}
/* ACTIVE state for MENU. */
ol.menuBar > li.menuInActive > a.header {
border-bottom-width: 0px ;
}
ol.menuBar > li.menuInActive > ol.items {
display: block ;
}
ol.menuBar > li.menuInActive li.item:hover {
background-color: #F0F0F0 ;
}
/* PRECLOSE state for MENU. */
ol.menuBar > li.menuInPreClose > a.header {
border-bottom-width: 0px ;
}
ol.menuBar > li.menuInPreClose > ol.items {
display: block ;
}
</style>
</head>
<body>
<h1>
Treating Complex UI Widgets Like State Machines
</h1>
<!-- BEGIN: Menu Bar Widget. -->
<ol class="menuBar">
<li class="menu">
<a href="#" class="header">
Foods
</a>
<ol class="items">
<li class="item">
Cuban Pork Chops
</li>
<li class="item">
General Tso's Chicken
</li>
<li class="item">
Buffalo Wings
</li>
</ol>
</li>
<li class="menu">
<a href="#" class="header">
Movies
</a>
<ol class="items">
<li class="item">
Better Than Chocolate
</li>
<li class="item">
Terminator 2
</li>
<li class="item">
The Family Man
</li>
</ol>
</li>
</ol>
<!-- END: Menu Bar Widget. -->
<!-- Include JavaScript library. -->
<script type="text/javascript" src="./jquery-1.6.1.js"></script>
<script type="text/javascript">
// Create a sandbox for our menu bar widget controller.
(function( $, menuBar ){
// Cache DOM references for later use.
var dom = {};
dom.menuBar = menuBar;
// This is the current state of the widget. Once the
// states are defined, this will be further set. For this
// widget, we have three defined states:
//
// - Default
// - Active
// - PreClose
var currentState = null;
// I fascilitate the transition from the current to
// the target state.
var gotoState = function( newState /* [, data ] */ ){
// Check to see if the current state is available
// and has a teardown method:
if (
currentState &&
currentState.teardown
){
// Teardown the old state.
currentState.teardown();
}
// Check to see if the new state has a setup method.
if (newState.setup){
// The new state transition may have data
// being passed to it. As such, let's pop off
// the first argument (the state) in order to
// get the setup() argument list.
var setupArguments = Array.prototype.slice.call(
arguments,
1
);
// Setup the new state with the arguments (if
// any) that were passed to this transition.
newState.setup.apply( newState, setupArguments );
}
// Store the new state.
currentState = newState;
};
// Define the states for this widget. Each state is going
// to have a setup and teardown state.
// ---------------------------------------------- //
// ---------------------------------------------- //
var inDefault = {
// I am the description of the state.
description: "I am the state in which all of the menu items are hidden. Only the header of each menu is shown in the menu bar.",
// I am the DOM elements that may be necessary for
// the setup and teardown of this state.
dom: {},
// I setup the current state.
setup: function(){
// Add a mouse-enter event for the menu headers.
// When the user mouses into the header, we need
// to show the relevant menu items.
dom.menuBar.delegate(
"li.menu",
"mouseenter",
function( event ){
// Go to the active state. Since this
// menu bar has more than one menu in it,
// we need to pass the relevant menu on
// to the next state.
gotoState( inActive, $( this ) );
}
);
},
// I teardown the current state.
teardown: function(){
// Remove the mouse-enter event.
dom.menuBar.undelegate(
"li.menu",
"mouseenter"
);
}
};
// ---------------------------------------------- //
// ---------------------------------------------- //
var inActive = {
// I am the description of the state.
description: "I am the state in which the user has moused into a menu header and has caused the menu items for that menu to be shown.",
// I am the DOM elements that may be necessary for
// the setup and teardown of this state.
dom: {},
// I setup the current state.
setup: function( menu ){
// Store the dom elements for this state.
inActive.dom.menu = menu;
inActive.dom.header = menu.children( "a.header" );
inActive.dom.items = menu.children( "ol.items" );
// Change the menu class.
inActive.dom.menu.addClass( "menuInActive" );
// Catch the click handler to prevent any default
// action from taking place.
inActive.dom.header.click(
function( event ){
// Kill the default behavior - this isn't
// a "real" link.
event.preventDefault();
}
);
// Delegate the click items on the menu to listen
// for clicks to individual items.
inActive.dom.items.delegate(
"li.item",
"click",
function( event ){
// Log for proof.
console.log(
"Clicked:",
$.trim( $( this ).text() )
);
}
);
// Bind to the mouse-leave event. If the user
// exits the menu, we need to close it.
inActive.dom.menu.mouseleave(
function( event ){
// Go to the pre-close state. Pass along
// the menu that is being closed.
gotoState( inPreClose, menu );
}
);
},
// I teardown the current state.
teardown: function(){
// Change the menu class.
inActive.dom.menu.removeClass( "menuInActive" );
// Remove the click handler on the header.
inActive.dom.header.unbind( "click" );
// Undelegate the click event for menu items.
inActive.dom.items.undelegate( "li.item", "click" );
}
};
// ---------------------------------------------- //
// ---------------------------------------------- //
var inPreClose = {
// I am the description of the state.
description: "I am the state in which the user has moused-out of the open menu and we need to close it. However, rather than closing it right away, we are going to put a small delay on it. Of course, if the user mouses into another menu, we will close the active one immediatley.",
// I am the DOM elements that may be necessary for
// the setup and teardown of this state.
dom: {},
// I setup the current state.
setup: function( menu ){
// Store the dom elements for this state.
inPreClose.dom.menu = menu;
// Change the menu class.
inPreClose.dom.menu.addClass( "menuInPreClose" );
// Start the timer for the close.
inPreClose.timer = setTimeout(
function(){
// The user has not moved back into the
// menu system. Let's close the menu and
// move back into the default state.
gotoState( inDefault );
},
(1 * 1000)
);
// Bind to the mouse entry of any of the menus.
// Any re-entry will cause us to go to the active
// state for that menu.
dom.menuBar.delegate(
"li.menu",
"mouseenter",
function( event ){
// Go to the active state to show the
// given menu.
gotoState( inActive, $( this ) );
}
);
},
// I teardown the current state.
teardown: function(){
// Change the menu class.
inPreClose.dom.menu.removeClass( "menuInPreClose" );
// Clear the timer (this way, the menu doesn't
// close if we mouse back over it).
clearTimeout( inPreClose.timer );
// Remove the mouse enter event for the menus.
dom.menuBar.undelegate( "li.menu", "mouseenter" );
},
// I am the timer used to keep track of when this
// menu needs to be closed.
timer: null
};
// ---------------------------------------------- //
// ---------------------------------------------- //
// ---------------------------------------------- //
// ---------------------------------------------- //
// To start with, put the menu bar into the default state.
gotoState( inDefault );
})( jQuery, jQuery( "ol.menuBar" ) );
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment