/[suikacvs]/webroot/www/js/jste/tutorial.js
Suika

Contents of /webroot/www/js/jste/tutorial.js

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.24 - (hide annotations) (download) (as text)
Mon Feb 2 04:29:37 2009 UTC (16 years, 6 months ago) by wakaba
Branch: MAIN
Changes since 1.23: +36 -9 lines
File MIME type: application/javascript
Added support for course/@name to split namespaces of courses; Added support for the url command; Added a new use case test data

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 wakaba 1.10 this.target = target;
78 wakaba 1.1 if (target.addEventListener) {
79     this.code = onevent;
80     } else if (target.attachEvent) {
81     this.code = function () {
82     onevent (event);
83     };
84 wakaba 1.10 } else {
85     this.code = onevent;
86 wakaba 1.1 }
87 wakaba 1.10 this.disabled = true;
88     this.start ();
89 wakaba 1.1 }, {
90 wakaba 1.10 start: function () {
91     if (!this.disabled) return;
92     if (this.target.addEventListener) {
93     this.target.addEventListener (this.eventType, this.code, false);
94     this.disabled = false;
95     } else if (this.target.attachEvent) {
96     this.target.attachEvent ("on" + this.eventType, this.code);
97     this.disabled = false;
98     }
99     }, // start
100 wakaba 1.1 stop: function () {
101 wakaba 1.10 if (this.disabled) return;
102 wakaba 1.1 if (this.target.removeEventListener) {
103     this.target.removeEventListener (this.eventType, this.code, false);
104 wakaba 1.10 this.disabled = true;
105 wakaba 1.11 } else if (this.target.detachEvent) {
106 wakaba 1.1 this.target.detachEvent ("on" + this.eventType, this.code);
107 wakaba 1.10 this.disabled = true;
108 wakaba 1.1 }
109     } // stop
110     }); // Observer
111    
112 wakaba 1.5 new JSTE.Observer ('load', window, function () {
113     JSTE.windowLoaded = true;
114     });
115    
116 wakaba 1.10
117     JSTE.Hash = new JSTE.Class (function (hash) {
118     this.hash = hash || {};
119     }, {
120     forEach: function (code) {
121     for (var n in this.hash) {
122     var r = code (n, this.hash[n]);
123     if (r && r.stop) break;
124     }
125     }, // forEach
126     clone: function (code) {
127     var newHash = {};
128     this.forEach (function (n, v) {
129     newHash[n] = v;
130     });
131     return new this.constructor (newHash);
132     }, // clone
133    
134     getNamedItem: function (n) {
135     return this.hash[n];
136     }, // getNamedItem
137     setNamedItem: function (n, v) {
138     return this.hash[n] = v;
139 wakaba 1.22 }, // setNamedItem
140    
141 wakaba 1.23 getNames: function () {
142     var r = new JSTE.List;
143     for (var n in this.hash) {
144     r.push (n);
145     }
146     return r;
147     }, // getNames
148    
149 wakaba 1.22 getByNames: function (names) {
150     var self = this;
151     return names.forEach (function (name) {
152     var value = self.getNamedItem (name);
153     if (value != null) {
154     return new JSTE.List.Return (value);
155     } else {
156     return null;
157     }
158     });
159     } // getByNames
160     }); // Hash
161 wakaba 1.10
162    
163 wakaba 1.1 JSTE.List = new JSTE.Class (function (arrayLike) {
164     this.list = arrayLike || [];
165     }, {
166     getLast: function () {
167     if (this.list.length) {
168     return this.list[this.list.length - 1];
169     } else {
170     return null;
171     }
172     }, // getLast
173    
174     forEach: function (code) {
175     var length = this.list.length;
176     for (var i = 0; i < length; i++) {
177     var r = code (this.list[i]);
178     if (r && r.stop) return r.returnValue;
179     }
180     return null;
181     }, // forEach
182 wakaba 1.23 map: function (code) {
183     var newList = new this.constructor;
184     var length = this.list.length;
185     for (var i = 0; i < length; i++) {
186     newList.push (code (this.list[i]));
187     }
188     return newList;
189     }, // map
190     mapToHash: function (code) {
191     var newHash = new JSTE.Hash;
192     var length = this.list.length;
193     for (var i = 0; i < length; i++) {
194     var nv = code (this.list[i]);
195     newHash.setNamedItem (nv[0], nv[1]);
196     }
197     return newHash;
198     }, // mapToHash
199 wakaba 1.4
200     numberToInteger: function () {
201     var newList = [];
202     this.forEach (function (item) {
203     if (typeof item === "number") {
204     newList.push (Math.floor (item));
205     } else {
206     newList.push (item);
207     }
208     });
209     return new this.constructor (newList);
210     }, // numberToInteger
211 wakaba 1.23
212 wakaba 1.1 clone: function () {
213     var newList = [];
214     this.forEach (function (item) {
215     newList.push (item);
216     });
217     return new this.constructor (newList);
218     }, // clone
219    
220     grep: function (code) {
221     var newList = [];
222     this.forEach (function (item) {
223     if (code (item)) {
224     newList.push (item);
225     }
226     });
227     return new this.constructor (newList);
228     }, // grep
229     onlyNonNull: function () {
230     return this.grep (function (item) {
231     return item != null; /* Intentionally "!=" */
232     });
233     }, // onlyNonNull
234 wakaba 1.11
235     uniq: function (eq) {
236     if (!eq) eq = function (i1, i2) { return i1 === i2 };
237     var prevItems = [];
238     return this.grep (function (item) {
239     for (var i = 0; i < prevItems.length; i++) {
240     if (eq (item, prevItems[i])) {
241     return false;
242     }
243     }
244     prevItems.push (item);
245     return true;
246     });
247     }, // uniq
248 wakaba 1.1
249     getFirstMatch: function (code) {
250     return this.forEach (function (item) {
251     if (code (item)) {
252     return new JSTE.List.Return (item);
253     }
254     });
255     }, // getFirstMatch
256    
257     switchByElementType: function () {
258     var cases = new JSTE.List (arguments);
259     this.forEach (function (n) {
260     cases.forEach (function (c) {
261     if (c.namespaceURI == n.namespaceURI) {
262     return new JSTE.List.Return (c.execute (n));
263     }
264     });
265     });
266     }, // switchByElementType
267    
268 wakaba 1.10 // destructive
269 wakaba 1.1 push: function (item) {
270     this.list.push (item);
271     }, // push
272 wakaba 1.10
273     // destructive
274 wakaba 1.1 pushCloneOfLast: function () {
275     this.list.push (this.getLast ().clone ());
276     }, // pushCloneOfLast
277 wakaba 1.10
278     // destructive
279 wakaba 1.1 append: function (list) {
280     var self = this;
281     list.forEach (function (n) {
282     self.list.push (n);
283     });
284 wakaba 1.10 return this;
285 wakaba 1.1 }, // append
286    
287 wakaba 1.10 // destructive
288 wakaba 1.1 pop: function () {
289     return this.list.pop ();
290     }, // pop
291 wakaba 1.10
292     // destructive
293 wakaba 1.1 remove: function (removedItem) {
294     var length = this.list.length;
295     for (var i = length - 1; i >= 0; --i) {
296     var item = this.list[i];
297     if (item == removedItem) { // Intentionally "=="
298     this.list.splice (i, 1);
299     }
300     }
301     }, // remove
302 wakaba 1.10
303     // destructive
304 wakaba 1.1 clear: function () {
305     this.list.splice (0, this.list.length);
306     } // clear
307    
308     }); // List
309    
310 wakaba 1.10 JSTE.Class.addClassMethods (JSTE.List, {
311     spaceSeparated: function (v) {
312     return new JSTE.List ((v || '').split (JSTE.SpaceChars)).grep (function (v) {
313     return v.length;
314     });
315     }, // spaceSeparated
316    
317     getCommonItems: function (l1, l2, cb, eq) {
318 wakaba 1.11 if (!eq) eq = function (i1, i2) { return i1 === i2 };
319 wakaba 1.10
320     var common = new JSTE.List;
321    
322     l1 = l1.grep (function (i1) {
323     var hasI1;
324     l2 = l2.grep (function (i2) {
325     if (eq (i1, i2)) {
326     common.push (i1);
327     hasI1 = true;
328     return false;
329     } else {
330     return true;
331     }
332     });
333     return !hasI1;
334     });
335    
336     cb (common, l1, l2);
337     } // getCommonItems
338     }); // List class methods
339    
340 wakaba 1.1 JSTE.List.Return = new JSTE.Class (function (rv) {
341     this.stop = true;
342     this.returnValue = rv;
343     }, {
344    
345     }); // Return
346    
347     JSTE.List.SwitchByLocalName = new JSTE.Class (function (ns, cases, ow) {
348     this.namespaceURI = ns;
349     this.cases = cases;
350     this.otherwise = ow || function (n) { };
351     }, {
352     execute: function (n) {
353     for (var ln in this.cases) {
354     if (JSTE.Element.matchLocalName (n, ln)) {
355     return this.cases[ln] (n);
356     }
357     }
358     return this.otherwise (n);
359     }
360     });
361    
362    
363     if (!JSTE.Node) JSTE.Node = {};
364    
365     JSTE.Class.addClassMethods (JSTE.Node, {
366     querySelector: function (node, selectors) {
367     if (node.querySelector) {
368     var el;
369     try {
370     el = node.querySelector (selectors);
371     } catch (e) {
372     el = null;
373     }
374     return el;
375     } else if (window.uu && uu.css) {
376     if (selectors != "") {
377     /* NOTE: uu.css return all elements for "" or ",xxx" */
378     return uu.css (selectors, node)[0];
379     } else {
380     return null;
381     }
382     } else if (window.Ten && Ten.DOM && Ten.DOM.getElementsBySelector) {
383     return Ten.DOM.getElementsBySelector (selectors)[0];
384     } else {
385     return null;
386     }
387     }, // querySelector
388     querySelectorAll: function (node, selectors) {
389     if (node.querySelectorAll) {
390     var nl;
391     try {
392     nl = node.querySelectorAll (selectors);
393     } catch (e) {
394     nl = null;
395     }
396     return new JSTE.List (nl);
397     } else if (window.uu && uu.css) {
398     if (selectors != "") {
399 wakaba 1.11 /* NOTE: uu.css return all elements for "" or ",xxx". */
400 wakaba 1.1 return new JSTE.List (uu.css (selectors, node));
401     } else {
402     return new JSTE.List;
403     }
404     } else if (window.Ten && Ten.DOM && Ten.DOM.getElementsBySelector) {
405     return new JSTE.List (Ten.DOM.getElementsBySelector (selectors));
406     } else {
407     return new JSTE.List;
408     }
409     } // querySelectorAll
410     });
411    
412     if (!JSTE.Element) JSTE.Element = {};
413    
414     JSTE.Class.addClassMethods (JSTE.Element, {
415     getLocalName: function (el) {
416     var localName = el.localName;
417     if (!localName) {
418     localName = el.nodeName;
419     if (el.prefix) {
420     localName = localName.substring (el.prefix.length + 1);
421     }
422     }
423     return localName;
424     }, // getLocalName
425    
426     match: function (el, ns, ln) {
427     if (el.nodeType !== 1) return false;
428     if (el.namespaceURI !== ns) return false;
429     return JSTE.Element.matchLocalName (el, ln);
430     }, // match
431     matchLocalName: function (el, ln) {
432     var localName = JSTE.Element.getLocalName (el);
433     if (ln instanceof RegExp) {
434     if (!localName.match (ln)) return false;
435     } else {
436     if (localName !== ln) return false;
437     }
438     return true;
439     }, // matchLocalName
440    
441     getChildElement: function (el, ns, ln) {
442     return new JSTE.List (el.childNodes).getFirstMatch (function (item) {
443     return JSTE.Element.match (item, ns, ln);
444     });
445     }, // getChildElement
446     getChildElements: function (el, ns, ln) {
447     return new JSTE.List (el.childNodes).grep (function (item) {
448     return JSTE.Element.match (item, ns, ln);
449     });
450     }, // getChildElements
451 wakaba 1.17 getChildrenClassifiedByType: function (el) {
452     var r = new JSTE.ElementHash;
453     new JSTE.List (el.childNodes).forEach (function (n) {
454     if (n.nodeType == 1) {
455     r.getOrCreate (n.namespaceURI, JSTE.Element.getLocalName (n)).push (n);
456     } else {
457     r.getOrCreate (null, n.nodeType).push (n);
458     }
459     });
460     return r;
461     }, // getChildrenClassifiedByType
462 wakaba 1.18
463     isEmpty: function (el) {
464     // HTML5 definition of "empty"
465     return !new JSTE.List (el.childNodes).forEach (function (n) {
466     var nt = n.nodeType;
467     if (nt == 1) {
468     return new JSTE.List.Return (true /* not empty */);
469     } else if (nt == 3 || nt == 4) {
470     if (/[^\u0009\u000A\u000C\u000D\u0020]/.test (n.data)) {
471     return new JSTE.List.Return (true /* not empty */);
472     }
473     } else if (nt == 7 || nt == 8) { // comment/pi
474     // does not affect emptyness
475     } else {
476     // We don't support EntityReference.
477     return new JSTE.List.Return (true /* not empty */);
478     }
479     });
480     }, // isEmpty
481 wakaba 1.1
482     appendText: function (el, s) {
483     return el.appendChild (el.ownerDocument.createTextNode (s));
484     }, // appendText
485    
486     createTemplate: function (doc, node) {
487     var df = doc.createDocumentFragment ();
488     new JSTE.List (node.childNodes).forEach (function (n) {
489     if (n.nodeType == 1) {
490     var c = doc.createElement (JSTE.Element.getLocalName (n));
491 wakaba 1.15 new JSTE.List (n.attributes).forEach (function (n) {
492 wakaba 1.1 c.setAttribute (n.name, n.value);
493     });
494     c.appendChild (JSTE.Element.createTemplate (doc, n));
495     df.appendChild (c);
496     } else if (n.nodeType == 3 || n.nodeType == 4) {
497     df.appendChild (doc.createTextNode (n.data));
498     }
499     });
500     return df;
501     }, // createTemplate
502    
503     hasAttribute: function (el, localName) {
504     if (el.hasAttribute) {
505     return el.hasAttribute (localName);
506     } else {
507     return el.getAttribute (localName) != null;
508     }
509     }, // hasAttribute
510    
511     getClassNames: function (el) {
512     return new JSTE.List (el.className.split (JSTE.SpaceChars));
513     }, // getClassNames
514     addClassName: function (el, newClassName) {
515     el.className = el.className + ' ' + newClassName;
516     }, // deleteClassName
517     deleteClassName: function (el, oldClassName) {
518     var classNames = el.className.split (JSTE.SpaceChars);
519     var newClasses = [];
520     for (var n in classNames) {
521     if (classNames[n] != oldClassName) {
522     newClasses.push (classNames[n]);
523     }
524     }
525     el.className = newClasses.join (' ');
526     }, // deleteClassName
527     replaceClassName: function (el, oldClassName, newClassName) {
528     var classNames = el.className.split (JSTE.SpaceChars);
529     var newClasses = [newClassName];
530     for (var n in classNames) {
531     if (classNames[n] != oldClassName) {
532     newClasses.push (classNames[n]);
533     }
534     }
535     el.className = newClasses.join (' ');
536     }, // replaceClassName
537    
538     getIds: function (el) {
539     return new JSTE.List (el.id != "" ? [el.id] : []);
540 wakaba 1.5 }, // getIds
541    
542     /*
543     NR.js <http://suika.fam.cx/www/css/noderect/NodeRect.js> must be loaded
544     before the invocation.
545     */
546     scroll: function (elements) {
547     if (!JSTE.windowLoaded) {
548     new JSTE.Observer ('load', window, function () {
549     JSTE.Element.scroll (elements);
550     });
551     return;
552     }
553    
554     var top = Infinity;
555     var left = Infinity;
556     var topEl;
557     var leftEl;
558     elements.forEach (function (el) {
559     var rect = NR.Element.getRects (el, window).borderBox;
560     if (rect.top < top) {
561     top = rect.top;
562     topEl = el;
563     }
564     if (rect.left < left) {
565     left = rect.left;
566     leftEl = el;
567     }
568     });
569    
570     if (!leftEl && !topEl) {
571     return;
572     }
573    
574     var doc = (leftEl || topEl).ownerDocument;
575 wakaba 1.1
576 wakaba 1.5 var rect = NR.View.getViewportRects (window, doc).contentBox;
577     if (rect.top <= top && top <= rect.bottom) {
578     top = rect.top;
579     }
580     if (rect.left <= left && left <= rect.right) {
581     left = rect.left;
582     }
583    
584     /*
585     Set scroll* of both <html> and <body> elements, to support all of
586     four browsers and two (or three) rendering modes. This might result
587     in confusing the user if both <html> and <body> elements have their
588     'overflow' properties specified to 'scroll'.
589    
590     Note that this code does not do a good job if the |el| is within an
591     |overflow: scroll| element.
592     */
593     doc.body.scrollTop = top;
594     doc.body.scrollLeft = left;
595     doc.documentElement.scrollTop = top;
596     doc.documentElement.scrollLeft = left;
597     } // scroll
598 wakaba 1.18 }); // Element
599 wakaba 1.1
600 wakaba 1.17 JSTE.ElementHash = new JSTE.Class (function () {
601     this.items = [];
602     }, {
603     get: function (ns, ln) {
604     ns = ns || '';
605     if (this.items[ns]) {
606     return this.items[ns].getNamedItem (ln) || new JSTE.List;
607     } else {
608     return new JSTE.List;
609     }
610     }, // get
611     getOrCreate: function (ns, ln) {
612     ns = ns || '';
613     if (this.items[ns]) {
614     var l = this.items[ns].getNamedItem (ln);
615     if (!l) this.items[ns].setNamedItem (ln, l = new JSTE.List);
616     return l;
617     } else {
618     var l;
619     this.items[ns] = new JSTE.Hash;
620     this.items[ns].setNamedItem (ln, l = new JSTE.List);
621     return l;
622     }
623     } // getOrCreate
624     }); // ElementHash
625    
626 wakaba 1.9 JSTE.XHR = new JSTE.Class (function (url, onsuccess, onerror) {
627     try {
628     this._xhr = new XMLHttpRequest ();
629     } catch (e) {
630     try {
631     this._xhr = new ActiveXObject ('Msxml2.XMLHTTP');
632     } catch (e) {
633     try {
634     this._xhr = new ActiveXObject ('Microsoft.XMLHTTP');
635     } catch (e) {
636     try {
637     this._xhr = new ActiveXObject ('Msxml2.XMLHTTP.4.0');
638     } catch (e) {
639     this._xhr = null;
640     }
641     }
642     }
643     }
644    
645     this._url = url;
646     this._onsuccess = onsuccess || function () { };
647     this._onerror = onerror || function () { };
648     }, {
649     get: function () {
650     if (!this._xhr) return;
651    
652     var self = this;
653     this._xhr.open ('GET', this._url, true);
654     this._xhr.onreadystatechange = function () {
655     self._onreadystatechange ();
656     }; // onreadystatechange
657     this._xhr.send (null);
658     }, // get
659    
660     _onreadystatechange: function () {
661     if (this._xhr.readyState == 4) {
662     if (this.succeeded ()) {
663     this._onsuccess ();
664     } else {
665     this._onerror ();
666     }
667     }
668     }, // _onreadystatechange
669    
670     succeeded: function () {
671     return (this._xhr.status < 400);
672     }, // succeeded
673    
674     getDocument: function () {
675     return this._xhr.responseXML;
676     } // getDocument
677     }); // XHR
678    
679 wakaba 1.22 // An abstract class
680     JSTE.Storage = new JSTE.Class (function () {
681    
682     }, {
683 wakaba 1.23 get: function (name) {
684     throw "not implemented";
685     }, // get
686     set: function (name, value) {
687     throw "not implemented";
688     }, // set
689    
690     getNames: function () {
691     throw "not implemented";
692 wakaba 1.24 }, // getNames
693    
694     setPrefix: function (newPrefix) {
695     throw "not implemented";
696     } // setPrefix
697 wakaba 1.22 }); // Storage
698    
699     JSTE.Storage.PageLocal = new JSTE.Subclass (function () {
700 wakaba 1.24 this.keyPrefix = '';
701 wakaba 1.22 }, JSTE.Storage, {
702     get: function (name) {
703 wakaba 1.24 return this['value-' + this.keyPrefix + name];
704 wakaba 1.22 }, // get
705     set: function (name, value) {
706 wakaba 1.24 this['value-' + this.keyPrefix + name] = value;
707 wakaba 1.22 }, // set
708    
709     getNames: function () {
710     var names = new JSTE.List;
711     for (var n in this) {
712 wakaba 1.24 if (n.substring (0, 6 + this.keyPrefix.length) == 'value-' + this.keyPrefix) {
713     names.push (n.substring (6 + this.keyPrefix.length));
714 wakaba 1.22 }
715     }
716     return names;
717 wakaba 1.24 }, // getNames
718    
719     setPrefix: function (newPrefix) {
720     this.keyPrefix = newPrefix;
721     } // setPrefix
722 wakaba 1.23 }); // PageLocal
723    
724     JSTE.Storage.Cookie = JSTE.Subclass (function () {
725     this.keyPrefix = '';
726     this.domain = null;
727     this.path = '/';
728     this.persistent = false;
729     this.expires = null; // or Date
730     }, JSTE.Storage, {
731     _parse: function () {
732     return new JSTE.List (document.cookie.split (/;/)).mapToHash (function (nv) {
733     nv = nv.replace (/^\s+/, '').replace (/\s+$/, '').split (/=/, 2);
734     nv[0] = decodeURIComponent (nv[0]);
735     nv[1] = decodeURIComponent (nv[1]);
736     return nv;
737     });
738     }, // _parse
739    
740     get: function (name) {
741     return this._parse ().getNamedItem (this.keyPrefix + name);
742     }, // get
743     set: function (name, value) {
744     name = this.keyPrefix + name;
745     var r = encodeURIComponent (name) + '=' + encodeURIComponent (value);
746     if (this.domain) {
747     r += '; domain=' + this.domain;
748     }
749     if (this.path) {
750     r += '; path=' + this.path;
751     }
752     if (this.persistent) {
753     r += '; expires=' + new Date (2030, 1-1, 1).toUTCString ();
754     } else if (this.expires) {
755     r += '; expires=' + this.expires.toUTCString ();
756     }
757     document.cookie = r;
758     }, // set
759     delete: function (name) {
760     var expires = this.expires;
761     var persistent = this.persistent;
762     this.expires = new Date (0);
763     this.persistent = false;
764     this.set (name, '');
765     this.expires = expires;
766     this.persistent = persistent;
767     }, // delete
768    
769     getNames: function () {
770     var self = this;
771     return this._parse ().getNames ().grep (function (name) {
772     return name.substring (0, self.keyPrefix.length) == self.keyPrefix;
773     }).map (function (name) {
774     return name.substring (self.keyPrefix.length);
775     });
776 wakaba 1.24 }, // getNames
777    
778     setPrefix: function (newPrefix) {
779     this.keyPrefix = newPrefix;
780     } // setPrefix
781 wakaba 1.23 }); // Cookie
782    
783     JSTE.Storage.Local = JSTE.Class (function () {
784     var self = new JSTE.Storage.Cookie;
785     self.keyPrefix = 'localStorage-';
786     self.persistent = true;
787 wakaba 1.24 self.setPrefix = function (newPrefix) {
788     this.keyPrefix = 'localStorage-' + newPrefix;
789     }; // setPrefix
790 wakaba 1.23 return self;
791     }); // Local
792 wakaba 1.22
793 wakaba 1.9
794 wakaba 1.1 /* Events: load, close, shown, hidden */
795 wakaba 1.18 JSTE.Message = new JSTE.Class (function (doc, template, commandTarget, availCommands) {
796 wakaba 1.1 if (!doc) return;
797     this._targetDocument = doc;
798     this._template = template || doc.createDocumentFragment ();
799 wakaba 1.8
800 wakaba 1.1 this._commandTarget = commandTarget;
801 wakaba 1.18 this._availCommands = availCommands || new JSTE.List;
802 wakaba 1.8
803 wakaba 1.1 this.hidden = true;
804     this.select = "";
805    
806     var e = new JSTE.Event ('load');
807     this.dispatchEvent (e);
808     }, {
809     render: function () {
810     var self = this;
811     var doc = this._targetDocument;
812    
813     var msgContainer = doc.createElement ('section');
814     msgContainer.appendChild (this._template);
815 wakaba 1.20
816     if (!this._availCommands.list.length) {
817 wakaba 1.8 this._availCommands.push ({name: 'back'});
818     this._availCommands.push ({name: 'next'});
819 wakaba 1.7 }
820 wakaba 1.20
821     this._availCommands = this._availCommands.grep(function (item) {
822     return self._commandTarget.canExecuteCommand (item.name, item.args);
823     });
824 wakaba 1.1
825 wakaba 1.8 this._outermostElement = this._render (msgContainer);
826 wakaba 1.1
827     this.show ();
828     }, // render
829     _render: function (msgContainer, buttonContainer) {
830     var doc = this._targetDocument;
831    
832     var container = doc.createElement ('article');
833    
834     container.appendChild (msgContainer);
835 wakaba 1.8
836     var buttonContainer = this.createCommandButtons ();
837 wakaba 1.1 container.appendChild (buttonContainer);
838 wakaba 1.8
839 wakaba 1.1 doc.documentElement.appendChild (container);
840    
841     return container;
842     }, // _render
843 wakaba 1.8 createCommandButtons: function () {
844     var self = this;
845 wakaba 1.18 var doc = this._targetDocument;
846     var buttonContainer = doc.createElement ('menu');
847 wakaba 1.8 this._availCommands.forEach (function (cmd) {
848 wakaba 1.18 var label = cmd.name;
849     if (cmd.labelTemplate) {
850     label = JSTE.Element.createTemplate (doc, cmd.labelTemplate);
851     }
852    
853 wakaba 1.8 var button = new JSTE.Message.Button
854 wakaba 1.18 (label, self._commandTarget, cmd.name, cmd.args);
855 wakaba 1.8 buttonContainer.appendChild (button.element);
856     });
857     return buttonContainer;
858     }, // createCommandButtons
859    
860 wakaba 1.1 remove: function () {
861     this.hide ();
862    
863     this._remove ();
864    
865     if (this._outermostElement && this._outermostElement.parentNode) {
866     this._outermostElement.parentNode.removeChild (this._outermostElement);
867     }
868    
869     var e = new JSTE.Event ("close");
870     this.dispatchEvent (e);
871     }, // remove
872     _remove: function () {
873    
874     }, // remove
875    
876     show: function () {
877     if (!this.hidden) return;
878     this.hidden = false;
879     if (this._outermostElement) {
880     JSTE.Element.replaceClassName
881     (this._outermostElement, "jste-hidden", "jste-shown");
882     }
883    
884     var e = new JSTE.Event ("shown");
885     this.dispatchEvent (e);
886     }, // show
887     hide: function () {
888     if (this.hidden) return;
889     this.hidden = true;
890     if (this._outermostElement) {
891     JSTE.Element.replaceClassName
892     (this._outermostElement, "jste-shown", "jste-hidden");
893     }
894    
895     var e = new JSTE.Event ("hidden");
896     this.dispatchEvent (e);
897     }, // hide
898    
899     setTimeout: function () {
900     /* TODO: ... */
901    
902     }
903    
904     }); // Message
905    
906 wakaba 1.7 /* TODO: button label text should refer message catalog */
907    
908 wakaba 1.1 JSTE.Message.Button =
909 wakaba 1.18 new JSTE.Class (function (label, commandTarget, commandName, commandArgs) {
910     this._label = label != null ? label : "";
911 wakaba 1.6
912 wakaba 1.1 if (commandTarget && commandTarget instanceof Function) {
913     this._command = commandTarget;
914 wakaba 1.6 this._classNames = new JSTE.List;
915 wakaba 1.1 } else if (commandTarget) {
916     this._command = function () {
917     return commandTarget.executeCommand.apply
918     (commandTarget, [commandName, commandArgs]);
919     };
920 wakaba 1.6 this._classNames = new JSTE.List (['jste-command-' + commandName]);
921 wakaba 1.1 } else {
922     this._command = function () { };
923 wakaba 1.6 this._classNames = new JSTE.List;
924 wakaba 1.1 }
925    
926 wakaba 1.6 this._createElement ();
927 wakaba 1.1 }, {
928 wakaba 1.6 _createElement: function () {
929     try {
930     this.element = document.createElement ('button');
931     this.element.setAttribute ('type', 'button');
932     } catch (e) {
933     this.element = document.createElement ('<button type=button>');
934     }
935 wakaba 1.18 if (this._label.nodeType) {
936     this.element.appendChild (this._label);
937     } else {
938     JSTE.Element.appendText (this.element, this._label);
939     }
940 wakaba 1.6 this.element.className = this._classNames.list.join (' ');
941 wakaba 1.1
942 wakaba 1.6 var self = this;
943     new JSTE.Observer ("click", this.element, function (e) {
944     self._command (e);
945     });
946     } // _createElement
947 wakaba 1.1 }); // Button
948    
949     JSTE.Course = new JSTE.Class (function (doc) {
950     this._targetDocument = doc;
951    
952 wakaba 1.22 this._entryPointsByStateName = new JSTE.Hash;
953 wakaba 1.23 this._entryPointsByStateName.setNamedItem ('done', 'special-none');
954 wakaba 1.22
955 wakaba 1.1 this._entryPointsByURL = {};
956     this._entryPointsById = {};
957     this._entryPointsByClassName = {};
958    
959     this._stepsState = new JSTE.List ([new JSTE.Hash]);
960     this._steps = new JSTE.Hash;
961    
962     var nullState = new JSTE.Step;
963     nullState.uid = "";
964     this._steps.setNamedItem (nullState.uid, nullState);
965     this._initialStepUid = nullState.uid;
966     }, {
967 wakaba 1.10 _processStepsContent: function (el, parentSteps) {
968 wakaba 1.1 var self = this;
969     new JSTE.List (el.childNodes).switchByElementType (
970     new JSTE.List.SwitchByLocalName (JSTE.WATNS, {
971 wakaba 1.10 steps: function (n) { self._processStepsElement (n, parentSteps) },
972     step: function (n) { self._processStepElement (n, parentSteps) },
973     jump: function (n) { self._processJumpElement (n, parentSteps) },
974 wakaba 1.14 entryPoint: function (n) { self._processEntryPointElement (n, parentSteps) }
975 wakaba 1.1 })
976     );
977     }, // _processStepsContent
978 wakaba 1.10 _processStepsElement: function (e, parentSteps) {
979     var steps = new JSTE.Steps ();
980     steps.parentSteps = parentSteps;
981 wakaba 1.1 this._stepsState.pushCloneOfLast ();
982     this._stepsState.getLast ().prevStep = null;
983 wakaba 1.10 this._processStepsContent (e, steps);
984 wakaba 1.1 this._stepsState.pop ();
985     }, // _processStepsElement
986    
987 wakaba 1.14 _processEntryPointElement: function (e, parentSteps) {
988 wakaba 1.22 if (JSTE.Element.hasAttribute (e, 'state')) {
989     this.setEntryPointByStateName
990     (e.getAttribute ('state'), e.getAttribute ('step'));
991     } else if (JSTE.Element.hasAttribute (e, 'url')) {
992 wakaba 1.1 this.setEntryPointByURL
993     (e.getAttribute ('url'), e.getAttribute ('step'));
994     } else if (JSTE.Element.hasAttribute (e, 'root-id')) {
995     this.setEntryPointById
996     (e.getAttribute ('root-id'), e.getAttribute ('step'));
997     } else if (JSTE.Element.hasAttribute (e, 'root-class')) {
998     this.setEntryPointByClassName
999     (e.getAttribute ('root-class'), e.getAttribute ('step'));
1000     }
1001 wakaba 1.14 }, // _processEntryPointElement
1002 wakaba 1.22 setEntryPointByStateName: function (stateName, stepName) {
1003     this._entryPointsByStateName.setNamedItem (stateName, stepName || '');
1004     }, // setEntryPointByStateName
1005 wakaba 1.1 setEntryPointByURL: function (url, stepName) {
1006 wakaba 1.24 // TODO: HTML5 URL->URI convertion
1007     this._entryPointsByURL[encodeURI (url)] = stepName || '';
1008 wakaba 1.1 }, // setEntryPointByURL
1009     setEntryPointById: function (id, stepName) {
1010     this._entryPointsById[id] = stepName || '';
1011     }, // setEntryPointById
1012     setEntryPointByClassName: function (className, stepName) {
1013     this._entryPointsByClassName[className] = stepName || '';
1014     }, // setEntryPointByClassName
1015 wakaba 1.22 findEntryPoint: function (doc, states) {
1016 wakaba 1.1 var self = this;
1017     var td = this._targetDocument;
1018     var stepName;
1019 wakaba 1.22
1020     if (states) {
1021     stepName = self._entryPointsByStateName.getByNames (states.getNames ());
1022     if (stepName) return stepName;
1023     }
1024 wakaba 1.1
1025     var url = doc.URL;
1026     if (url) {
1027     stepName = self._entryPointsByURL[url];
1028     if (stepName) return 'id-' + stepName;
1029     }
1030 wakaba 1.24 // TODO: multiple elements with same ID
1031     // TODO: interpage "back" button
1032     // TODO: prefetch
1033 wakaba 1.1
1034     var docEl = td.documentElement;
1035     if (docEl) {
1036     var docElId = JSTE.Element.getIds (docEl).forEach (function (i) {
1037     stepName = self._entryPointsById[i];
1038     if (stepName) return new JSTE.List.Return (stepName);
1039     });
1040     if (stepName) return 'id-' + stepName;
1041    
1042     stepName = JSTE.Element.getClassNames (docEl).forEach (function (c) {
1043     stepName = self._entryPointsByClassName[c];
1044     if (stepName) return new JSTE.List.Return (stepName);
1045     });
1046     if (stepName) return 'id-' + stepName;
1047     }
1048    
1049     var bodyEl = td.body;
1050     if (bodyEl) {
1051     var bodyElId = JSTE.Element.getIds (bodyEl).forEach (function (i) {
1052     stepName = self._entryPointsById[i];
1053     if (stepName) return new JSTE.List.Return (stepName);
1054     });
1055     if (stepName) return 'id-' + stepName;
1056    
1057     stepName = JSTE.Element.getClassNames (bodyEl).forEach (function (c) {
1058     stepName = self._entryPointsByClassName[c];
1059     if (stepName) return new JSTE.List.Return (stepName);
1060     });
1061     if (stepName) return 'id-' + stepName;
1062     }
1063    
1064     return this._initialStepUid;
1065     }, // findEntryPoint
1066    
1067 wakaba 1.10 _processStepElement: function (e, parentSteps) {
1068 wakaba 1.18 var self = this;
1069    
1070 wakaba 1.1 var step = new JSTE.Step (e.getAttribute ('id'));
1071 wakaba 1.10 step.parentSteps = parentSteps;
1072 wakaba 1.1 step.setPreviousStep (this._stepsState.getLast ().prevStep);
1073     step.select = e.getAttribute ('select') || "";
1074     step.nextEvents.append
1075 wakaba 1.10 (JSTE.List.spaceSeparated (e.getAttribute ('next-event')));
1076 wakaba 1.17
1077     var cs = JSTE.Element.getChildrenClassifiedByType (e);
1078    
1079     var msgEl = cs.get (JSTE.WATNS, 'message').list[0];
1080 wakaba 1.1 if (msgEl) {
1081     var msg = JSTE.Element.createTemplate (this._targetDocument, msgEl);
1082     step.setMessageTemplate (msg);
1083     }
1084 wakaba 1.17
1085     var nextEls = cs.get (JSTE.WATNS, 'next-step');
1086 wakaba 1.16 if (nextEls.list.length) {
1087 wakaba 1.1 nextEls.forEach (function (nextEl) {
1088     step.addNextStep
1089     (nextEl.getAttribute ('if'), nextEl.getAttribute ('step'));
1090     });
1091     this._stepsState.getLast ().prevStep = null;
1092     } else {
1093     this._stepsState.getLast ().prevStep = step;
1094     }
1095 wakaba 1.17
1096 wakaba 1.19 cs.get (JSTE.WATNS, 'command').forEach (function (bEl) {
1097 wakaba 1.18 var cmd = {
1098 wakaba 1.20 name: bEl.getAttribute ('type') || 'gotoStep'
1099 wakaba 1.18 };
1100     if (cmd.name == 'gotoStep') {
1101     cmd.args = ['id-' + bEl.getAttribute ('step')];
1102 wakaba 1.24 } else if (cmd.name == 'url') {
1103     cmd.args = [bEl.getAttribute ('href')];
1104 wakaba 1.18 }
1105     if (!JSTE.Element.isEmpty (bEl)) {
1106     cmd.labelTemplate = JSTE.Element.createTemplate (self._targetDocument, bEl);
1107     }
1108     step.availCommands.push (cmd);
1109 wakaba 1.22 }); // wat:command
1110 wakaba 1.18
1111 wakaba 1.22 cs.get (JSTE.WATNS, 'save-state').forEach (function (bEl) {
1112     var ss = new JSTE.SaveState
1113     (bEl.getAttribute ('name'), bEl.getAttribute ('value'));
1114     step.saveStates.push (ss);
1115     }); // wat:save-state
1116 wakaba 1.13
1117     var evs = JSTE.List.spaceSeparated (e.getAttribute ('entry-event'));
1118     if (evs.list.length) {
1119     var jump = new JSTE.Jump (step.select, evs, step.uid);
1120     if (parentSteps) parentSteps._jumps.push (jump);
1121     }
1122 wakaba 1.1
1123     this._steps.setNamedItem (step.uid, step);
1124     if (!this._initialStepUid) {
1125     this._initialStepUid = step.uid;
1126     }
1127     }, // _processStepElement
1128    
1129 wakaba 1.10 _processJumpElement: function (e, parentSteps) {
1130     var target = e.getAttribute ('target') || '';
1131     var evs = JSTE.List.spaceSeparated (e.getAttribute ('event'));
1132     var stepName = e.getAttribute ('step') || '';
1133    
1134     var jump = new JSTE.Jump (target, evs, 'id-' + stepName);
1135     if (parentSteps) parentSteps._jumps.push (jump);
1136 wakaba 1.1 }, // _processJumpElement
1137    
1138     getStep: function (uid) {
1139     return this._steps.getNamedItem (uid);
1140     } // getStep
1141     }); // Course
1142    
1143 wakaba 1.9 JSTE.Class.addClassMethods (JSTE.Course, {
1144     createFromDocument: function (doc, targetDoc) {
1145     var course = new JSTE.Course (targetDoc);
1146     var docEl = doc.documentElement;
1147     if (!docEl) return course;
1148     if (!JSTE.Element.match (docEl, JSTE.WATNS, 'course')) return course;
1149 wakaba 1.10 course._processStepsContent (docEl, null);
1150 wakaba 1.24 course.name = docEl.hasAttribute ('name') ? docEl.getAttribute ('name') + '-' : '';
1151 wakaba 1.9 return course;
1152     }, // createFromDocument
1153     createFromURL: function (url, targetDoc, onload, onerror) {
1154     new JSTE.XHR (url, function () {
1155     var course = JSTE.Course.createFromDocument
1156     (this.getDocument (), targetDoc);
1157     if (onload) onload (course);
1158     }, onerror).get ();
1159     } // creatFromURL
1160     }); // Course class methods
1161 wakaba 1.1
1162 wakaba 1.10 JSTE.Jump = new JSTE.Class (function (selectors, eventNames, stepUid) {
1163     this.selectors = selectors;
1164     this.eventNames = eventNames;
1165     this.stepUid = stepUid;
1166     // this.parentSteps
1167     }, {
1168     startObserver: function (doc, commandTarget) {
1169     var self = this;
1170     var observers = new JSTE.List;
1171    
1172     var onev = function () {
1173     commandTarget.gotoStep (self.stepUid);
1174     };
1175    
1176     JSTE.Node.querySelectorAll (doc, this.selectors).forEach
1177     (function (el) {
1178     self.eventNames.forEach (function (evName) {
1179     var ob = new JSTE.Observer (evName, el, onev);
1180     ob._stepUid = self.stepUid;
1181     observers.push (ob);
1182     });
1183     });
1184    
1185     return observers;
1186     } // startObserver
1187     }); // Jump
1188    
1189     JSTE.Steps = new JSTE.Class (function () {
1190     this._jumps = new JSTE.List;
1191     this._jumpHandlers = new JSTE.List;
1192     }, {
1193     setCurrentStepByUid: function (uid) {
1194     this._jumpHandlers.forEach (function (jh) {
1195     if (jh._stepUid != uid && jh.disabled) {
1196     jh.start ();
1197     } else if (jh._stepUid == uid && !jh.disabled) {
1198     jh.stop ();
1199     }
1200     });
1201     }, // setCurrentStepByUid
1202    
1203     installJumps: function (doc, commandTarget) {
1204     if (this._jumpHandlers.list.length) return;
1205     var self = this;
1206     this._jumps.forEach (function (j) {
1207     self._jumpHandlers.append (j.startObserver (doc, commandTarget));
1208     });
1209     }, // installJumps
1210    
1211     uninstallJumps: function () {
1212     this._jumpHandlers.forEach (function (jh) {
1213     jh.stop ();
1214     });
1215     this._jumpHandlers.clear ();
1216     } // uninstallJumps
1217     }); // Steps
1218    
1219 wakaba 1.1 JSTE.Step = new JSTE.Class (function (id) {
1220     if (id != null && id != '') {
1221     this.uid = 'id-' + id;
1222     } else {
1223     this.uid = 'rand-' + Math.random ();
1224     }
1225     this._nextSteps = new JSTE.List;
1226     this.nextEvents = new JSTE.List;
1227 wakaba 1.18 this.availCommands = new JSTE.List;
1228 wakaba 1.22 this.saveStates = new JSTE.List;
1229 wakaba 1.1 this.select = "";
1230     }, {
1231     setMessageTemplate: function (msg) {
1232     this._messageTemplate = msg;
1233     }, // setMessageTemplate
1234     hasMessage: function () {
1235     return this._messageTemplate ? true : false;
1236     }, // hasMessage
1237     createMessage: function (msg, doc, commandTarget) {
1238     var msg;
1239     if (this._messageTemplate) {
1240     var clone = JSTE.Element.createTemplate (doc, this._messageTemplate);
1241 wakaba 1.18 msg = new msg (doc, clone, commandTarget, this.availCommands.clone ());
1242 wakaba 1.1 } else {
1243     msg = new msg (doc, null, commandTarget);
1244     }
1245     msg.select = this.select;
1246     return msg;
1247     }, // createMessage
1248    
1249     addNextStep: function (condition, stepId) {
1250 wakaba 1.16 if (stepId != null) this._nextSteps.push ([condition, stepId]);
1251 wakaba 1.1 }, // addNextStep
1252     setPreviousStep: function (prevStep) {
1253     if (!prevStep) return;
1254     if (prevStep._defaultNextStepUid) return;
1255     prevStep._defaultNextStepUid = this.uid;
1256     }, // setPreviousStep
1257    
1258     getNextStepUid: function (doc) {
1259     var m = this._nextSteps.getFirstMatch (function (item) {
1260     var condition = item[0];
1261     if (condition) {
1262     return JSTE.Node.querySelector (doc, condition) != null;
1263     } else {
1264     return true;
1265     }
1266     });
1267     if (m) {
1268     return 'id-' + m[1];
1269     } else if (this._defaultNextStepUid) {
1270     return this._defaultNextStepUid;
1271     } else {
1272     return null;
1273     }
1274 wakaba 1.10 }, // getNextStepUid
1275    
1276     getAncestorStepsObjects: function () {
1277     var steps = new JSTE.List;
1278     var s = this.parentSteps;
1279     while (s != null) {
1280     steps.push (s);
1281     s = s.parentSteps;
1282     }
1283     return steps;
1284     } // getAncestorStepsObjects
1285 wakaba 1.1 }); // Step
1286    
1287 wakaba 1.22 JSTE.SaveState = new JSTE.Class (function (name, value) {
1288     this.name = name || '';
1289     this.value = value || '';
1290     }, {
1291     save: function (states) {
1292     states.set (this.name, this.value);
1293     } // save
1294     }); // SaveState
1295    
1296     /* Events: load, error, cssomready, close */
1297 wakaba 1.9 JSTE.Tutorial = new JSTE.Class (function (course, doc, args) {
1298 wakaba 1.1 this._course = course;
1299     this._targetDocument = doc;
1300     this._messageClass = JSTE.Message;
1301     if (args) {
1302     if (args.messageClass) this._messageClass = args.messageClass;
1303 wakaba 1.22 if (args.states) this._states = args.states;
1304 wakaba 1.1 }
1305 wakaba 1.22 if (!this._states) this._states = new JSTE.Storage.PageLocal;
1306 wakaba 1.24 this._states.setPrefix (course.name);
1307 wakaba 1.1
1308     this._currentMessages = new JSTE.List;
1309     this._currentObservers = new JSTE.List;
1310     this._prevStepUids = new JSTE.List;
1311 wakaba 1.10 this._currentStepsObjects = new JSTE.List;
1312 wakaba 1.1
1313 wakaba 1.22 var stepUid = this._course.findEntryPoint (document, this._states);
1314 wakaba 1.1 this._currentStep = this._getStepOrError (stepUid);
1315     if (this._currentStep) {
1316     var e = new JSTE.Event ('load');
1317     this.dispatchEvent (e);
1318    
1319 wakaba 1.3 var self = this;
1320     new JSTE.Observer ('cssomready', this, function () {
1321     self._renderCurrentStep ();
1322     });
1323     this._dispatchCSSOMReadyEvent ();
1324 wakaba 1.1 return this;
1325     } else {
1326     return {};
1327     }
1328     }, {
1329     _getStepOrError: function (stepUid) {
1330     var step = this._course.getStep (stepUid);
1331     if (step) {
1332     return step;
1333 wakaba 1.22 } else if (stepUid == 'special-none') {
1334     return null;
1335 wakaba 1.1 } else {
1336     var e = new JSTE.Event ('error');
1337     e.errorMessage = 'Step not found';
1338     e.errorArguments = [this._currentStepUid];
1339     this.dispatchEvent (e);
1340     return null;
1341     }
1342     }, // _getStepOrError
1343    
1344     _renderCurrentStep: function () {
1345     var self = this;
1346     var step = this._currentStep;
1347 wakaba 1.22
1348     step.saveStates.forEach (function (ss) { ss.save (self._states) });
1349 wakaba 1.1
1350     /* Message */
1351     var msg = step.createMessage
1352     (this._messageClass, this._targetDocument, this);
1353     msg.render ();
1354     this._currentMessages.push (msg);
1355    
1356     /* Next-events */
1357     var selectedNodes = JSTE.Node.querySelectorAll
1358     (this._targetDocument, step.select);
1359     var handler = function () {
1360     self.executeCommand ("next");
1361     };
1362     selectedNodes.forEach (function (node) {
1363     step.nextEvents.forEach (function (eventType) {
1364     self._currentObservers.push
1365     (new JSTE.Observer (eventType, node, handler));
1366     });
1367     });
1368 wakaba 1.10
1369     JSTE.List.getCommonItems (this._currentStepsObjects,
1370     step.getAncestorStepsObjects (),
1371     function (common, onlyInOld, onlyInNew) {
1372     common.forEach (function (item) {
1373     item.setCurrentStepByUid (step.uid);
1374     });
1375     onlyInOld.forEach (function (item) {
1376     item.uninstallJumps ();
1377     });
1378     onlyInNew.forEach (function (item) {
1379     item.installJumps (self._targetDocument, self);
1380     });
1381     self._currentStepsObjects = common.append (onlyInNew);
1382     });
1383 wakaba 1.1 }, // _renderCurrentStep
1384     clearMessages: function () {
1385     this._currentMessages.forEach (function (msg) {
1386     msg.remove ();
1387     });
1388     this._currentMessages.clear ();
1389    
1390     this._currentObservers.forEach (function (ob) {
1391     ob.stop ();
1392     });
1393     this._currentObservers.clear ();
1394     }, // clearMessages
1395 wakaba 1.10 clearStepsHandlers: function () {
1396     this._currentStepsObjects.forEach (function (item) {
1397     item.uninstallJumps ();
1398     });
1399     this._currentStepsObjects.clear ();
1400     }, // clearStepsHandlers
1401 wakaba 1.1
1402     executeCommand: function (commandName, commandArgs) {
1403     if (this[commandName]) {
1404     return this[commandName].apply (this, commandArgs || []);
1405     } else {
1406     var e = new JSTE.Event ('error');
1407     e.errorMessage = 'Command not found';
1408     e.errorArguments = [commandName];
1409     return null;
1410     }
1411     }, // executeCommand
1412 wakaba 1.7 canExecuteCommand: function (commandName, commandArgs) {
1413     if (this[commandName]) {
1414     var can = this['can' + commandName.substring (0, 1).toUpperCase ()
1415     + commandName.substring (1)];
1416     if (can) {
1417     return can.apply (this, arguments);
1418     } else {
1419     return true;
1420     }
1421     } else {
1422     return false;
1423     }
1424     }, // canExecuteCommand
1425 wakaba 1.1
1426     back: function () {
1427     var prevStepUid = this._prevStepUids.pop ();
1428     var prevStep = this._getStepOrError (prevStepUid);
1429     if (prevStep) {
1430     this.clearMessages ();
1431     this._currentStep = prevStep;
1432     this._renderCurrentStep ();
1433     }
1434     }, // back
1435 wakaba 1.7 canBack: function () {
1436     return this._prevStepUids.list.length > 0;
1437     }, // canBack
1438 wakaba 1.1 next: function () {
1439     var nextStepUid = this._currentStep.getNextStepUid (this._targetDocument);
1440     var nextStep = this._getStepOrError (nextStepUid);
1441     if (nextStep) {
1442     this._prevStepUids.push (this._currentStep.uid);
1443     this.clearMessages ();
1444     this._currentStep = nextStep;
1445     this._renderCurrentStep ();
1446     }
1447 wakaba 1.3 }, // next
1448 wakaba 1.7 canNext: function () {
1449     return this._currentStep.getNextStepUid (this._targetDocument) != null;
1450     }, // canNext
1451 wakaba 1.10 gotoStep: function (uid) {
1452     var nextStep = this._getStepOrError (uid);
1453     if (nextStep) {
1454     this._prevStepUids.push (this._currentStep.uid);
1455     this.clearMessages ();
1456     this._currentStep = nextStep;
1457     this._renderCurrentStep ();
1458     }
1459     }, // gotoStep
1460 wakaba 1.24
1461     url: function (href) {
1462     location.href = href;
1463     }, // url
1464 wakaba 1.20
1465     close: function () {
1466     this.clearMessages ();
1467 wakaba 1.22 var e = new JSTE.Event ('closed');
1468     this.dispatchEvent (e);
1469 wakaba 1.20 }, // close
1470 wakaba 1.3
1471     // <http://twitter.com/waka/status/1129513097>
1472     _dispatchCSSOMReadyEvent: function () {
1473     var self = this;
1474     var e = new JSTE.Event ('cssomready');
1475     if (window.opera && document.readyState != 'complete') {
1476     new JSTE.Observer ('readystatechange', document, function () {
1477     if (document.readyState == 'complete') {
1478     self.dispatchEvent (e);
1479     }
1480     });
1481     } else {
1482     this.dispatchEvent (e);
1483     }
1484     } // dispatchCSSOMReadyEvent
1485    
1486 wakaba 1.1 }); // Tutorial
1487 wakaba 1.9
1488     JSTE.Class.addClassMethods (JSTE.Tutorial, {
1489 wakaba 1.12 createFromURL: function (url, doc, args, onload) {
1490 wakaba 1.9 JSTE.Course.createFromURL (url, doc, function (course) {
1491 wakaba 1.12 var tutorial = new JSTE.Tutorial (course, doc, args);
1492     if (onload) onload (tutorial);
1493 wakaba 1.9 });
1494     } // createFromURL
1495     }); // Tutorial class methods
1496    
1497    
1498 wakaba 1.5
1499     if (JSTE.onLoadFunctions) {
1500     new JSTE.List (JSTE.onLoadFunctions).forEach (function (code) {
1501     code ();
1502     });
1503     }
1504    
1505     if (JSTE.isDynamicallyLoaded) {
1506     JSTE.windowLoaded = true;
1507     }
1508 wakaba 1.2
1509     /* ***** BEGIN LICENSE BLOCK *****
1510     * Copyright 2008-2009 Wakaba <w@suika.fam.cx>. All rights reserved.
1511     *
1512     * This program is free software; you can redistribute it and/or
1513     * modify it under the same terms as Perl itself.
1514     *
1515     * Alternatively, the contents of this file may be used
1516     * under the following terms (the "MPL/GPL/LGPL"),
1517     * in which case the provisions of the MPL/GPL/LGPL are applicable instead
1518     * of those above. If you wish to allow use of your version of this file only
1519     * under the terms of the MPL/GPL/LGPL, and not to allow others to
1520     * use your version of this file under the terms of the Perl, indicate your
1521     * decision by deleting the provisions above and replace them with the notice
1522     * and other provisions required by the MPL/GPL/LGPL. If you do not delete
1523     * the provisions above, a recipient may use your version of this file under
1524     * the terms of any one of the Perl or the MPL/GPL/LGPL.
1525     *
1526     * "MPL/GPL/LGPL":
1527     *
1528     * Version: MPL 1.1/GPL 2.0/LGPL 2.1
1529     *
1530     * The contents of this file are subject to the Mozilla Public License Version
1531     * 1.1 (the "License"); you may not use this file except in compliance with
1532     * the License. You may obtain a copy of the License at
1533     * <http://www.mozilla.org/MPL/>
1534     *
1535     * Software distributed under the License is distributed on an "AS IS" basis,
1536     * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
1537     * for the specific language governing rights and limitations under the
1538     * License.
1539     *
1540     * The Original Code is JSTE code.
1541     *
1542     * The Initial Developer of the Original Code is Wakaba.
1543     * Portions created by the Initial Developer are Copyright (C) 2008
1544     * the Initial Developer. All Rights Reserved.
1545     *
1546     * Contributor(s):
1547     * Wakaba <w@suika.fam.cx>
1548     *
1549     * Alternatively, the contents of this file may be used under the terms of
1550     * either the GNU General Public License Version 2 or later (the "GPL"), or
1551     * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
1552     * in which case the provisions of the GPL or the LGPL are applicable instead
1553     * of those above. If you wish to allow use of your version of this file only
1554     * under the terms of either the GPL or the LGPL, and not to allow others to
1555     * use your version of this file under the terms of the MPL, indicate your
1556     * decision by deleting the provisions above and replace them with the notice
1557     * and other provisions required by the LGPL or the GPL. If you do not delete
1558     * the provisions above, a recipient may use your version of this file under
1559     * the terms of any one of the MPL, the GPL or the LGPL.
1560     *
1561     * ***** END LICENSE BLOCK ***** */

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24