Upload
davejohnson
View
6.185
Download
4
Embed Size (px)
DESCRIPTION
Covers implementation details of five important Ajax design patterns in terms of DOM, CSS and JavaScript.
Citation preview
CommunityOne :: May 5 :: 2008
Agenda
• About me• Patterns Overview• Patterns
– Inline Editing– Composite Controls– Popup– Copy and Paste– Live Scrolling
CommunityOne :: May 5 :: 2008
About
• Author Enterprise Ajax• http://blogs.nitobi.com/dave
CommunityOne :: May 5 :: 2008
Nitobi
• Nitobi co-founder, CTO• Located in Vancouver, Canada• Declarative, server integrated, Ajax
user-interface components• User interface consulting and training
services• Online usability service RobotReplay
CommunityOne :: May 5 :: 2008
CommunityOne :: May 5 :: 2008
Customers
CommunityOne :: May 5 :: 2008
Patterns
OVERVIEW
CommunityOne :: May 5 :: 2008
“Each pattern describes a problem which occurs over and over again in our environment, and then describes the core of the solution to that problem, in such a way that you can use this solution a million times over, without ever doing it the same way twice”
A Pattern Language
CommunityOne :: May 5 :: 2008
Architectural Patterns
Literally.
• MVC• Peer-to-peer• SOA
CommunityOne :: May 5 :: 2008
Software Design Patterns
• Gang of Four (GoF)– Creational– Behavioural– Structural
• Others– Concurrency
CommunityOne :: May 5 :: 2008
UI Design Patterns
• Bill Scott and others• Described as
– Problem– Context– Principle– Solution– Why / How
CommunityOne :: May 5 :: 2008
Ajax Design Patterns
• What we are talking about today
• Applies principles from all areas:– MVC– UI– OOP
CommunityOne :: May 5 :: 2008
Bad Patterns
• Increased complexity• Does not provide re-use• Language hacks
THE PROBLEMS
CommunityOne :: May 5 :: 2008
ObserverEvent = function(type) {
this.handlers = {};this.guid = 0;
}Event.prototype.subscribe = function(func) {
this.handlers[this.guid++] = func;return guid;
}Event.prototype.notify = function(evtArgs) {
for (var item in this.handlers) {this.handlers[item].apply(this, arguments);
}}
CommunityOne :: May 5 :: 2008
“The meaning of life is that it is to be lived, and it is not to be traded and conceptualized and squeezed into a pattern of systems”
Bruce Lee
CommunityOne :: May 5 :: 2008
Programmers Are Lazy
“If you have a difficult task, give it to a lazy person - they will find an easier way to do it.”
Hlade's Law
CommunityOne :: May 5 :: 2008
Ajax Patterns - Goals
• Making the web like the desktop• Improving user experience• More responsive applications• Remove web / desktop divide• Marriage of JavaScript, DOM, CSS
CommunityOne :: May 5 :: 2008
Describing Ajax Patterns
What is the patternWhen is the pattern appliedWhy use the patternHow is the pattern implemented
CommunityOne :: May 5 :: 2008
Best Practices
• Consider the user and the developer
• Common approaches– Progressive enhancement
http://en.wikipedia.org/wiki/Progressive_enhancement
– Unobtrusive JavaScripthttp://en.wikipedia.org/wiki/Unobtrusive_JavaScript
CommunityOne :: May 5 :: 2008
Pitfalls
• Cross browser• Cross doctype• I18n and localization• Accessibility
CommunityOne :: May 5 :: 2008
THE PATTERNS
CommunityOne :: May 5 :: 2008
Inline Edit
• What edit in page without page refresh
• When titles, tags etc• Why remove page refreshes
CommunityOne :: May 5 :: 2008
How
1. User invited to click on data field2. Field is replaced with an editing
control3. User blurs or saves and field is
saved behind the scenes and editor removed
CommunityOne :: May 5 :: 2008
CommunityOne :: May 5 :: 2008
DOM
<div id="title" class="editable">Edit me!</div>
CommunityOne :: May 5 :: 2008
CSS
#title {
width:200px;
border:1px solid black;
cursor:pointer;
cursor:hand;
}
CommunityOne :: May 5 :: 2008
Editedit: function(evt) {
//create the editorthis.editor = nitobi.html.createElement( “input”, { id : ”editor”, type : ”text”, value : this.node.innerHTML }, { width: this.node.offsetWidth, height: this.node.offsetHeight );this.node.replaceChild(this.editor, this.node.firstChild);
//set the focus so that it is ready to gothis.editor.focus();
//attach events for bluring and key handlingnitobi.html.attachEvent(this.editor, "blur", this.save);nitobi.html.attachEvent(this.editor, "keydown", this.keydown);
//detach the event for going into edit modenitobi.html.detachEvent(this.node, "click", this.edit);
}
CommunityOne :: May 5 :: 2008
Key Handling
keydown: function(evt) {
//check for enter key at least
if (evt.keyCode == 13) {
this.editor.blur();
}
}
CommunityOne :: May 5 :: 2008
Savingsave: function(evt) {
//send the data to the servervar xhr = new HttpRequest();xhr.onRequestComplete.subscribe(this.afterSave);xhr.open(“POST”, “mysite.com”, true);xhr.send(“title=“+this.editor.value);//revert to the view onlythis.node.innerHTML = this.editor.value;//show an activity indicatorthis.activity.style.display = “inline”;
},afterSave: function(httpResponse) {
//concurrency pattern//undo with command patternattachEvent(this.node, "click", this.edit);
}
CommunityOne :: May 5 :: 2008
Pitfalls / Best Practices
• Potential Pitfalls – page contents can move when changing
“modes”– too subtle invitation to edit– too many edit invitations– concurrency or failure
• Best Practices – avoid page jitter – make render & edit modes same size – activate on click– deactivate on blur
CommunityOne :: May 5 :: 2008
Composite Controls
• What inline edit multiple text fields• When simple forms, combo box• Why remove page refreshes
CommunityOne :: May 5 :: 2008
How
1. Create a Form JavaScript class that implements IBlurable interface
2. All form elements have key and mouse events attached through IBlurable
3. When user clicks on fields the mousedown event on the newly clicked field blocks the blur on the previous field
4. When user clicks outside of the composite control the blur event is not blocked
CommunityOne :: May 5 :: 2008
Aside: Event Order
• Click on the first name field– mousedown– focus– mouseup– click
• Click on the last name field– mousedown (last name)– blur (first name)– focus (last name)– mouseup (last name)– click (last name)
• Also consider relatedTarget / fromElement
CommunityOne :: May 5 :: 2008
CommunityOne :: May 5 :: 2008
DOM
<form id="form" name="form">
<label for="first">First: </label>
<input name="first" id="first">
<label for="last">Last: </label>
<input name="last" id="last">
</form>
CommunityOne :: May 5 :: 2008
NameFormNameForm = function(node) {
this.form = node;//setup the blurable interfaceIBlurable.call(this, this.form.elements);this.onBlur.subscribe(this.save, this);
}
//implement the interface (ie copy over the methods)nitobi.lang.implement(NameForm, IBlurable);
NameForm.prototype.save = function(evt) {//same issues as inline edit casevar xhr = new nitobi.ajax.HttpRequest();xhr.open("POST", "http://nitobi.com/name", true);xhr.send(this.form);
};
CommunityOne :: May 5 :: 2008
Pitfalls / Best Practices
• Potential Pitfalls – event order differences across browsers– event problems on certain controls
(select, scrollbars)
• Best Practices – use for data validation behind the
scenes on sub-forms– consider both keyboard (tabbing) and
mouse events
CommunityOne :: May 5 :: 2008
Popup
• What display additional information• When details are required in context• Why remove page refresh
CommunityOne :: May 5 :: 2008
How
1. User moves mouse over element that has mouseover event attached
2. Popup is displayed3. User moves over another part of the
trigger element firing mouseout event on a timeout
4. If show is called while waiting for a hide the hide is cancelled preventing flicker and allowing user to mouse over the contents of the popup
CommunityOne :: May 5 :: 2008
CommunityOne :: May 5 :: 2008
DOM
<span id="title"><strong>
<a href="#">Iron Man</a></strong><span class="year">(2008)</span>
</span>
<div id="details">When wealthy ... <a href="#">(more)</a>
</div>
CommunityOne :: May 5 :: 2008
Aside: Event Bubbling
• Events bubble up through the DOM• They are also “captured”• Event handlers fire when trigged
from any child node
CommunityOne :: May 5 :: 2008
CSS
#details {position: absolute;display: none;top: 0px;left: 0px;width: 300px;height: 100px;border: 1px solid black;background-color: white;
}
CommunityOne :: May 5 :: 2008
ConstructorPopup = function(title, detail) {
this.detail = detail;//attach mouseover event for shownitobi.html.attachEvent(title, "mouseover", this.show);var _t = this;this.hideTimeout = null;//attach a delayed mouseout event for hidenitobi.html.attachEvent(title, "mouseout", function() {
_t.hideTimeout = setTimeout(_t.hide, 200);});
}
CommunityOne :: May 5 :: 2008
ShowPopup.prototype.show = function(evt) {
if (this.hideTimeout != null) {//clear the hide timeout
clearTimeout(this.hideTimeout);this.hideTimeout
} else {//show the popupvar style = this.detail.style;style.display = "block";style.top = (evt.clientY + 5) + "px";style.left = (evt.clientX + 5) + "px";
}}
CommunityOne :: May 5 :: 2008
Pitfalls / Best Practices
• Potential Pitfalls – mouse event bubbling causes flickering
or moving of the popup– rogue popups– Interaction between trigger and
contents• Best Practices
– enable keyboard access– provide some way to close manually– make certain it disappears!
CommunityOne :: May 5 :: 2008
Copy and Paste
• What user can enter bulk data• When interop desktop and browser• Why remove page refreshes
CommunityOne :: May 5 :: 2008
How - Copy
1. User clicks on focusable element (<a>)2. Filter for ctrl+c on keydown event3. Set value of hidden <textarea> to the
data that is to be copied4. Focus on the hidden <textarea>5. Magic6. Capture keyup event on hidden
<textarea> to focus back on your control7. <textarea> value now on OS clipboard
CommunityOne :: May 5 :: 2008
How - Paste
1. User clicks on focusable element (<a>)2. Filter for ctrl+v on keydown event3. Focus on the hidden <textarea>4. Magic5. Capture keyup event on hidden
<textarea>6. Data from OS clipboard now in the
<textarea> where you can access it and focus back on your control
CommunityOne :: May 5 :: 2008
CommunityOne :: May 5 :: 2008
DOM
<div id="copyable">
Copy this text
</div>
<textarea id=“clipboard”></textarea>
CommunityOne :: May 5 :: 2008
CSS
#clipboard {position: absolute;
left: -5000px;
}
CommunityOne :: May 5 :: 2008
Constructorvar Copyable = function(node) {
//replace the contents with an <a> tag so that it is focusablethis.source = node;this.source.innerHTML = "<a href=\"#\" class=\"focusable\">"+this.source.innerHTML+"</a>";
//intercept all key presses to look for ctrl+c/vnitobi.html.attachEvent(this.source, "keydown", this.handleKey);
//create the clipboardthis.clipboard = nitobi.html.createElement("textarea", { id : "clipboard"+node.id } );document.body.appendChild(this.clipboard);
}
CommunityOne :: May 5 :: 2008
handleKey
handleKey: function(evt) {var k = evt.keyCode;//offset keycode for modifier keysk = k + (evt.shiftKey?256:0)+
(evt.ctrlKey?512:0)+(evt.metaKey?1024:0);
//lookup the method in a mapvar handler = this.keyMap[k];//call that methodif (handler != null)
handler.call(this);}
CommunityOne :: May 5 :: 2008
Copycopy: function() {
//get the data we want to copyvar data = this.source.firstChild.innerHTML;if (!nitobi.browser.IE) {
//focus back control when the copy is completeattachEvent(this.clipboard, "keyup",
this.focus);//set the value, focus and select the valuethis.clipboard.value = data;this.clipboard.focus();
this.clipboard.setSelectionRange(0,data.length);} else {
window.clipboardData.setData("Text",data);}
}
CommunityOne :: May 5 :: 2008
Pastepaste: function() {
//catch the textarea keyup to grab the resultsnitobi.html.attachEventOnce(this.clipboard, "keyup", this.pasteReady);this.clipboard.focus();
},
pasteReady: function() {//get the clipboard value and insert itthis.source.firstChild.innerHTML = this.clipboard.value;//focus back on the controlthis.source.firstChild.focus();
}
CommunityOne :: May 5 :: 2008
Pitfalls / Best Practices
• Pitfalls– user is unaware of functionality– keyboard short cuts don’t match OS– no context menu support in some
browsers• Best Practices
– capture ctrl/cmd+c/v/x for different OS– support various formats depending on
use case
CommunityOne :: May 5 :: 2008
Live Scrolling
• What scroll through large datasets• When viewing, filtering, sorting• Why remove page refreshes
CommunityOne :: May 5 :: 2008
How
1. Create clipping <div> and surface <div> where data goes
2. Repeat for a scrollbar3. Connect scrollbar scroll events to
position the data surface4. Retrieve data from the server as
user scrolls
CommunityOne :: May 5 :: 2008
CommunityOne :: May 5 :: 2008
DOM
<div id=“viewport”><div id=“surface”>
<div id=“data”></div></div>
</div>
<div id=“scrollcontainer"><div id="scrollbar">
<div id=“range”></div></div>
</div>
CommunityOne :: May 5 :: 2008
CSS#scrollcontainer {
width: 16px;height: 300px;overflow: hidden;
}#scrollbar {
height: 100%;width: 16px;//width: 17px;position: relative;overflow-x: hidden;overflow-y: scroll;
}#range {
overflow: hidden;width: 1px;height: 3000px;"
}
#viewport {position: relative;overflow: hidden;width: 300px;height: 300px;border: 1px solid black;
}
#surface {width: 2000px;height: 2000px;
}
CommunityOne :: May 5 :: 2008
Scrolling
var evt = “mousewheel”;
if (nitobi.browser.MOZ)
evt = “DOMMouseScroll”;
//attach the event to the surface
nitobi.html.attachEvent(
scollSurface, evt,
this.handleMouseWheel);
CommunityOne :: May 5 :: 2008
Pitfalls / Best Practices
• Potential Pitfalls – dual-scrollbar issue – sluggish performance – extremely large data sets
• Best Practices – provide dynamic tooltip showing location
within scroll – animate scroll – if desire a hybrid, use animation on paging.– support mouse scroll wheel
CommunityOne :: May 5 :: 2008
Acknowledgements
• Bill Scott• Michael Mahemoff• Andre Charland
CommunityOne :: May 5 :: 2008
Thank You
Questions?
email: [email protected]: blogs.nitobi.com/davebook: enterpriseajax.comtweets: twitter.com/davejohnson
CommunityOne :: May 5 :: 2008
http://flickr.com/photos/daveknapik/2115474105/http://flickr.com/photos/preciouskhyatt/2153351428/http://flickr.com/photos/sandcastlematt/403101240/