SemanticScuttle/includes/js/dojox/presentation/_base.js

557 lines
16 KiB
JavaScript

if(!dojo._hasResource["dojox.presentation._base"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.presentation._base"] = true;
dojo.provide("dojox.presentation._base");
dojo.experimental("dojox.presentation");
dojo.require("dijit._Widget");
dojo.require("dijit._Container");
dojo.require("dijit._Templated");
dojo.require("dijit.layout.StackContainer");
dojo.require("dijit.layout.ContentPane");
dojo.require("dojo.fx");
dojo.declare("dojox.presentation.Deck", [ dijit.layout.StackContainer, dijit._Templated ], {
// summary:
// dojox.presentation class
// basic powerpoint esque engine for handling transitons and control
// in a page-by-page and part-by-part way
//
// FIXME: parsing part(s)/widget(s) in href="" Slides not working
// TODO: make auto actions progress.
// FIXME: Safari keydown/press/up listener not working.
// noClick=true prevents progression of slides in that broweser
//
// fullScreen: Boolean
// unsupported (that i know of) just yet. Default it to take control
// of window. Would be nice to be able to contain presentation in a
// styled container, like StackContainer ... theoretically possible.
// [and may not need this variable?]
fullScreen: true,
// useNav: Boolean
// true to allow navigation popup, false to disallow
useNav: true,
// navDuration: Integer
// time in MS fadein/out of popup nav [default: 250]
navDuration: 250,
// noClick: Boolean
// if true, prevents _any_ click events to propagate actions
// (limiting control to keyboard and/or action.on="auto" or action.delay=""
// actions.
noClick: false,
// setHash: Boolean
// if true, window location bar will get a #link to slide for direct
// access to a particular slide number.
setHash: true,
// just to over-ride:
templateString: null,
templateString:"<div class=\"dojoShow\" dojoAttachPoint=\"showHolder\">\n\t<div class=\"dojoShowNav\" dojoAttachPoint=\"showNav\" dojoAttachEvent=\"onmouseover: _showNav, onmouseout: _hideNav\">\n\t<div class=\"dojoShowNavToggler\" dojoAttachPoint=\"showToggler\">\n\t\t<img dojoAttachPoint=\"prevNode\" src=\"${prevIcon}\" dojoAttachEvent=\"onclick:previousSlide\">\n\t\t<select dojoAttachEvent=\"onchange:_onEvent\" dojoAttachPoint=\"select\">\n\t\t\t<option dojoAttachPoint=\"_option\">Title</option>\n\t\t</select>\n\t\t<img dojoAttachPoint=\"nextNode\" src=\"${nextIcon}\" dojoAttachEvent=\"onclick:nextSlide\">\n\t</div>\n\t</div>\n\t<div dojoAttachPoint=\"containerNode\"></div>\n</div>\n",
// nextIcon: String
// icon for navigation "next" button
nextIcon: dojo.moduleUrl('dojox.presentation','resources/icons/next.png'),
// prevIcon: String
// icon for navigation "previous" button
prevIcon: dojo.moduleUrl('dojox.presentation','resources/icons/prev.png'),
_navOpacMin: 0,
_navOpacMax: 0.85,
_slideIndex: 0,
// Private:
_slides: [],
_navShowing: true,
_inNav: false,
startup: function(){
// summary: connect to the various handlers and controls for this presention
this.inherited(arguments);
if(this.useNav){
this._hideNav();
}else{
this.showNav.style.display = "none";
}
this.connect(document,'onclick', '_onEvent');
this.connect(document,'onkeypress', '_onEvent');
// only if this.fullScreen == true?
this.connect(window, 'onresize', '_resizeWindow');
this._resizeWindow();
this._updateSlides();
this._readHash();
this._setHash();
},
moveTo: function(/* Integer */ number){
// summary: jump to slide based on param
var slideIndex = number - 1;
if(slideIndex < 0)
slideIndex = 0;
if(slideIndex > this._slides.length - 1)
slideIndex = this._slides.length - 1;
this._gotoSlide(slideIndex);
},
onMove: function (number){
// summary: stub function? TODOC: ?
},
nextSlide: function(/*Event*/ evt){
// summary: transition to the next slide.
if (!this.selectedChildWidget.isLastChild) {
this._gotoSlide(this._slideIndex+1);
}
if (evt) { evt.stopPropagation(); }
},
previousSlide: function(/*Event*/ evt){
// summary: transition to the previous slide
if (!this.selectedChildWidget.isFirstChild) {
this._gotoSlide(this._slideIndex-1);
} else { this.selectedChildWidget._reset(); }
if (evt) { evt.stopPropagation();}
},
getHash: function(id){
// summary: get the current hash to set in localtion
return this.id+"_SlideNo_"+id;
},
_hideNav: function(evt){
// summary: hides navigation
if(this._navAnim){ this._navAnim.stop(); }
this._navAnim = dojo.animateProperty({
node:this.showNav,
duration:this.navDuration,
properties: {
opacity: { end:this._navOpacMin }
}
}).play();
},
_showNav: function(evt){
// summary: shows navigation
if(this._navAnim){ this._navAnim.stop(); }
this._navAnim = dojo.animateProperty({
node:this.showNav,
duration:this.navDuration,
properties: {
opacity: { end:this._navOpacMax }
}
}).play();
},
_handleNav: function(evt){
// summary: does nothing? _that_ seems useful.
evt.stopPropagation();
},
_updateSlides: function(){
// summary:
// populate navigation select list with refs to slides call this
// if you add a node to your presentation dynamically.
this._slides = this.getChildren();
if(this.useNav){
// populate the select box with top-level slides
var i=0;
dojo.forEach(this._slides,dojo.hitch(this,function(slide){
i++;
var tmp = this._option.cloneNode(true);
tmp.text = slide.title+" ("+i+") ";
this._option.parentNode.insertBefore(tmp,this._option);
}));
if(this._option.parentNode){
this._option.parentNode.removeChild(this._option);
}
// dojo._destroyElement(this._option);
}
},
_onEvent: function(/* Event */ evt){
// summary:
// main presentation function, determines next 'best action' for a
// specified event.
var _node = evt.target;
var _type = evt.type;
if(_type == "click" || _type == "change"){
if(_node.index && _node.parentNode == this.select){
this._gotoSlide(_node.index);
}else if(_node == this.select){
this._gotoSlide(_node.selectedIndex);
}else{
if (this.noClick || this.selectedChildWidget.noClick || this._isUnclickable(evt)) return;
this.selectedChildWidget._nextAction(evt);
}
}else if(_type=="keydown" || _type == "keypress"){
// FIXME: safari doesn't report keydown/keypress?
var key = (evt.charCode == dojo.keys.SPACE ? dojo.keys.SPACE : evt.keyCode);
switch(key){
case dojo.keys.DELETE:
case dojo.keys.BACKSPACE:
case dojo.keys.LEFT_ARROW:
case dojo.keys.UP_ARROW:
case dojo.keys.PAGE_UP:
case 80: // key 'p'
this.previousSlide(evt);
break;
case dojo.keys.ENTER:
case dojo.keys.SPACE:
case dojo.keys.RIGHT_ARROW:
case dojo.keys.DOWN_ARROW:
case dojo.keys.PAGE_DOWN:
case 78: // key 'n'
this.selectedChildWidget._nextAction(evt);
break;
case dojo.keys.HOME: this._gotoSlide(0);
}
}
this._resizeWindow();
evt.stopPropagation();
},
_gotoSlide: function(/* Integer */ slideIndex){
// summary: goes to slide
this.selectChild(this._slides[slideIndex]);
this.selectedChildWidget._reset();
this._slideIndex = slideIndex;
if(this.useNav){
this.select.selectedIndex = slideIndex;
}
if(this.setHash){
this._setHash();
}
this.onMove(this._slideIndex+1);
},
_isUnclickable: function(/* Event */ evt){
// summary: returns true||false base of a nodes click-ability
var nodeName = evt.target.nodeName.toLowerCase();
// TODO: check for noClick='true' in target attrs & return true
// TODO: check for relayClick='true' in target attrs & return false
switch(nodeName){
case 'a' :
case 'input' :
case 'textarea' : return true; break;
}
return false;
},
_readHash: function(){
var th = window.location.hash;
if (th.length && this.setHash) {
var parts = (""+window.location).split(this.getHash(''));
if(parts.length>1){
this._gotoSlide(parseInt(parts[1])-1);
}
}
},
_setHash: function(){
// summary: sets url #mark to direct slide access
if(this.setHash){
var slideNo = this._slideIndex+1;
window.location.href = "#"+this.getHash(slideNo);
}
},
_resizeWindow: function(/*Event*/ evt){
// summary: resize this and children to fix this window/container
// only if this.fullScreen?
dojo.body().style.height = "auto";
var wh = dijit.getViewport();
var h = Math.max(
document.documentElement.scrollHeight || dojo.body().scrollHeight,
wh.h);
var w = wh.w;
this.selectedChildWidget.domNode.style.height = h +'px';
this.selectedChildWidget.domNode.style.width = w +'px';
},
_transition: function(newWidget,oldWidget){
// summary: over-ride stackcontainers _transition method
// but atm, i find it to be ugly with not way to call
// _showChild() without over-riding it too. hopefull
// basic toggles in superclass._transition will be available
// in dijit, and this won't be necessary.
var anims = [];
if(oldWidget){
/*
anims.push(dojo.fadeOut({ node: oldWidget.domNode,
duration:250,
onEnd: dojo.hitch(this,function(){
this._hideChild(oldWidget);
})
}));
*/
this._hideChild(oldWidget);
}
if(newWidget){
/*
anims.push(dojo.fadeIn({
node:newWidget.domNode, start:0, end:1,
duration:300,
onEnd: dojo.hitch(this,function(){
this._showChild(newWidget);
newWidget._reset();
})
})
);
*/
this._showChild(newWidget);
newWidget._reset();
}
//dojo.fx.combine(anims).play();
}
});
dojo.declare(
"dojox.presentation.Slide",
[dijit.layout.ContentPane,dijit._Contained,dijit._Container,dijit._Templated],
{
// summary:
// a Comonent of a dojox.presentation, and container for each 'Slide'
// made up of direct HTML (no part/action relationship), and dojox.presentation.Part(s),
// and their attached Actions.
// templatPath: String
// make a ContentPane templated, and style the 'titleNode'
templateString:"<div dojoAttachPoint=\"showSlide\" class=\"dojoShowPrint dojoShowSlide\">\n\t<h1 class=\"showTitle\" dojoAttachPoint=\"slideTitle\"><span class=\"dojoShowSlideTitle\" dojoAttachPoint=\"slideTitleText\">${title}</span></h1>\n\t<div class=\"dojoShowBody\" dojoAttachPoint=\"containerNode\"></div>\n</div>\n",
// title: String
// string to insert into titleNode, title of Slide
title: "",
// inherited from ContentPane FIXME: don't seem to work ATM?
refreshOnShow: true,
preLoad: false,
doLayout: true,
parseContent: true,
// noClick: Boolean
// true on slide tag prevents clicking, false allows
// (can also be set on base presentation for global control)
noClick: false,
// private holders:
_parts: [],
_actions: [],
_actionIndex: 0,
_runningDelay: false,
startup: function(){
// summary: setup this slide with actions and components (Parts)
this.inherited(arguments);
this.slideTitleText.innerHTML = this.title;
var children = this.getChildren();
this._actions = [];
dojo.forEach(children,function(child){
var tmpClass = child.declaredClass.toLowerCase();
switch(tmpClass){
case "dojox.presentation.part" : this._parts.push(child); break;
case "dojox.presentation.action" : this._actions.push(child); break;
}
},this);
},
_nextAction: function(evt){
// summary: gotoAndPlay current cached action
var tmpAction = this._actions[this._actionIndex] || 0;
if (tmpAction){
// is this action a delayed action? [auto? thoughts?]
if(tmpAction.on == "delay"){
this._runningDelay = setTimeout(
dojo.hitch(tmpAction,"_runAction"),tmpAction.delay
);
console.debug('started delay action',this._runningDelay);
}else{
tmpAction._runAction();
}
// FIXME: it gets hairy here. maybe runAction should
// call _actionIndex++ onEnd? if a delayed action is running, do
// we want to prevent action++?
var tmpNext = this._getNextAction();
this._actionIndex++;
if(tmpNext.on == "delay"){
// FIXME: yeah it looks like _runAction() onend should report
// _actionIndex++
console.debug('started delay action',this._runningDelay);
setTimeout(dojo.hitch(tmpNext,"_runAction"),tmpNext.delay);
}
}else{
// no more actions in this slide
this.getParent().nextSlide(evt);
}
},
_getNextAction: function(){
// summary: returns the _next action in this sequence
return this._actions[this._actionIndex+1] || 0;
},
_reset: function(){
// summary: set action chain back to 0 and re-init each Part
this._actionIndex = [0];
dojo.forEach(this._parts,function(part){
part._reset();
},this);
}
});
dojo.declare("dojox.presentation.Part", [dijit._Widget,dijit._Contained], {
// summary:
// a node in a presentation.Slide that inherits control from a
// dojox.presentation.Action
// can be any element type, and requires styling before parsing
//
// as: String
// like an ID, attach to Action via (part) as="" / (action) forSlide="" tags
// this should be unique identifier?
as: "",
// startVisible: boolean
// true to leave in page on slide startup/reset
// false to hide on slide startup/reset
startVisible: false,
// isShowing: Boolean,
// private holder for _current_ state of Part
_isShowing: false,
postCreate: function(){
// summary: override and init() this component
this._reset();
},
_reset: function(){
// summary: set part back to initial calculate state
// these _seem_ backwards, but quickToggle flips it
this._isShowing =! this.startVisible;
this._quickToggle();
},
_quickToggle: function(){
// summary: ugly [unworking] fix to test setting state of component
// before/after an animation. display:none prevents fadeIns?
if(this._isShowing){
dojo.style(this.domNode,'display','none');
dojo.style(this.domNode,'visibility','hidden');
dojo.style(this.domNode,'opacity',0);
}else{
dojo.style(this.domNode,'display','');
dojo.style(this.domNode,'visibility','visible');
dojo.style(this.domNode,'opacity',1);
}
this._isShowing =! this._isShowing;
}
});
dojo.declare("dojox.presentation.Action", [dijit._Widget,dijit._Contained], {
// summary:
// a widget to attach to a dojox.presentation.Part to control
// it's properties based on an inherited chain of events ...
//
//
// on: String
// FIXME: only 'click' supported ATM. plans include on="delay",
// on="end" of="", and on="auto". those should make semantic sense
// to you.
on: 'click',
// forSlide: String
// attach this action to a dojox.presentation.Part with a matching 'as' attribute
forSlide: "",
// toggle: String
// will toggle attached [matching] node(s) via forSlide/as relationship(s)
toggle: 'fade',
// delay: Integer
//
delay: 0,
// duration: Integer
// default time in MS to run this action effect on it's 'forSlide' node
duration: 1000,
// private holders:
_attached: [],
_nullAnim: false,
_runAction: function(){
// summary: runs this action on attached node(s)
var anims = [];
// executes the action for each attached 'Part'
dojo.forEach(this._attached,function(node){
// FIXME: this is ugly, and where is toggle class? :(
var dir = (node._isShowing) ? "Out" : "In";
// node._isShowing =! node._isShowing;
//var _anim = dojox.fx[ this.toggle ? this.toggle+dir : "fade"+dir]({
var _anim = dojo.fadeIn({
node:node.domNode,
duration: this.duration,
beforeBegin: dojo.hitch(node,"_quickToggle")
});
anims.push(_anim);
},this);
var _anim = dojo.fx.combine(anims);
if(_anim){ _anim.play(); }
},
_getSiblingsByType: function(/* String */ declaredClass){
// summary: quick replacement for getChildrenByType("class"), but in
// a child here ... so it's getSiblings. courtesy bill in #dojo
// could be moved into parent, and just call this.getChildren(),
// which makes more sense.
var siblings = dojo.filter( this.getParent().getChildren(), function(widget){
return widget.declaredClass==declaredClass;
}
);
return siblings; // dijit._Widget
},
postCreate: function(){
// summary: run this once, should this be startup: function()?
this.inherited(arguments);
// prevent actions from being visible, _always_
dojo.style(this.domNode,"display","none");
var parents = this._getSiblingsByType('dojox.presentation.Part');
// create a list of "parts" we are attached to via forSlide/as
this._attached = [];
dojo.forEach(parents,function(parentPart){
if(this.forSlide == parentPart.as){
this._attached.push(parentPart);
}
},this);
}
});
}