Skip to content

Instantly share code, notes, and snippets.

@ericf
Created February 9, 2011 20:30
Show Gist options
  • Select an option

  • Save ericf/819214 to your computer and use it in GitHub Desktop.

Select an option

Save ericf/819214 to your computer and use it in GitHub Desktop.
MVC-ish YUI 3 Setup
/**
* CaseWindow
*/
var CaseWindow,
CASE_WINDOW = 'caseWindow',
ATTRS = {},
VIEWS = {},
USER_ID = 'userID',
CASE_ID = 'caseID',
CASE_PATH = 'casePath',
CASE_INFO_PATH = 'caseInfoPath',
CASE_RESOURCE = 'caseResource',
CASE_INFO_RESOURCE = 'caseInfoResource',
TITLE = 'title',
STEP = 'step',
INFO_NODES = 'infoNodes',
STATE = 'state',
DIAGNOSES = 'diagnoses',
CONTENT = 'content',
HOST = 'host',
// actions
START = 'start',
MORE = 'more',
READY = 'ready',
DIAGNOSE = 'diagnose',
CHANGE = 'Change',
E_CASE_DATA = 'caseData',
YLang = Y.Lang,
isValue = YLang.isValue,
isArray = YLang.isArray,
isString = YLang.isString,
isNumber = YLang.isNumber;
// *** Attributes *** //
ATTRS[ USER_ID ] = { validator: isString, initOnly: true };
ATTRS[ CASE_ID ] = { validator: isString, initOnly: true };
ATTRS[ CASE_PATH ] = { validator: isString, initOnly: true };
ATTRS[ CASE_INFO_PATH ] = { validator: isString, initOnly: true };
ATTRS[ CASE_RESOURCE ] = { valueFn: '_initCaseResource', writeOnce: true };
ATTRS[ CASE_INFO_RESOURCE ] = { valueFn: '_initCaseInfoResource', writeOnce: true };
ATTRS[ TITLE ] = { validator: isString, writeOnce: true };
ATTRS[ STEP ] = { validator: isNumber };
ATTRS[ INFO_NODES ] = { writeOnce: true };
ATTRS[ STATE ] = { validator: isString };
ATTRS[ DIAGNOSES ] = { validator: isArray };
// *** CaseWindow *** //
CaseWindow = Y.Base.create(CASE_WINDOW, Y.Base, [], {
// *** Prototype *** //
// *** Lifecycle Methods *** //
initializer : function () {
// these attributes much have values
this._requireAttrs([ USER_ID, CASE_ID, CASE_PATH, CASE_INFO_PATH ]);
// publish events
this.publish(E_CASE_DATA, { defaultFn: this._defCaseDataFn });
// attribute change event handlers
this.after(STATE+CHANGE, this._afterStateChange);
// get initial data
this.get(CASE_INFO_RESOURCE).GET({ on: { success: Y.bind(function(e){
this.get(CASE_RESOURCE).GET();
}, this)}});
},
// *** Public Methods *** //
showView : function (newViewClass, prevViewClass) {
var content = Y.one('#content'),
newViewName = newViewClass.NS,
prevViewName = prevViewClass ? prevViewClass.NS : null,
tmp, viewContent;
this.plug(newViewClass, { after: {
contentChange : function(e){
tmp = Y.Markout('body').div().node().hide().append(e.newVal);
}
}});
viewContent = tmp ? tmp.get('children') : this[newViewName].get('content');
function transToNewView () {
content.get('children').remove().destroy(true);
content.removeClass(prevViewName).addClass(newViewName);
content.setContent(viewContent).transition('viewIn');
Y.one('doc').set('scrollTop', 0);
if (tmp) {
tmp.remove().destroy(true);
}
}
if (prevViewClass) {
content.transition('viewOut', Y.bind(function(){
this.unplug(prevViewClass);
transToNewView();
}, this));
} else {
transToNewView();
}
},
start : function () {
this.get(CASE_RESOURCE).POST({ entity: { action: START } });
},
more : function () {
this.get(CASE_RESOURCE).POST({ entity: {
action : MORE,
step : this.get(STEP),
diagnoses : this.getDiagnoses()
}});
},
ready : function () {
this.get(CASE_RESOURCE).POST({ entity: {
action : READY,
step : this.get(STEP),
diagnoses : this.getDiagnoses()
}});
},
diagnose : function (diagnosis, answers) {
this.get(CASE_RESOURCE).POST({ entity: {
action : DIAGNOSE,
diagnosis : diagnosis,
answers : answers
}});
},
getDiagnoses : function () {
var view = this[this.get(STATE)],
diagnoses = view ? view.get(DIAGNOSES) : null;
return diagnoses || this.get(DIAGNOSES);
},
// *** Private Methods *** //
_requireAttrs : function (attrs) {
Y.each(attrs, Y.bind(function(attr){
if ( ! isValue(this.get(attr))) {
Y.error('A CaseWindow needs to be configured with: ' + attr);
}
}, this));
},
_initCaseResource : function () {
var uri = this.get(CASE_PATH) + '{userID}/{caseID}/';
return new Y.Resource({
uri : Y.Lang.sub(uri, {
userID : this.get(USER_ID),
caseID : this.get(CASE_ID)
}),
headers : {
'Accept' : 'application/json',
'Content-Type' : 'application/json'
},
on : {
success : Y.bind(function(e){
this.fire(E_CASE_DATA, { data: e.entity });
}, this)
}
});
},
_initCaseInfoResource : function () {
return new Y.Resource({
uri : this.get(CASE_INFO_PATH),
headers : { Accept: 'application/json' },
on : {
success : Y.bind(function(e){
var info = [];
Y.each(e.entity.info, function(i){
info.push(Y.Node.create(i));
});
this.set(TITLE, e.entity.title);
this.set(INFO_NODES, Y.all(info));
}, this)
}
});
},
_defCaseDataFn : function (e) {
var data = e.data;
this.set(STATE, data.state);
this.set(STEP, data.step);
this.set(DIAGNOSES, data.diagnoses);
},
_afterStateChange : function (e) {
var VIEWS = CaseWindow.VIEWS;
this.showView(VIEWS[e.newVal], VIEWS[e.prevVal]);
}
}, {
// *** Static *** //
ATTRS : ATTRS,
VIEWS : VIEWS
});
// *** Transitions *** //
Y.mix(Y.Transition.fx, {
viewIn : {
opacity : 1,
duration : 0.5,
on : {
start : function(){
this.setStyle('opacity', 0);
}
}
},
viewOut : {
opacity : 0,
duration : 0.5
}
});
// *** Namespace *** //
Y.CaseWindow = CaseWindow;
<!DOCTYPE html>
<html>
<head>
<title>MVC-ish YUI 3 Setup</title>
</head>
<body>
<script src="yui/yui-min.js"></script>
<script>
YUI( /* config */ ).use('window-case', function(Y){
// CaseWindow configuration comes from server/database
new Y.CaseWindow({
"userID" : "7kwfecrx",
"caseID" : "6test",
"casePath" : "/api/case/",
"caseInfoPath" : "/resources/cases/6test.json"
});
});
</script>
</body>
</html>
/**
* CaseWindow — Intro View
*/
Y.mix(Y.CaseWindow.VIEWS, {
intro : Y.Base.create('intro', Y.Plugin.Base, [Y.BaseComponentMgr], {
initializer : function () {
var host = this.get(HOST),
content = Y.Markout(),
moreIntro = Y.one('#intro-more').hide(),
startButton;
this.addComponent('introOverlay', {
requires : ['overlay', 'overlay-extras'],
initializer : function(){
return new Y.Overlay({
width : '500px',
centered : true,
zIndex : 100,
visible : false,
render : true,
srcNode : moreIntro.show(),
headerContent : moreIntro.one('h2'),
bodyContent : moreIntro.one('p'),
footerContent : (function(){
var controls = Y.Markout().div({ 'class': 'controls' });
controls.button({ 'class': 'default' }, 'Okay, Start');
return controls.node();
}()),
plugins : [
Y.Plugin.OverlayModal,
Y.Plugin.OverlayKeepaligned
]
});
},
destructor : function(overlay){
moreIntro.appendTo('#intro');
overlay.destroy();
}
});
content.node().append(Y.one('#intro'));
startButton = content.div({ id: 'start' }).button({ 'class': 'large default' }, 'Start').node();
startButton.on('click', Y.bind(function(e){
e.preventDefault();
this.use('introOverlay', function(overlay){
overlay.show().get('boundingBox').one('button').once('click', Y.bind(function(e){
e.preventDefault();
host.start();
}, this));
});
}, this));
this.set(CONTENT, content.node());
},
destructor : function () {
Y.one('#intro').appendTo('body');
}
}, { NS: 'intro' })
});
/**
* CaseWindow — Steps View
*/
Y.mix(Y.CaseWindow.VIEWS, {
steps : Y.Base.create('steps', Y.Plugin.Base, [Y.BaseComponentMgr], {
initializer : function () {
var host = this.get(HOST),
content = Y.Markout(),
infoViewerNode, infoViewerContentNode, differentialNode;
infoViewerNode = content.div({ id: 'infoViewer', className: 'section' });
infoViewerNode.h2('1) Read Patient Information');
infoViewerContentNode = infoViewerNode.div({ className: 'yui3-infoviewer-loading yui3-infoviewer-content' }).node();
differentialNode = content.div({ id: 'differential', className: 'section' }).node().hide();
this.set(CONTENT, content.node());
this.addComponent('infoViewer', {
requires : ['infoviewer'],
initializer : function(){
return new Y.InfoViewer({
srcNode : infoViewerContentNode,
infoNodes : host.get(INFO_NODES),
step : host.get(STEP),
render : true
});
}
});
this.addComponent('differential', {
requires : ['differential'],
initializer : function(){
return new Y.Differential({
isLastStep : host.get(STEP) === host.get(INFO_NODES).size() - 1,
children : host.get(DIAGNOSES),
render : differentialNode.show('fadeIn', { duration: 0.20 })
});
}
});
this.addComponent('anotherStepOverlay', {
requires : ['overlay', 'overlay-extras'],
initializer : function(){
return new Y.Overlay({
width : '500px',
centered : true,
zIndex : 100,
visible : false,
render : true,
headerContent : Y.Markout().h2('New Information Available').node(),
bodyContent : Y.Markout().p('Some new last-minute information has become available that you should consider before making your diagnosis final.').node(),
footerContent : (function(){
var controls = Y.Markout().div({ 'class': 'controls' });
controls.button({ 'class': 'default' }, 'Okay, View Info');
return controls.node();
}()),
plugins : [
Y.Plugin.OverlayModal,
Y.Plugin.OverlayKeepaligned
]
});
}
});
// create InfoViewer and Differential, and bind to host events
this.use('infoViewer', 'differential', function(infoViewer, differential){
this.afterHostEvent(STEP+CHANGE, function(e){
infoViewer.set(STEP, e.newVal);
if (e.newVal === host.get(INFO_NODES).size() - 1) {
differential.set('isLastStep', true);
}
});
differential.on('more', Y.bind(host.more, host));
differential.on('ready', Y.bind(function(e){
var anotherStepHandler = this.afterHostEvent(STEP+CHANGE, function(e){
anotherStepHandler.detach();
this.use('anotherStepOverlay', function(overlay){
overlay.show().get('boundingBox').one('button').once('click', Y.bind(overlay.hide, overlay));
});
});
host.ready();
}, this));
this.afterHostEvent(DIAGNOSES+CHANGE, function(e){
Y.Array.invoke(differential.removeAll().toJSON(), 'destroy');
differential.add(e.newVal);
});
});
},
_getDiagnoses : function () {
var differential = this.getComponent('differential'),
diagnoses = null;
if (differential) {
diagnoses = [];
differential.each(function(diagnosis){
diagnoses.push({
text : diagnosis.get('text'),
value : diagnosis.get('value')
});
});
}
return diagnoses;
}
}, {
NS : 'steps',
ATTRS : {
diagnoses : { getter: '_getDiagnoses' }
}
})
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment