_base.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625
  1. if (!dojo._hasResource["dojox.presentation._base"]) { // _hasResource checks
  2. // added by build. Do
  3. // not use _hasResource
  4. // directly in your
  5. // code.
  6. dojo._hasResource["dojox.presentation._base"] = true;
  7. dojo.provide("dojox.presentation._base");
  8. dojo.experimental("dojox.presentation");
  9. dojo.require("dijit._Widget");
  10. dojo.require("dijit._Container");
  11. dojo.require("dijit._Templated");
  12. dojo.require("dijit.layout.StackContainer");
  13. dojo.require("dijit.layout.ContentPane");
  14. dojo.require("dojo.fx");
  15. dojo.declare("dojox.presentation.Deck", [dijit.layout.StackContainer,
  16. dijit._Templated], {
  17. // summary:
  18. // dojox.presentation class
  19. // basic powerpoint esque engine for handling transitons and
  20. // control
  21. // in a page-by-page and part-by-part way
  22. //
  23. // FIXME: parsing part(s)/widget(s) in href="" Slides not
  24. // working
  25. // TODO: make auto actions progress.
  26. // FIXME: Safari keydown/press/up listener not working.
  27. // noClick=true prevents progression of slides in that broweser
  28. //
  29. // fullScreen: Boolean
  30. // unsupported (that i know of) just yet. Default it to take
  31. // control
  32. // of window. Would be nice to be able to contain presentation
  33. // in a
  34. // styled container, like StackContainer ... theoretically
  35. // possible.
  36. // [and may not need this variable?]
  37. fullScreen : true,
  38. // useNav: Boolean
  39. // true to allow navigation popup, false to disallow
  40. useNav : true,
  41. // navDuration: Integer
  42. // time in MS fadein/out of popup nav [default: 250]
  43. navDuration : 250,
  44. // noClick: Boolean
  45. // if true, prevents _any_ click events to propagate actions
  46. // (limiting control to keyboard and/or action.on="auto" or
  47. // action.delay=""
  48. // actions.
  49. noClick : false,
  50. // setHash: Boolean
  51. // if true, window location bar will get a #link to slide for
  52. // direct
  53. // access to a particular slide number.
  54. setHash : true,
  55. // just to over-ride:
  56. templateString : null,
  57. 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",
  58. // nextIcon: String
  59. // icon for navigation "next" button
  60. nextIcon : dojo.moduleUrl('dojox.presentation',
  61. 'resources/icons/next.png'),
  62. // prevIcon: String
  63. // icon for navigation "previous" button
  64. prevIcon : dojo.moduleUrl('dojox.presentation',
  65. 'resources/icons/prev.png'),
  66. _navOpacMin : 0,
  67. _navOpacMax : 0.85,
  68. _slideIndex : 0,
  69. // Private:
  70. _slides : [],
  71. _navShowing : true,
  72. _inNav : false,
  73. startup : function() {
  74. // summary: connect to the various handlers and controls for
  75. // this presention
  76. dojox.presentation.Deck.superclass.startup.call(this);
  77. if (this.useNav) {
  78. this._hideNav();
  79. } else {
  80. this.showNav.style.display = "none";
  81. }
  82. this.connect(document, 'onclick', '_onEvent');
  83. this.connect(document, 'onkeypress', '_onEvent');
  84. // only if this.fullScreen == true?
  85. this.connect(window, 'onresize', '_resizeWindow');
  86. this._resizeWindow();
  87. this._updateSlides();
  88. this._readHash();
  89. this._setHash();
  90. },
  91. moveTo : function(/* Integer */number) {
  92. // summary: jump to slide based on param
  93. var slideIndex = number - 1;
  94. if (slideIndex < 0)
  95. slideIndex = 0;
  96. if (slideIndex > this._slides.length - 1)
  97. slideIndex = this._slides.length - 1;
  98. this._gotoSlide(slideIndex);
  99. },
  100. onMove : function(number) {
  101. // summary: stub function? TODOC: ?
  102. },
  103. nextSlide : function(/* Event */evt) {
  104. // summary: transition to the next slide.
  105. if (!this.selectedChildWidget.isLastChild) {
  106. this._gotoSlide(this._slideIndex + 1);
  107. }
  108. if (evt) {
  109. evt.stopPropagation();
  110. }
  111. },
  112. previousSlide : function(/* Event */evt) {
  113. // summary: transition to the previous slide
  114. if (!this.selectedChildWidget.isFirstChild) {
  115. this._gotoSlide(this._slideIndex - 1);
  116. } else {
  117. this.selectedChildWidget._reset();
  118. }
  119. if (evt) {
  120. evt.stopPropagation();
  121. }
  122. },
  123. getHash : function(id) {
  124. // summary: get the current hash to set in localtion
  125. return this.id + "_SlideNo_" + id;
  126. },
  127. _hideNav : function(evt) {
  128. // summary: hides navigation
  129. if (this._navAnim) {
  130. this._navAnim.stop();
  131. }
  132. this._navAnim = dojo.animateProperty({
  133. node : this.showNav,
  134. duration : this.navDuration,
  135. properties : {
  136. opacity : {
  137. end : this._navOpacMin
  138. }
  139. }
  140. }).play();
  141. },
  142. _showNav : function(evt) {
  143. // summary: shows navigation
  144. if (this._navAnim) {
  145. this._navAnim.stop();
  146. }
  147. this._navAnim = dojo.animateProperty({
  148. node : this.showNav,
  149. duration : this.navDuration,
  150. properties : {
  151. opacity : {
  152. end : this._navOpacMax
  153. }
  154. }
  155. }).play();
  156. },
  157. _handleNav : function(evt) {
  158. // summary: does nothing? _that_ seems useful.
  159. evt.stopPropagation();
  160. },
  161. _updateSlides : function() {
  162. // summary:
  163. // populate navigation select list with refs to slides call
  164. // this
  165. // if you add a node to your presentation dynamically.
  166. this._slides = this.getChildren();
  167. if (this.useNav) {
  168. // populate the select box with top-level slides
  169. var i = 0;
  170. dojo.forEach(this._slides, dojo.hitch(this, function(
  171. slide) {
  172. i++;
  173. var tmp = this._option
  174. .cloneNode(true);
  175. tmp.text = slide.title + " (" + i
  176. + ") ";
  177. this._option.parentNode
  178. .insertBefore(tmp,
  179. this._option);
  180. }));
  181. if (this._option.parentNode) {
  182. this._option.parentNode.removeChild(this._option);
  183. }
  184. // dojo._destroyElement(this._option);
  185. }
  186. },
  187. _onEvent : function(/* Event */evt) {
  188. // summary:
  189. // main presentation function, determines next 'best action'
  190. // for a
  191. // specified event.
  192. var _node = evt.target;
  193. var _type = evt.type;
  194. if (_type == "click" || _type == "change") {
  195. if (_node.index && _node.parentNode == this.select) {
  196. this._gotoSlide(_node.index);
  197. } else if (_node == this.select) {
  198. this._gotoSlide(_node.selectedIndex);
  199. } else {
  200. if (this.noClick
  201. || this.selectedChildWidget.noClick
  202. || this._isUnclickable(evt))
  203. return;
  204. this.selectedChildWidget._nextAction(evt);
  205. }
  206. } else if (_type == "keydown" || _type == "keypress") {
  207. // FIXME: safari doesn't report keydown/keypress?
  208. var key = (evt.charCode == dojo.keys.SPACE
  209. ? dojo.keys.SPACE
  210. : evt.keyCode);
  211. switch (key) {
  212. case dojo.keys.DELETE :
  213. case dojo.keys.BACKSPACE :
  214. case dojo.keys.LEFT_ARROW :
  215. case dojo.keys.UP_ARROW :
  216. case dojo.keys.PAGE_UP :
  217. case 80 : // key 'p'
  218. this.previousSlide(evt);
  219. break;
  220. case dojo.keys.ENTER :
  221. case dojo.keys.SPACE :
  222. case dojo.keys.RIGHT_ARROW :
  223. case dojo.keys.DOWN_ARROW :
  224. case dojo.keys.PAGE_DOWN :
  225. case 78 : // key 'n'
  226. this.selectedChildWidget._nextAction(evt);
  227. break;
  228. case dojo.keys.HOME :
  229. this._gotoSlide(0);
  230. }
  231. }
  232. this._resizeWindow();
  233. evt.stopPropagation();
  234. },
  235. _gotoSlide : function(/* Integer */slideIndex) {
  236. // summary: goes to slide
  237. this.selectChild(this._slides[slideIndex]);
  238. this.selectedChildWidget._reset();
  239. this._slideIndex = slideIndex;
  240. if (this.useNav) {
  241. this.select.selectedIndex = slideIndex;
  242. }
  243. if (this.setHash) {
  244. this._setHash();
  245. }
  246. this.onMove(this._slideIndex + 1);
  247. },
  248. _isUnclickable : function(/* Event */evt) {
  249. // summary: returns true||false base of a nodes
  250. // click-ability
  251. var nodeName = evt.target.nodeName.toLowerCase();
  252. // TODO: check for noClick='true' in target attrs & return
  253. // true
  254. // TODO: check for relayClick='true' in target attrs &
  255. // return false
  256. switch (nodeName) {
  257. case 'a' :
  258. case 'input' :
  259. case 'textarea' :
  260. return true;
  261. break;
  262. }
  263. return false;
  264. },
  265. _readHash : function() {
  266. var th = window.location.hash;
  267. if (th.length && this.setHash) {
  268. var parts = ("" + window.location).split(this
  269. .getHash(''));
  270. if (parts.length > 1) {
  271. this._gotoSlide(parseInt(parts[1]) - 1);
  272. }
  273. }
  274. },
  275. _setHash : function() {
  276. // summary: sets url #mark to direct slide access
  277. if (this.setHash) {
  278. var slideNo = this._slideIndex + 1;
  279. window.location.href = "#" + this.getHash(slideNo);
  280. }
  281. },
  282. _resizeWindow : function(/* Event */evt) {
  283. // summary: resize this and children to fix this
  284. // window/container
  285. // only if this.fullScreen?
  286. dojo.body().style.height = "auto";
  287. var wh = dijit.getViewport();
  288. var h = Math.max(document.documentElement.scrollHeight
  289. || dojo.body().scrollHeight, wh.h);
  290. var w = wh.w;
  291. this.selectedChildWidget.domNode.style.height = h + 'px';
  292. this.selectedChildWidget.domNode.style.width = w + 'px';
  293. },
  294. _transition : function(newWidget, oldWidget) {
  295. // summary: over-ride stackcontainers _transition method
  296. // but atm, i find it to be ugly with not way to call
  297. // _showChild() without over-riding it too. hopefull
  298. // basic toggles in superclass._transition will be available
  299. // in dijit, and this won't be necessary.
  300. var anims = [];
  301. if (oldWidget) {
  302. /*
  303. * anims.push(dojo.fadeOut({ node: oldWidget.domNode,
  304. * duration:250, onEnd: dojo.hitch(this,function(){
  305. * this._hideChild(oldWidget); }) }));
  306. */
  307. this._hideChild(oldWidget);
  308. }
  309. if (newWidget) {
  310. /*
  311. * anims.push(dojo.fadeIn({ node:newWidget.domNode,
  312. * start:0, end:1, duration:300, onEnd:
  313. * dojo.hitch(this,function(){
  314. * this._showChild(newWidget); newWidget._reset(); }) }) );
  315. */
  316. this._showChild(newWidget);
  317. newWidget._reset();
  318. }
  319. // dojo.fx.combine(anims).play();
  320. }
  321. });
  322. dojo.declare("dojox.presentation.Slide", [dijit.layout.ContentPane,
  323. dijit._Contained, dijit._Container, dijit._Templated], {
  324. // summary:
  325. // a Comonent of a dojox.presentation, and container for each
  326. // 'Slide'
  327. // made up of direct HTML (no part/action relationship), and
  328. // dojox.presentation.Part(s),
  329. // and their attached Actions.
  330. // templatPath: String
  331. // make a ContentPane templated, and style the 'titleNode'
  332. 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",
  333. // title: String
  334. // string to insert into titleNode, title of Slide
  335. title : "",
  336. // inherited from ContentPane FIXME: don't seem to work ATM?
  337. refreshOnShow : true,
  338. preLoad : false,
  339. doLayout : true,
  340. parseContent : true,
  341. // noClick: Boolean
  342. // true on slide tag prevents clicking, false allows
  343. // (can also be set on base presentation for global control)
  344. noClick : false,
  345. // private holders:
  346. _parts : [],
  347. _actions : [],
  348. _actionIndex : 0,
  349. _runningDelay : false,
  350. startup : function() {
  351. // summary: setup this slide with actions and components
  352. // (Parts)
  353. this.slideTitleText.innerHTML = this.title;
  354. var children = this.getChildren();
  355. this._actions = [];
  356. dojo.forEach(children, function(child) {
  357. var tmpClass = child.declaredClass
  358. .toLowerCase();
  359. switch (tmpClass) {
  360. case "dojox.presentation.part" :
  361. this._parts.push(child);
  362. break;
  363. case "dojox.presentation.action" :
  364. this._actions.push(child);
  365. break;
  366. }
  367. }, this);
  368. },
  369. _nextAction : function(evt) {
  370. // summary: gotoAndPlay current cached action
  371. var tmpAction = this._actions[this._actionIndex] || 0;
  372. if (tmpAction) {
  373. // is this action a delayed action? [auto? thoughts?]
  374. if (tmpAction.on == "delay") {
  375. this._runningDelay = setTimeout(dojo.hitch(
  376. tmpAction, "_runAction"),
  377. tmpAction.delay);
  378. console.debug('started delay action',
  379. this._runningDelay);
  380. } else {
  381. tmpAction._runAction();
  382. }
  383. // FIXME: it gets hairy here. maybe runAction should
  384. // call _actionIndex++ onEnd? if a delayed action is
  385. // running, do
  386. // we want to prevent action++?
  387. var tmpNext = this._getNextAction();
  388. this._actionIndex++;
  389. if (tmpNext.on == "delay") {
  390. // FIXME: yeah it looks like _runAction() onend
  391. // should report
  392. // _actionIndex++
  393. console.debug('started delay action',
  394. this._runningDelay);
  395. setTimeout(dojo.hitch(tmpNext, "_runAction"),
  396. tmpNext.delay);
  397. }
  398. } else {
  399. // no more actions in this slide
  400. this.getParent().nextSlide(evt);
  401. }
  402. },
  403. _getNextAction : function() {
  404. // summary: returns the _next action in this sequence
  405. return this._actions[this._actionIndex + 1] || 0;
  406. },
  407. _reset : function() {
  408. // summary: set action chain back to 0 and re-init each Part
  409. this._actionIndex = [0];
  410. dojo.forEach(this._parts, function(part) {
  411. part._reset();
  412. }, this);
  413. }
  414. });
  415. dojo.declare("dojox.presentation.Part", [dijit._Widget, dijit._Contained],
  416. {
  417. // summary:
  418. // a node in a presentation.Slide that inherits control from a
  419. // dojox.presentation.Action
  420. // can be any element type, and requires styling before parsing
  421. //
  422. // as: String
  423. // like an ID, attach to Action via (part) as="" / (action)
  424. // forSlide="" tags
  425. // this should be unique identifier?
  426. as : null,
  427. // startVisible: boolean
  428. // true to leave in page on slide startup/reset
  429. // false to hide on slide startup/reset
  430. startVisible : false,
  431. // isShowing: Boolean,
  432. // private holder for _current_ state of Part
  433. _isShowing : false,
  434. postCreate : function() {
  435. // summary: override and init() this component
  436. this._reset();
  437. },
  438. _reset : function() {
  439. // summary: set part back to initial calculate state
  440. // these _seem_ backwards, but quickToggle flips it
  441. this._isShowing = !this.startVisible;
  442. this._quickToggle();
  443. },
  444. _quickToggle : function() {
  445. // summary: ugly [unworking] fix to test setting state of
  446. // component
  447. // before/after an animation. display:none prevents fadeIns?
  448. if (this._isShowing) {
  449. dojo.style(this.domNode, 'display', 'none');
  450. dojo.style(this.domNode, 'visibility', 'hidden');
  451. dojo.style(this.domNode, 'opacity', 0);
  452. } else {
  453. dojo.style(this.domNode, 'display', '');
  454. dojo.style(this.domNode, 'visibility', 'visible');
  455. dojo.style(this.domNode, 'opacity', 1);
  456. }
  457. this._isShowing = !this._isShowing;
  458. }
  459. });
  460. dojo.declare("dojox.presentation.Action",
  461. [dijit._Widget, dijit._Contained], {
  462. // summary:
  463. // a widget to attach to a dojox.presentation.Part to control
  464. // it's properties based on an inherited chain of events ...
  465. //
  466. //
  467. // on: String
  468. // FIXME: only 'click' supported ATM. plans include on="delay",
  469. // on="end" of="", and on="auto". those should make semantic
  470. // sense
  471. // to you.
  472. on : 'click',
  473. // forSlide: String
  474. // attach this action to a dojox.presentation.Part with a
  475. // matching 'as' attribute
  476. forSlide : null,
  477. // toggle: String
  478. // will toggle attached [matching] node(s) via forSlide/as
  479. // relationship(s)
  480. toggle : 'fade',
  481. // delay: Integer
  482. //
  483. delay : 0,
  484. // duration: Integer
  485. // default time in MS to run this action effect on it's
  486. // 'forSlide' node
  487. duration : 1000,
  488. // private holders:
  489. _attached : [],
  490. _nullAnim : false,
  491. _runAction : function() {
  492. // summary: runs this action on attached node(s)
  493. var anims = [];
  494. // executes the action for each attached 'Part'
  495. dojo.forEach(this._attached, function(node) {
  496. // FIXME: this is ugly, and where is toggle
  497. // class? :(
  498. var dir = (node._isShowing) ? "Out" : "In";
  499. // node._isShowing =! node._isShowing;
  500. node._quickToggle(); // (?) this is annoying
  501. // var _anim = dojox.fx[ this.toggle ?
  502. // this.toggle+dir : "fade"+dir]({
  503. var _anim = dojo.fadeIn({
  504. node : node.domNode,
  505. duration : this.duration
  506. // beforeBegin:
  507. // dojo.hitch(node,"_quickToggle")
  508. });
  509. anims.push(_anim);
  510. }, this);
  511. var _anim = dojo.fx.combine(anims);
  512. if (_anim) {
  513. _anim.play();
  514. }
  515. },
  516. _getSiblingsByType : function(/* String */declaredClass) {
  517. // summary: quick replacement for
  518. // getChildrenByType("class"), but in
  519. // a child here ... so it's getSiblings. courtesy bill in
  520. // #dojo
  521. // could be moved into parent, and just call
  522. // this.getChildren(),
  523. // which makes more sense.
  524. var siblings = dojo.filter(this.getParent().getChildren(),
  525. function(widget) {
  526. return widget.declaredClass == declaredClass;
  527. });
  528. return siblings; // dijit._Widget
  529. },
  530. postCreate : function() {
  531. // summary: run this once, should this be startup:
  532. // function()?
  533. // prevent actions from being visible, _always_
  534. dojo.style(this.domNode, "display", "none");
  535. var parents = this
  536. ._getSiblingsByType('dojox.presentation.Part');
  537. // create a list of "parts" we are attached to via
  538. // forSlide/as
  539. this._attached = [];
  540. dojo.forEach(parents, function(parentPart) {
  541. if (this.forSlide == parentPart.as) {
  542. this._attached.push(parentPart);
  543. }
  544. }, this);
  545. }
  546. });
  547. }