/[suikacvs]/test/suikawebwww/www/js/jste/tutorial.js
Suika

Contents of /test/suikawebwww/www/js/jste/tutorial.js

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.7 - (hide annotations) (download) (as text)
Sun Jan 25 13:13:01 2009 UTC (17 years, 5 months ago) by wakaba
Branch: MAIN
Changes since 1.6: +32 -7 lines
File MIME type: application/javascript
Don't show buttons if it the command has no effect

1 wakaba 1.1
2     if (typeof (JSTE) === "undefined") var JSTE = {};
3    
4     JSTE.WATNS = 'http://suika.fam.cx/ns/wat';
5     JSTE.SpaceChars = /[\x09\x0A\x0C\x0D\x20]+/;
6    
7     JSTE.Class = function (constructor, prototype) {
8     return JSTE.Subclass (constructor, JSTE.EventTarget, prototype);
9     }; // Class
10    
11     JSTE.Class.addClassMethods = function (classObject, methods) {
12     new JSTE.Hash (methods).forEach (function (n, v) {
13     if (!classObject[n]) {
14     classObject[n] = v;
15     }
16     });
17     }; // addClassMethods
18    
19     JSTE.Subclass = function (constructor, superclass, prototype) {
20     constructor.prototype = new superclass;
21     for (var n in prototype) {
22     constructor.prototype[n] = prototype[n];
23     }
24     constructor.prototype.constructor = constructor;
25     constructor.prototype._super = superclass;
26     return constructor;
27     }; // Subclass
28    
29     JSTE.EventTarget = new JSTE.Subclass (function () {
30    
31     }, function () {}, {
32     addEventListener: function (eventType, handler, useCapture) {
33     if (useCapture) return;
34     if (!this.eventListeners) this.eventListeners = {};
35     if (!this.eventListeners[eventType]) {
36     this.eventListeners[eventType] = new JSTE.List;
37     }
38     this.eventListeners[eventType].push (handler);
39     }, // addEventListener
40     removeEventListener: function (eventType, handler, useCapture) {
41     if (useCapture) return;
42     if (!this.eventListeners) return;
43     if (!this.eventListeners[eventType]) return;
44     this.eventListeners[eventType].remove (handler);
45     }, // removeEventListener
46     dispatchEvent: function (e) {
47     if (!this.eventListeners) return;
48     var handlers = this.eventListeners[e.type];
49     if (!handlers) return;
50     e.currentTarget = this;
51     e.target = this;
52     var preventDefault;
53     handlers.forEach (function (handler) {
54     if (handler.apply (this, [e])) {
55     preventDefault = true;
56     }
57     });
58     return preventDefault || e.isDefaultPrevented ();
59     } // dispatchEvent
60     }); // EventTarget
61    
62     JSTE.Event = new JSTE.Class (function (eventType, canBubble, cancelable) {
63     this.type = eventType;
64     this.bubbles = canBubble;
65     this.cancelable = cancelable;
66     }, {
67     preventDefault: function () {
68     this.defaultPrevented = true;
69     }, // preventDefault
70     isDefaultPrevented: function () {
71     return this.defaultPrevented;
72     } // isDefaultPrevented
73     });
74    
75     JSTE.Observer = new JSTE.Class (function (eventType, target, onevent) {
76     this.eventType = eventType;
77     if (target.addEventListener) {
78     this.target = target;
79     this.code = onevent;
80     target.addEventListener (eventType, this.code, false);
81     } else if (target.attachEvent) {
82     this.code = function () {
83     onevent (event);
84     };
85     this.target = target;
86     target.attachEvent ("on" + eventType, this.code);
87     }
88     }, {
89     stop: function () {
90     if (this.target.removeEventListener) {
91     this.target.removeEventListener (this.eventType, this.code, false);
92     } else if (this.detachEvent) {
93     this.target.detachEvent ("on" + this.eventType, this.code);
94     }
95     } // stop
96     }); // Observer
97    
98 wakaba 1.5 new JSTE.Observer ('load', window, function () {
99     JSTE.windowLoaded = true;
100     });
101    
102 wakaba 1.1 JSTE.List = new JSTE.Class (function (arrayLike) {
103     this.list = arrayLike || [];
104     }, {
105     getLast: function () {
106     if (this.list.length) {
107     return this.list[this.list.length - 1];
108     } else {
109     return null;
110     }
111     }, // getLast
112    
113     forEach: function (code) {
114     var length = this.list.length;
115     for (var i = 0; i < length; i++) {
116     var r = code (this.list[i]);
117     if (r && r.stop) return r.returnValue;
118     }
119     return null;
120     }, // forEach
121 wakaba 1.4
122     numberToInteger: function () {
123     var newList = [];
124     this.forEach (function (item) {
125     if (typeof item === "number") {
126     newList.push (Math.floor (item));
127     } else {
128     newList.push (item);
129     }
130     });
131     return new this.constructor (newList);
132     }, // numberToInteger
133 wakaba 1.1
134     clone: function () {
135     var newList = [];
136     this.forEach (function (item) {
137     newList.push (item);
138     });
139     return new this.constructor (newList);
140     }, // clone
141    
142     grep: function (code) {
143     var newList = [];
144     this.forEach (function (item) {
145     if (code (item)) {
146     newList.push (item);
147     }
148     });
149     return new this.constructor (newList);
150     }, // grep
151     onlyNonNull: function () {
152     return this.grep (function (item) {
153     return item != null; /* Intentionally "!=" */
154     });
155     }, // onlyNonNull
156    
157     getFirstMatch: function (code) {
158     return this.forEach (function (item) {
159     if (code (item)) {
160     return new JSTE.List.Return (item);
161     }
162     });
163     }, // getFirstMatch
164    
165     switchByElementType: function () {
166     var cases = new JSTE.List (arguments);
167     this.forEach (function (n) {
168     cases.forEach (function (c) {
169     if (c.namespaceURI == n.namespaceURI) {
170     return new JSTE.List.Return (c.execute (n));
171     }
172     });
173     });
174     }, // switchByElementType
175    
176     push: function (item) {
177     this.list.push (item);
178     }, // push
179     pushCloneOfLast: function () {
180     this.list.push (this.getLast ().clone ());
181     }, // pushCloneOfLast
182     append: function (list) {
183     var self = this;
184     list.forEach (function (n) {
185     self.list.push (n);
186     });
187     }, // append
188    
189     pop: function () {
190     return this.list.pop ();
191     }, // pop
192     remove: function (removedItem) {
193     var length = this.list.length;
194     for (var i = length - 1; i >= 0; --i) {
195     var item = this.list[i];
196     if (item == removedItem) { // Intentionally "=="
197     this.list.splice (i, 1);
198     }
199     }
200     }, // remove
201     clear: function () {
202     this.list.splice (0, this.list.length);
203     } // clear
204    
205     }); // List
206    
207     JSTE.List.Return = new JSTE.Class (function (rv) {
208     this.stop = true;
209     this.returnValue = rv;
210     }, {
211    
212     }); // Return
213    
214     JSTE.List.SpaceSeparated = function (v) {
215     return new JSTE.List ((v || '').split (JSTE.SpaceChars)).grep (function (v) {
216     return v.length;
217     });
218     }; // SpaceSeparated
219    
220     JSTE.List.SwitchByLocalName = new JSTE.Class (function (ns, cases, ow) {
221     this.namespaceURI = ns;
222     this.cases = cases;
223     this.otherwise = ow || function (n) { };
224     }, {
225     execute: function (n) {
226     for (var ln in this.cases) {
227     if (JSTE.Element.matchLocalName (n, ln)) {
228     return this.cases[ln] (n);
229     }
230     }
231     return this.otherwise (n);
232     }
233     });
234    
235     JSTE.Hash = new JSTE.Class (function (hash) {
236     this.hash = hash || {};
237     }, {
238     forEach: function (code) {
239     for (var n in this.hash) {
240     var r = code (n, this.hash[n]);
241     if (r && r.stop) break;
242     }
243     }, // forEach
244     clone: function (code) {
245     var newHash = {};
246     this.forEach (function (n, v) {
247     newHash[n] = v;
248     });
249     return new this.constructor (newHash);
250     }, // clone
251    
252     getNamedItem: function (n) {
253     return this.hash[n];
254     }, // getNamedItem
255     setNamedItem: function (n, v) {
256     return this.hash[n] = v;
257     } // setNamedItem
258     });
259    
260     if (!JSTE.Node) JSTE.Node = {};
261    
262     JSTE.Class.addClassMethods (JSTE.Node, {
263     querySelector: function (node, selectors) {
264     if (node.querySelector) {
265     var el;
266     try {
267     el = node.querySelector (selectors);
268     } catch (e) {
269     el = null;
270     }
271     return el;
272     } else if (window.uu && uu.css) {
273     if (selectors != "") {
274     /* NOTE: uu.css return all elements for "" or ",xxx" */
275     return uu.css (selectors, node)[0];
276     } else {
277     return null;
278     }
279     } else if (window.Ten && Ten.DOM && Ten.DOM.getElementsBySelector) {
280     return Ten.DOM.getElementsBySelector (selectors)[0];
281     } else {
282     return null;
283     }
284     }, // querySelector
285     querySelectorAll: function (node, selectors) {
286     if (node.querySelectorAll) {
287     var nl;
288     try {
289     nl = node.querySelectorAll (selectors);
290     } catch (e) {
291     nl = null;
292     }
293     return new JSTE.List (nl);
294     } else if (window.uu && uu.css) {
295     if (selectors != "") {
296     /* NOTE: uu.css return all elements for "" or ",xxx" */
297     return new JSTE.List (uu.css (selectors, node));
298     } else {
299     return new JSTE.List;
300     }
301     } else if (window.Ten && Ten.DOM && Ten.DOM.getElementsBySelector) {
302     return new JSTE.List (Ten.DOM.getElementsBySelector (selectors));
303     } else {
304     return new JSTE.List;
305     }
306     } // querySelectorAll
307     });
308    
309     if (!JSTE.Element) JSTE.Element = {};
310    
311     JSTE.Class.addClassMethods (JSTE.Element, {
312     getLocalName: function (el) {
313     var localName = el.localName;
314     if (!localName) {
315     localName = el.nodeName;
316     if (el.prefix) {
317     localName = localName.substring (el.prefix.length + 1);
318     }
319     }
320     return localName;
321     }, // getLocalName
322    
323     match: function (el, ns, ln) {
324     if (el.nodeType !== 1) return false;
325     if (el.namespaceURI !== ns) return false;
326     return JSTE.Element.matchLocalName (el, ln);
327     }, // match
328     matchLocalName: function (el, ln) {
329     var localName = JSTE.Element.getLocalName (el);
330     if (ln instanceof RegExp) {
331     if (!localName.match (ln)) return false;
332     } else {
333     if (localName !== ln) return false;
334     }
335     return true;
336     }, // matchLocalName
337    
338     getChildElement: function (el, ns, ln) {
339     return new JSTE.List (el.childNodes).getFirstMatch (function (item) {
340     return JSTE.Element.match (item, ns, ln);
341     });
342     }, // getChildElement
343     getChildElements: function (el, ns, ln) {
344     return new JSTE.List (el.childNodes).grep (function (item) {
345     return JSTE.Element.match (item, ns, ln);
346     });
347     }, // getChildElements
348    
349     appendText: function (el, s) {
350     return el.appendChild (el.ownerDocument.createTextNode (s));
351     }, // appendText
352    
353     createTemplate: function (doc, node) {
354     var df = doc.createDocumentFragment ();
355     new JSTE.List (node.childNodes).forEach (function (n) {
356     if (n.nodeType == 1) {
357     var c = doc.createElement (JSTE.Element.getLocalName (n));
358     new JSTE.List (c.attributes).forEach (function (n) {
359     c.setAttribute (n.name, n.value);
360     });
361     c.appendChild (JSTE.Element.createTemplate (doc, n));
362     df.appendChild (c);
363     } else if (n.nodeType == 3 || n.nodeType == 4) {
364     df.appendChild (doc.createTextNode (n.data));
365     }
366     });
367     return df;
368     }, // createTemplate
369    
370     hasAttribute: function (el, localName) {
371     if (el.hasAttribute) {
372     return el.hasAttribute (localName);
373     } else {
374     return el.getAttribute (localName) != null;
375     }
376     }, // hasAttribute
377    
378     getClassNames: function (el) {
379     return new JSTE.List (el.className.split (JSTE.SpaceChars));
380     }, // getClassNames
381     addClassName: function (el, newClassName) {
382     el.className = el.className + ' ' + newClassName;
383     }, // deleteClassName
384     deleteClassName: function (el, oldClassName) {
385     var classNames = el.className.split (JSTE.SpaceChars);
386     var newClasses = [];
387     for (var n in classNames) {
388     if (classNames[n] != oldClassName) {
389     newClasses.push (classNames[n]);
390     }
391     }
392     el.className = newClasses.join (' ');
393     }, // deleteClassName
394     replaceClassName: function (el, oldClassName, newClassName) {
395     var classNames = el.className.split (JSTE.SpaceChars);
396     var newClasses = [newClassName];
397     for (var n in classNames) {
398     if (classNames[n] != oldClassName) {
399     newClasses.push (classNames[n]);
400     }
401     }
402     el.className = newClasses.join (' ');
403     }, // replaceClassName
404    
405     getIds: function (el) {
406     return new JSTE.List (el.id != "" ? [el.id] : []);
407 wakaba 1.5 }, // getIds
408    
409     /*
410     NR.js <http://suika.fam.cx/www/css/noderect/NodeRect.js> must be loaded
411     before the invocation.
412     */
413     scroll: function (elements) {
414     if (!JSTE.windowLoaded) {
415     new JSTE.Observer ('load', window, function () {
416     JSTE.Element.scroll (elements);
417     });
418     return;
419     }
420    
421     var top = Infinity;
422     var left = Infinity;
423     var topEl;
424     var leftEl;
425     elements.forEach (function (el) {
426     var rect = NR.Element.getRects (el, window).borderBox;
427     if (rect.top < top) {
428     top = rect.top;
429     topEl = el;
430     }
431     if (rect.left < left) {
432     left = rect.left;
433     leftEl = el;
434     }
435     });
436    
437     if (!leftEl && !topEl) {
438     return;
439     }
440    
441     var doc = (leftEl || topEl).ownerDocument;
442 wakaba 1.1
443 wakaba 1.5 var rect = NR.View.getViewportRects (window, doc).contentBox;
444     if (rect.top <= top && top <= rect.bottom) {
445     top = rect.top;
446     }
447     if (rect.left <= left && left <= rect.right) {
448     left = rect.left;
449     }
450    
451     /*
452     Set scroll* of both <html> and <body> elements, to support all of
453     four browsers and two (or three) rendering modes. This might result
454     in confusing the user if both <html> and <body> elements have their
455     'overflow' properties specified to 'scroll'.
456    
457     Note that this code does not do a good job if the |el| is within an
458     |overflow: scroll| element.
459     */
460     doc.body.scrollTop = top;
461     doc.body.scrollLeft = left;
462     doc.documentElement.scrollTop = top;
463     doc.documentElement.scrollLeft = left;
464     } // scroll
465    
466 wakaba 1.1 }); // JSTE.Element
467    
468     /* Events: load, close, shown, hidden */
469     JSTE.Message = new JSTE.Class (function (doc, template, commandTarget) {
470     if (!doc) return;
471     this._targetDocument = doc;
472     this._template = template || doc.createDocumentFragment ();
473     this._commandTarget = commandTarget;
474     this.hidden = true;
475     this.select = "";
476    
477     var e = new JSTE.Event ('load');
478     this.dispatchEvent (e);
479     }, {
480     render: function () {
481     var self = this;
482     var doc = this._targetDocument;
483    
484     var msgContainer = doc.createElement ('section');
485     msgContainer.appendChild (this._template);
486    
487     var buttonContainer = doc.createElement ('menu');
488    
489 wakaba 1.7 if (this._commandTarget.canBack ()) {
490     var backButton = new JSTE.Message.Button
491     ("Back", this._commandTarget, "back");
492     buttonContainer.appendChild (backButton.element);
493     }
494    
495     if (this._commandTarget.canNext ()) {
496     var nextButton = new JSTE.Message.Button
497     ("Next", this._commandTarget, "next");
498     buttonContainer.appendChild (nextButton.element);
499     }
500 wakaba 1.1
501     this._outermostElement = this._render (msgContainer, buttonContainer);
502    
503     this.show ();
504     }, // render
505     _render: function (msgContainer, buttonContainer) {
506     var doc = this._targetDocument;
507    
508     var container = doc.createElement ('article');
509     var style = doc.createElement ('style');
510     style.innerHTML = this.select + ' { outline: red 2px solid }';
511     container.appendChild (style);
512    
513     container.appendChild (msgContainer);
514     container.appendChild (buttonContainer);
515     doc.documentElement.appendChild (container);
516    
517     return container;
518     }, // _render
519     remove: function () {
520     this.hide ();
521    
522     this._remove ();
523    
524     if (this._outermostElement && this._outermostElement.parentNode) {
525     this._outermostElement.parentNode.removeChild (this._outermostElement);
526     }
527    
528     var e = new JSTE.Event ("close");
529     this.dispatchEvent (e);
530     }, // remove
531     _remove: function () {
532    
533     }, // remove
534    
535     show: function () {
536     if (!this.hidden) return;
537     this.hidden = false;
538     if (this._outermostElement) {
539     JSTE.Element.replaceClassName
540     (this._outermostElement, "jste-hidden", "jste-shown");
541     }
542    
543     var e = new JSTE.Event ("shown");
544     this.dispatchEvent (e);
545     }, // show
546     hide: function () {
547     if (this.hidden) return;
548     this.hidden = true;
549     if (this._outermostElement) {
550     JSTE.Element.replaceClassName
551     (this._outermostElement, "jste-shown", "jste-hidden");
552     }
553    
554     var e = new JSTE.Event ("hidden");
555     this.dispatchEvent (e);
556     }, // hide
557    
558     setTimeout: function () {
559     /* TODO: ... */
560    
561     }
562    
563     }); // Message
564    
565 wakaba 1.7 /* TODO: button label text should refer message catalog */
566    
567 wakaba 1.1 JSTE.Message.Button =
568     new JSTE.Class (function (labelText, commandTarget, commandName, commandArgs) {
569 wakaba 1.6 this._labelText = labelText != null ? labelText : "";
570    
571 wakaba 1.1 if (commandTarget && commandTarget instanceof Function) {
572     this._command = commandTarget;
573 wakaba 1.6 this._classNames = new JSTE.List;
574 wakaba 1.1 } else if (commandTarget) {
575     this._command = function () {
576     return commandTarget.executeCommand.apply
577     (commandTarget, [commandName, commandArgs]);
578     };
579 wakaba 1.6 this._classNames = new JSTE.List (['jste-command-' + commandName]);
580 wakaba 1.1 } else {
581     this._command = function () { };
582 wakaba 1.6 this._classNames = new JSTE.List;
583 wakaba 1.1 }
584    
585 wakaba 1.6 this._createElement ();
586 wakaba 1.1 }, {
587 wakaba 1.6 _createElement: function () {
588     try {
589     this.element = document.createElement ('button');
590     this.element.setAttribute ('type', 'button');
591     } catch (e) {
592     this.element = document.createElement ('<button type=button>');
593     }
594     JSTE.Element.appendText (this.element, this._labelText);
595     this.element.className = this._classNames.list.join (' ');
596 wakaba 1.1
597 wakaba 1.6 var self = this;
598     new JSTE.Observer ("click", this.element, function (e) {
599     self._command (e);
600     });
601     } // _createElement
602 wakaba 1.1 }); // Button
603    
604     JSTE.Course = new JSTE.Class (function (doc) {
605     this._targetDocument = doc;
606    
607     this._entryPointsByURL = {};
608     this._entryPointsById = {};
609     this._entryPointsByClassName = {};
610    
611     this._stepsState = new JSTE.List ([new JSTE.Hash]);
612     this._steps = new JSTE.Hash;
613    
614     var nullState = new JSTE.Step;
615     nullState.uid = "";
616     this._steps.setNamedItem (nullState.uid, nullState);
617     this._initialStepUid = nullState.uid;
618     }, {
619     _processStepsContent: function (el) {
620     var self = this;
621     new JSTE.List (el.childNodes).switchByElementType (
622     new JSTE.List.SwitchByLocalName (JSTE.WATNS, {
623     steps: function (n) { self._processStepsElement (n) },
624     step: function (n) { self._processStepElement (n) },
625     jump: function (n) { self._processJumpElement (n) },
626     entry: function (n) { self._processEntryElement (n) }
627     })
628     );
629     }, // _processStepsContent
630     _processStepsElement: function (e) {
631     this._stepsState.pushCloneOfLast ();
632     this._stepsState.getLast ().prevStep = null;
633     this._processStepsContent (e);
634     this._stepsState.pop ();
635     }, // _processStepsElement
636    
637     _processEntryElement: function (e) {
638     if (JSTE.Element.hasAttribute (e, 'url')) {
639     this.setEntryPointByURL
640     (e.getAttribute ('url'), e.getAttribute ('step'));
641     } else if (JSTE.Element.hasAttribute (e, 'root-id')) {
642     this.setEntryPointById
643     (e.getAttribute ('root-id'), e.getAttribute ('step'));
644     } else if (JSTE.Element.hasAttribute (e, 'root-class')) {
645     this.setEntryPointByClassName
646     (e.getAttribute ('root-class'), e.getAttribute ('step'));
647     }
648     }, // _processEntryElement
649     setEntryPointByURL: function (url, stepName) {
650     this._entryPointsByURL[url] = stepName || '';
651     }, // setEntryPointByURL
652     setEntryPointById: function (id, stepName) {
653     this._entryPointsById[id] = stepName || '';
654     }, // setEntryPointById
655     setEntryPointByClassName: function (className, stepName) {
656     this._entryPointsByClassName[className] = stepName || '';
657     }, // setEntryPointByClassName
658     findEntryPoint: function (doc) {
659     var self = this;
660     var td = this._targetDocument;
661     var stepName;
662    
663     var url = doc.URL;
664     if (url) {
665     stepName = self._entryPointsByURL[url];
666     if (stepName) return 'id-' + stepName;
667     }
668    
669     var docEl = td.documentElement;
670     if (docEl) {
671     var docElId = JSTE.Element.getIds (docEl).forEach (function (i) {
672     stepName = self._entryPointsById[i];
673     if (stepName) return new JSTE.List.Return (stepName);
674     });
675     if (stepName) return 'id-' + stepName;
676    
677     stepName = JSTE.Element.getClassNames (docEl).forEach (function (c) {
678     stepName = self._entryPointsByClassName[c];
679     if (stepName) return new JSTE.List.Return (stepName);
680     });
681     if (stepName) return 'id-' + stepName;
682     }
683    
684     var bodyEl = td.body;
685     if (bodyEl) {
686     var bodyElId = JSTE.Element.getIds (bodyEl).forEach (function (i) {
687     stepName = self._entryPointsById[i];
688     if (stepName) return new JSTE.List.Return (stepName);
689     });
690     if (stepName) return 'id-' + stepName;
691    
692     stepName = JSTE.Element.getClassNames (bodyEl).forEach (function (c) {
693     stepName = self._entryPointsByClassName[c];
694     if (stepName) return new JSTE.List.Return (stepName);
695     });
696     if (stepName) return 'id-' + stepName;
697     }
698    
699     return this._initialStepUid;
700     }, // findEntryPoint
701    
702     _processStepElement: function (e) {
703     var step = new JSTE.Step (e.getAttribute ('id'));
704     step.setPreviousStep (this._stepsState.getLast ().prevStep);
705     step.select = e.getAttribute ('select') || "";
706     step.nextEvents.append
707     (JSTE.List.SpaceSeparated (e.getAttribute ('next-event')));
708     var msgEl = JSTE.Element.getChildElement (e, JSTE.WATNS, 'message');
709     if (msgEl) {
710     var msg = JSTE.Element.createTemplate (this._targetDocument, msgEl);
711     step.setMessageTemplate (msg);
712     }
713     var nextEls = JSTE.Element.getChildElements (e, JSTE.WATNS, 'next-step');
714     if (nextEls.length) {
715     nextEls.forEach (function (nextEl) {
716     step.addNextStep
717     (nextEl.getAttribute ('if'), nextEl.getAttribute ('step'));
718     });
719     this._stepsState.getLast ().prevStep = null;
720     } else {
721     this._stepsState.getLast ().prevStep = step;
722     }
723     /* TODO: @save */
724    
725     this._steps.setNamedItem (step.uid, step);
726     if (!this._initialStepUid) {
727     this._initialStepUid = step.uid;
728     }
729     }, // _processStepElement
730    
731     _processJumpElement: function (e) {
732    
733     }, // _processJumpElement
734    
735     getStep: function (uid) {
736     return this._steps.getNamedItem (uid);
737     } // getStep
738     }); // Course
739    
740     JSTE.Course.createFromDocument = function (doc, targetDoc) {
741     var course = new JSTE.Course (targetDoc);
742     var docEl = doc.documentElement;
743     if (!docEl) return course;
744     if (!JSTE.Element.match (docEl, JSTE.WATNS, 'course')) return course;
745     course._processStepsContent (docEl);
746     return course;
747     }; // createFromDocument
748    
749     JSTE.Step = new JSTE.Class (function (id) {
750     if (id != null && id != '') {
751     this.uid = 'id-' + id;
752     } else {
753     this.uid = 'rand-' + Math.random ();
754     }
755     this._nextSteps = new JSTE.List;
756     this.nextEvents = new JSTE.List;
757     this.select = "";
758     }, {
759     setMessageTemplate: function (msg) {
760     this._messageTemplate = msg;
761     }, // setMessageTemplate
762     hasMessage: function () {
763     return this._messageTemplate ? true : false;
764     }, // hasMessage
765     createMessage: function (msg, doc, commandTarget) {
766     var msg;
767     if (this._messageTemplate) {
768     var clone = JSTE.Element.createTemplate (doc, this._messageTemplate);
769     msg = new msg (doc, clone, commandTarget);
770     } else {
771     msg = new msg (doc, null, commandTarget);
772     }
773     msg.select = this.select;
774     return msg;
775     }, // createMessage
776    
777     addNextStep: function (condition, stepId) {
778     this._nextSteps.push ([condition, stepId]);
779     }, // addNextStep
780     setPreviousStep: function (prevStep) {
781     if (!prevStep) return;
782     if (prevStep._defaultNextStepUid) return;
783     prevStep._defaultNextStepUid = this.uid;
784     }, // setPreviousStep
785    
786     getNextStepUid: function (doc) {
787     var m = this._nextSteps.getFirstMatch (function (item) {
788     var condition = item[0];
789     if (condition) {
790     return JSTE.Node.querySelector (doc, condition) != null;
791     } else {
792     return true;
793     }
794     });
795     if (m) {
796     return 'id-' + m[1];
797     } else if (this._defaultNextStepUid) {
798     return this._defaultNextStepUid;
799     } else {
800     return null;
801     }
802     } // getNextStepUid
803    
804     }); // Step
805    
806 wakaba 1.3 /* Events: load, error, cssomready */
807 wakaba 1.1 JSTE.Tutorial = new JSTE.Class (function (doc, course, args) {
808     this._course = course;
809     this._targetDocument = doc;
810     this._messageClass = JSTE.Message;
811     if (args) {
812     if (args.messageClass) this._messageClass = args.messageClass;
813     }
814    
815     this._currentMessages = new JSTE.List;
816     this._currentObservers = new JSTE.List;
817     this._prevStepUids = new JSTE.List;
818    
819     var stepUid = this._course.findEntryPoint (document);
820     this._currentStep = this._getStepOrError (stepUid);
821     if (this._currentStep) {
822     var e = new JSTE.Event ('load');
823     this.dispatchEvent (e);
824    
825 wakaba 1.3 var self = this;
826     new JSTE.Observer ('cssomready', this, function () {
827     self._renderCurrentStep ();
828     });
829     this._dispatchCSSOMReadyEvent ();
830 wakaba 1.1 return this;
831     } else {
832     return {};
833     }
834     }, {
835     _getStepOrError: function (stepUid) {
836     var step = this._course.getStep (stepUid);
837     if (step) {
838     return step;
839     } else {
840     var e = new JSTE.Event ('error');
841     e.errorMessage = 'Step not found';
842     e.errorArguments = [this._currentStepUid];
843     this.dispatchEvent (e);
844     return null;
845     }
846     }, // _getStepOrError
847    
848     _renderCurrentStep: function () {
849     var self = this;
850     var step = this._currentStep;
851    
852     /* Message */
853     var msg = step.createMessage
854     (this._messageClass, this._targetDocument, this);
855     msg.render ();
856     this._currentMessages.push (msg);
857    
858     /* Next-events */
859     var selectedNodes = JSTE.Node.querySelectorAll
860     (this._targetDocument, step.select);
861     var handler = function () {
862     self.executeCommand ("next");
863     };
864     selectedNodes.forEach (function (node) {
865     step.nextEvents.forEach (function (eventType) {
866     self._currentObservers.push
867     (new JSTE.Observer (eventType, node, handler));
868     });
869     });
870     }, // _renderCurrentStep
871     clearMessages: function () {
872     this._currentMessages.forEach (function (msg) {
873     msg.remove ();
874     });
875     this._currentMessages.clear ();
876    
877     this._currentObservers.forEach (function (ob) {
878     ob.stop ();
879     });
880     this._currentObservers.clear ();
881     }, // clearMessages
882    
883     executeCommand: function (commandName, commandArgs) {
884     if (this[commandName]) {
885     return this[commandName].apply (this, commandArgs || []);
886     } else {
887     var e = new JSTE.Event ('error');
888     e.errorMessage = 'Command not found';
889     e.errorArguments = [commandName];
890     return null;
891     }
892     }, // executeCommand
893 wakaba 1.7 canExecuteCommand: function (commandName, commandArgs) {
894     if (this[commandName]) {
895     var can = this['can' + commandName.substring (0, 1).toUpperCase ()
896     + commandName.substring (1)];
897     if (can) {
898     return can.apply (this, arguments);
899     } else {
900     return true;
901     }
902     } else {
903     return false;
904     }
905     }, // canExecuteCommand
906 wakaba 1.1
907     startTutorial: function () {
908     this.resetVisited ();
909    
910     }, // startTutorial
911     continueTutorial: function () {
912    
913     }, // continueTutorial
914    
915     saveVisited: function () {
916    
917     }, // saveVisited
918     resetVisited: function () {
919    
920     }, // resetVisited
921    
922     back: function () {
923     var prevStepUid = this._prevStepUids.pop ();
924     var prevStep = this._getStepOrError (prevStepUid);
925     if (prevStep) {
926     this.clearMessages ();
927     this._currentStep = prevStep;
928     this._renderCurrentStep ();
929     }
930     }, // back
931 wakaba 1.7 canBack: function () {
932     return this._prevStepUids.list.length > 0;
933     }, // canBack
934 wakaba 1.1 next: function () {
935     var nextStepUid = this._currentStep.getNextStepUid (this._targetDocument);
936     var nextStep = this._getStepOrError (nextStepUid);
937     if (nextStep) {
938     this._prevStepUids.push (this._currentStep.uid);
939     this.clearMessages ();
940     this._currentStep = nextStep;
941     this._renderCurrentStep ();
942     }
943 wakaba 1.3 }, // next
944 wakaba 1.7 canNext: function () {
945     return this._currentStep.getNextStepUid (this._targetDocument) != null;
946     }, // canNext
947 wakaba 1.3
948     // <http://twitter.com/waka/status/1129513097>
949     _dispatchCSSOMReadyEvent: function () {
950     var self = this;
951     var e = new JSTE.Event ('cssomready');
952     if (window.opera && document.readyState != 'complete') {
953     new JSTE.Observer ('readystatechange', document, function () {
954     if (document.readyState == 'complete') {
955     self.dispatchEvent (e);
956     }
957     });
958     } else {
959     this.dispatchEvent (e);
960     }
961     } // dispatchCSSOMReadyEvent
962    
963 wakaba 1.1 }); // Tutorial
964 wakaba 1.5
965     if (JSTE.onLoadFunctions) {
966     new JSTE.List (JSTE.onLoadFunctions).forEach (function (code) {
967     code ();
968     });
969     }
970    
971     if (JSTE.isDynamicallyLoaded) {
972     JSTE.windowLoaded = true;
973     }
974 wakaba 1.2
975     /* ***** BEGIN LICENSE BLOCK *****
976     * Copyright 2008-2009 Wakaba <[email protected]>. All rights reserved.
977     *
978     * This program is free software; you can redistribute it and/or
979     * modify it under the same terms as Perl itself.
980     *
981     * Alternatively, the contents of this file may be used
982     * under the following terms (the "MPL/GPL/LGPL"),
983     * in which case the provisions of the MPL/GPL/LGPL are applicable instead
984     * of those above. If you wish to allow use of your version of this file only
985     * under the terms of the MPL/GPL/LGPL, and not to allow others to
986     * use your version of this file under the terms of the Perl, indicate your
987     * decision by deleting the provisions above and replace them with the notice
988     * and other provisions required by the MPL/GPL/LGPL. If you do not delete
989     * the provisions above, a recipient may use your version of this file under
990     * the terms of any one of the Perl or the MPL/GPL/LGPL.
991     *
992     * "MPL/GPL/LGPL":
993     *
994     * Version: MPL 1.1/GPL 2.0/LGPL 2.1
995     *
996     * The contents of this file are subject to the Mozilla Public License Version
997     * 1.1 (the "License"); you may not use this file except in compliance with
998     * the License. You may obtain a copy of the License at
999     * <http://www.mozilla.org/MPL/>
1000     *
1001     * Software distributed under the License is distributed on an "AS IS" basis,
1002     * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
1003     * for the specific language governing rights and limitations under the
1004     * License.
1005     *
1006     * The Original Code is JSTE code.
1007     *
1008     * The Initial Developer of the Original Code is Wakaba.
1009     * Portions created by the Initial Developer are Copyright (C) 2008
1010     * the Initial Developer. All Rights Reserved.
1011     *
1012     * Contributor(s):
1013     * Wakaba <[email protected]>
1014     *
1015     * Alternatively, the contents of this file may be used under the terms of
1016     * either the GNU General Public License Version 2 or later (the "GPL"), or
1017     * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
1018     * in which case the provisions of the GPL or the LGPL are applicable instead
1019     * of those above. If you wish to allow use of your version of this file only
1020     * under the terms of either the GPL or the LGPL, and not to allow others to
1021     * use your version of this file under the terms of the MPL, indicate your
1022     * decision by deleting the provisions above and replace them with the notice
1023     * and other provisions required by the LGPL or the GPL. If you do not delete
1024     * the provisions above, a recipient may use your version of this file under
1025     * the terms of any one of the MPL, the GPL or the LGPL.
1026     *
1027     * ***** END LICENSE BLOCK ***** */

[email protected]
ViewVC Help
Powered by ViewVC 1.1.24