/[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.10 - (hide annotations) (download) (as text)
Mon Jan 26 09:27:22 2009 UTC (16 years, 7 months ago) by wakaba
Branch: MAIN
Changes since 1.9: +206 -49 lines
File MIME type: application/javascript
Implemented jump element (but it's somewhat broken in ie)

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.1 } else if (this.detachEvent) {
106     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     } // setNamedItem
140     });
141    
142    
143 wakaba 1.1 JSTE.List = new JSTE.Class (function (arrayLike) {
144     this.list = arrayLike || [];
145     }, {
146     getLast: function () {
147     if (this.list.length) {
148     return this.list[this.list.length - 1];
149     } else {
150     return null;
151     }
152     }, // getLast
153    
154     forEach: function (code) {
155     var length = this.list.length;
156     for (var i = 0; i < length; i++) {
157     var r = code (this.list[i]);
158     if (r && r.stop) return r.returnValue;
159     }
160     return null;
161     }, // forEach
162 wakaba 1.4
163     numberToInteger: function () {
164     var newList = [];
165     this.forEach (function (item) {
166     if (typeof item === "number") {
167     newList.push (Math.floor (item));
168     } else {
169     newList.push (item);
170     }
171     });
172     return new this.constructor (newList);
173     }, // numberToInteger
174 wakaba 1.1
175     clone: function () {
176     var newList = [];
177     this.forEach (function (item) {
178     newList.push (item);
179     });
180     return new this.constructor (newList);
181     }, // clone
182    
183     grep: function (code) {
184     var newList = [];
185     this.forEach (function (item) {
186     if (code (item)) {
187     newList.push (item);
188     }
189     });
190     return new this.constructor (newList);
191     }, // grep
192     onlyNonNull: function () {
193     return this.grep (function (item) {
194     return item != null; /* Intentionally "!=" */
195     });
196     }, // onlyNonNull
197    
198     getFirstMatch: function (code) {
199     return this.forEach (function (item) {
200     if (code (item)) {
201     return new JSTE.List.Return (item);
202     }
203     });
204     }, // getFirstMatch
205    
206     switchByElementType: function () {
207     var cases = new JSTE.List (arguments);
208     this.forEach (function (n) {
209     cases.forEach (function (c) {
210     if (c.namespaceURI == n.namespaceURI) {
211     return new JSTE.List.Return (c.execute (n));
212     }
213     });
214     });
215     }, // switchByElementType
216    
217 wakaba 1.10 // destructive
218 wakaba 1.1 push: function (item) {
219     this.list.push (item);
220     }, // push
221 wakaba 1.10
222     // destructive
223 wakaba 1.1 pushCloneOfLast: function () {
224     this.list.push (this.getLast ().clone ());
225     }, // pushCloneOfLast
226 wakaba 1.10
227     // destructive
228 wakaba 1.1 append: function (list) {
229     var self = this;
230     list.forEach (function (n) {
231     self.list.push (n);
232     });
233 wakaba 1.10 return this;
234 wakaba 1.1 }, // append
235    
236 wakaba 1.10 // destructive
237 wakaba 1.1 pop: function () {
238     return this.list.pop ();
239     }, // pop
240 wakaba 1.10
241     // destructive
242 wakaba 1.1 remove: function (removedItem) {
243     var length = this.list.length;
244     for (var i = length - 1; i >= 0; --i) {
245     var item = this.list[i];
246     if (item == removedItem) { // Intentionally "=="
247     this.list.splice (i, 1);
248     }
249     }
250     }, // remove
251 wakaba 1.10
252     // destructive
253 wakaba 1.1 clear: function () {
254     this.list.splice (0, this.list.length);
255     } // clear
256    
257     }); // List
258    
259 wakaba 1.10 JSTE.Class.addClassMethods (JSTE.List, {
260     spaceSeparated: function (v) {
261     return new JSTE.List ((v || '').split (JSTE.SpaceChars)).grep (function (v) {
262     return v.length;
263     });
264     }, // spaceSeparated
265    
266     getCommonItems: function (l1, l2, cb, eq) {
267     if (!eq) eq = function (i1, i2) { return i1 == i2 };
268    
269     var common = new JSTE.List;
270    
271     l1 = l1.grep (function (i1) {
272     var hasI1;
273     l2 = l2.grep (function (i2) {
274     if (eq (i1, i2)) {
275     common.push (i1);
276     hasI1 = true;
277     return false;
278     } else {
279     return true;
280     }
281     });
282     return !hasI1;
283     });
284    
285     cb (common, l1, l2);
286     } // getCommonItems
287     }); // List class methods
288    
289 wakaba 1.1 JSTE.List.Return = new JSTE.Class (function (rv) {
290     this.stop = true;
291     this.returnValue = rv;
292     }, {
293    
294     }); // Return
295    
296     JSTE.List.SwitchByLocalName = new JSTE.Class (function (ns, cases, ow) {
297     this.namespaceURI = ns;
298     this.cases = cases;
299     this.otherwise = ow || function (n) { };
300     }, {
301     execute: function (n) {
302     for (var ln in this.cases) {
303     if (JSTE.Element.matchLocalName (n, ln)) {
304     return this.cases[ln] (n);
305     }
306     }
307     return this.otherwise (n);
308     }
309     });
310    
311    
312     if (!JSTE.Node) JSTE.Node = {};
313    
314     JSTE.Class.addClassMethods (JSTE.Node, {
315     querySelector: function (node, selectors) {
316     if (node.querySelector) {
317     var el;
318     try {
319     el = node.querySelector (selectors);
320     } catch (e) {
321     el = null;
322     }
323     return el;
324     } else if (window.uu && uu.css) {
325     if (selectors != "") {
326     /* NOTE: uu.css return all elements for "" or ",xxx" */
327     return uu.css (selectors, node)[0];
328     } else {
329     return null;
330     }
331     } else if (window.Ten && Ten.DOM && Ten.DOM.getElementsBySelector) {
332     return Ten.DOM.getElementsBySelector (selectors)[0];
333     } else {
334     return null;
335     }
336     }, // querySelector
337     querySelectorAll: function (node, selectors) {
338     if (node.querySelectorAll) {
339     var nl;
340     try {
341     nl = node.querySelectorAll (selectors);
342     } catch (e) {
343     nl = null;
344     }
345     return new JSTE.List (nl);
346     } else if (window.uu && uu.css) {
347     if (selectors != "") {
348     /* NOTE: uu.css return all elements for "" or ",xxx" */
349     return new JSTE.List (uu.css (selectors, node));
350     } else {
351     return new JSTE.List;
352     }
353     } else if (window.Ten && Ten.DOM && Ten.DOM.getElementsBySelector) {
354     return new JSTE.List (Ten.DOM.getElementsBySelector (selectors));
355     } else {
356     return new JSTE.List;
357     }
358     } // querySelectorAll
359     });
360    
361     if (!JSTE.Element) JSTE.Element = {};
362    
363     JSTE.Class.addClassMethods (JSTE.Element, {
364     getLocalName: function (el) {
365     var localName = el.localName;
366     if (!localName) {
367     localName = el.nodeName;
368     if (el.prefix) {
369     localName = localName.substring (el.prefix.length + 1);
370     }
371     }
372     return localName;
373     }, // getLocalName
374    
375     match: function (el, ns, ln) {
376     if (el.nodeType !== 1) return false;
377     if (el.namespaceURI !== ns) return false;
378     return JSTE.Element.matchLocalName (el, ln);
379     }, // match
380     matchLocalName: function (el, ln) {
381     var localName = JSTE.Element.getLocalName (el);
382     if (ln instanceof RegExp) {
383     if (!localName.match (ln)) return false;
384     } else {
385     if (localName !== ln) return false;
386     }
387     return true;
388     }, // matchLocalName
389    
390     getChildElement: function (el, ns, ln) {
391     return new JSTE.List (el.childNodes).getFirstMatch (function (item) {
392     return JSTE.Element.match (item, ns, ln);
393     });
394     }, // getChildElement
395     getChildElements: function (el, ns, ln) {
396     return new JSTE.List (el.childNodes).grep (function (item) {
397     return JSTE.Element.match (item, ns, ln);
398     });
399     }, // getChildElements
400    
401     appendText: function (el, s) {
402     return el.appendChild (el.ownerDocument.createTextNode (s));
403     }, // appendText
404    
405     createTemplate: function (doc, node) {
406     var df = doc.createDocumentFragment ();
407     new JSTE.List (node.childNodes).forEach (function (n) {
408     if (n.nodeType == 1) {
409     var c = doc.createElement (JSTE.Element.getLocalName (n));
410     new JSTE.List (c.attributes).forEach (function (n) {
411     c.setAttribute (n.name, n.value);
412     });
413     c.appendChild (JSTE.Element.createTemplate (doc, n));
414     df.appendChild (c);
415     } else if (n.nodeType == 3 || n.nodeType == 4) {
416     df.appendChild (doc.createTextNode (n.data));
417     }
418     });
419     return df;
420     }, // createTemplate
421    
422     hasAttribute: function (el, localName) {
423     if (el.hasAttribute) {
424     return el.hasAttribute (localName);
425     } else {
426     return el.getAttribute (localName) != null;
427     }
428     }, // hasAttribute
429    
430     getClassNames: function (el) {
431     return new JSTE.List (el.className.split (JSTE.SpaceChars));
432     }, // getClassNames
433     addClassName: function (el, newClassName) {
434     el.className = el.className + ' ' + newClassName;
435     }, // deleteClassName
436     deleteClassName: function (el, oldClassName) {
437     var classNames = el.className.split (JSTE.SpaceChars);
438     var newClasses = [];
439     for (var n in classNames) {
440     if (classNames[n] != oldClassName) {
441     newClasses.push (classNames[n]);
442     }
443     }
444     el.className = newClasses.join (' ');
445     }, // deleteClassName
446     replaceClassName: function (el, oldClassName, newClassName) {
447     var classNames = el.className.split (JSTE.SpaceChars);
448     var newClasses = [newClassName];
449     for (var n in classNames) {
450     if (classNames[n] != oldClassName) {
451     newClasses.push (classNames[n]);
452     }
453     }
454     el.className = newClasses.join (' ');
455     }, // replaceClassName
456    
457     getIds: function (el) {
458     return new JSTE.List (el.id != "" ? [el.id] : []);
459 wakaba 1.5 }, // getIds
460    
461     /*
462     NR.js <http://suika.fam.cx/www/css/noderect/NodeRect.js> must be loaded
463     before the invocation.
464     */
465     scroll: function (elements) {
466     if (!JSTE.windowLoaded) {
467     new JSTE.Observer ('load', window, function () {
468     JSTE.Element.scroll (elements);
469     });
470     return;
471     }
472    
473     var top = Infinity;
474     var left = Infinity;
475     var topEl;
476     var leftEl;
477     elements.forEach (function (el) {
478     var rect = NR.Element.getRects (el, window).borderBox;
479     if (rect.top < top) {
480     top = rect.top;
481     topEl = el;
482     }
483     if (rect.left < left) {
484     left = rect.left;
485     leftEl = el;
486     }
487     });
488    
489     if (!leftEl && !topEl) {
490     return;
491     }
492    
493     var doc = (leftEl || topEl).ownerDocument;
494 wakaba 1.1
495 wakaba 1.5 var rect = NR.View.getViewportRects (window, doc).contentBox;
496     if (rect.top <= top && top <= rect.bottom) {
497     top = rect.top;
498     }
499     if (rect.left <= left && left <= rect.right) {
500     left = rect.left;
501     }
502    
503     /*
504     Set scroll* of both <html> and <body> elements, to support all of
505     four browsers and two (or three) rendering modes. This might result
506     in confusing the user if both <html> and <body> elements have their
507     'overflow' properties specified to 'scroll'.
508    
509     Note that this code does not do a good job if the |el| is within an
510     |overflow: scroll| element.
511     */
512     doc.body.scrollTop = top;
513     doc.body.scrollLeft = left;
514     doc.documentElement.scrollTop = top;
515     doc.documentElement.scrollLeft = left;
516     } // scroll
517    
518 wakaba 1.1 }); // JSTE.Element
519    
520 wakaba 1.9 JSTE.XHR = new JSTE.Class (function (url, onsuccess, onerror) {
521     try {
522     this._xhr = new XMLHttpRequest ();
523     } catch (e) {
524     try {
525     this._xhr = new ActiveXObject ('Msxml2.XMLHTTP');
526     } catch (e) {
527     try {
528     this._xhr = new ActiveXObject ('Microsoft.XMLHTTP');
529     } catch (e) {
530     try {
531     this._xhr = new ActiveXObject ('Msxml2.XMLHTTP.4.0');
532     } catch (e) {
533     this._xhr = null;
534     }
535     }
536     }
537     }
538    
539     this._url = url;
540     this._onsuccess = onsuccess || function () { };
541     this._onerror = onerror || function () { };
542     }, {
543     get: function () {
544     if (!this._xhr) return;
545    
546     var self = this;
547     this._xhr.open ('GET', this._url, true);
548     this._xhr.onreadystatechange = function () {
549     self._onreadystatechange ();
550     }; // onreadystatechange
551     this._xhr.send (null);
552     }, // get
553    
554     _onreadystatechange: function () {
555     if (this._xhr.readyState == 4) {
556     if (this.succeeded ()) {
557     this._onsuccess ();
558     } else {
559     this._onerror ();
560     }
561     }
562     }, // _onreadystatechange
563    
564     succeeded: function () {
565     return (this._xhr.status < 400);
566     }, // succeeded
567    
568     getDocument: function () {
569     return this._xhr.responseXML;
570     } // getDocument
571     }); // XHR
572    
573    
574 wakaba 1.1 /* Events: load, close, shown, hidden */
575     JSTE.Message = new JSTE.Class (function (doc, template, commandTarget) {
576     if (!doc) return;
577     this._targetDocument = doc;
578     this._template = template || doc.createDocumentFragment ();
579 wakaba 1.8
580 wakaba 1.1 this._commandTarget = commandTarget;
581 wakaba 1.8 this._availCommands = new JSTE.List;
582    
583 wakaba 1.1 this.hidden = true;
584     this.select = "";
585    
586     var e = new JSTE.Event ('load');
587     this.dispatchEvent (e);
588     }, {
589     render: function () {
590     var self = this;
591     var doc = this._targetDocument;
592    
593     var msgContainer = doc.createElement ('section');
594     msgContainer.appendChild (this._template);
595    
596 wakaba 1.7 if (this._commandTarget.canBack ()) {
597 wakaba 1.8 this._availCommands.push ({name: 'back'});
598 wakaba 1.7 }
599    
600     if (this._commandTarget.canNext ()) {
601 wakaba 1.8 this._availCommands.push ({name: 'next'});
602 wakaba 1.7 }
603 wakaba 1.1
604 wakaba 1.8 this._outermostElement = this._render (msgContainer);
605 wakaba 1.1
606     this.show ();
607     }, // render
608     _render: function (msgContainer, buttonContainer) {
609     var doc = this._targetDocument;
610    
611     var container = doc.createElement ('article');
612    
613     container.appendChild (msgContainer);
614 wakaba 1.8
615     var buttonContainer = this.createCommandButtons ();
616 wakaba 1.1 container.appendChild (buttonContainer);
617 wakaba 1.8
618 wakaba 1.1 doc.documentElement.appendChild (container);
619    
620     return container;
621     }, // _render
622 wakaba 1.8 createCommandButtons: function () {
623     var self = this;
624     var buttonContainer = this._targetDocument.createElement ('menu');
625     this._availCommands.forEach (function (cmd) {
626     var button = new JSTE.Message.Button
627     (cmd.name, self._commandTarget, cmd.name);
628     buttonContainer.appendChild (button.element);
629     });
630     return buttonContainer;
631     }, // createCommandButtons
632    
633 wakaba 1.1 remove: function () {
634     this.hide ();
635    
636     this._remove ();
637    
638     if (this._outermostElement && this._outermostElement.parentNode) {
639     this._outermostElement.parentNode.removeChild (this._outermostElement);
640     }
641    
642     var e = new JSTE.Event ("close");
643     this.dispatchEvent (e);
644     }, // remove
645     _remove: function () {
646    
647     }, // remove
648    
649     show: function () {
650     if (!this.hidden) return;
651     this.hidden = false;
652     if (this._outermostElement) {
653     JSTE.Element.replaceClassName
654     (this._outermostElement, "jste-hidden", "jste-shown");
655     }
656    
657     var e = new JSTE.Event ("shown");
658     this.dispatchEvent (e);
659     }, // show
660     hide: function () {
661     if (this.hidden) return;
662     this.hidden = true;
663     if (this._outermostElement) {
664     JSTE.Element.replaceClassName
665     (this._outermostElement, "jste-shown", "jste-hidden");
666     }
667    
668     var e = new JSTE.Event ("hidden");
669     this.dispatchEvent (e);
670     }, // hide
671    
672     setTimeout: function () {
673     /* TODO: ... */
674    
675     }
676    
677     }); // Message
678    
679 wakaba 1.7 /* TODO: button label text should refer message catalog */
680    
681 wakaba 1.1 JSTE.Message.Button =
682     new JSTE.Class (function (labelText, commandTarget, commandName, commandArgs) {
683 wakaba 1.6 this._labelText = labelText != null ? labelText : "";
684    
685 wakaba 1.1 if (commandTarget && commandTarget instanceof Function) {
686     this._command = commandTarget;
687 wakaba 1.6 this._classNames = new JSTE.List;
688 wakaba 1.1 } else if (commandTarget) {
689     this._command = function () {
690     return commandTarget.executeCommand.apply
691     (commandTarget, [commandName, commandArgs]);
692     };
693 wakaba 1.6 this._classNames = new JSTE.List (['jste-command-' + commandName]);
694 wakaba 1.1 } else {
695     this._command = function () { };
696 wakaba 1.6 this._classNames = new JSTE.List;
697 wakaba 1.1 }
698    
699 wakaba 1.6 this._createElement ();
700 wakaba 1.1 }, {
701 wakaba 1.6 _createElement: function () {
702     try {
703     this.element = document.createElement ('button');
704     this.element.setAttribute ('type', 'button');
705     } catch (e) {
706     this.element = document.createElement ('<button type=button>');
707     }
708     JSTE.Element.appendText (this.element, this._labelText);
709     this.element.className = this._classNames.list.join (' ');
710 wakaba 1.1
711 wakaba 1.6 var self = this;
712     new JSTE.Observer ("click", this.element, function (e) {
713     self._command (e);
714     });
715     } // _createElement
716 wakaba 1.1 }); // Button
717    
718     JSTE.Course = new JSTE.Class (function (doc) {
719     this._targetDocument = doc;
720    
721     this._entryPointsByURL = {};
722     this._entryPointsById = {};
723     this._entryPointsByClassName = {};
724    
725     this._stepsState = new JSTE.List ([new JSTE.Hash]);
726     this._steps = new JSTE.Hash;
727    
728     var nullState = new JSTE.Step;
729     nullState.uid = "";
730     this._steps.setNamedItem (nullState.uid, nullState);
731     this._initialStepUid = nullState.uid;
732     }, {
733 wakaba 1.10 _processStepsContent: function (el, parentSteps) {
734 wakaba 1.1 var self = this;
735     new JSTE.List (el.childNodes).switchByElementType (
736     new JSTE.List.SwitchByLocalName (JSTE.WATNS, {
737 wakaba 1.10 steps: function (n) { self._processStepsElement (n, parentSteps) },
738     step: function (n) { self._processStepElement (n, parentSteps) },
739     jump: function (n) { self._processJumpElement (n, parentSteps) },
740     entry: function (n) { self._processEntryElement (n, parentSteps) }
741 wakaba 1.1 })
742     );
743     }, // _processStepsContent
744 wakaba 1.10 _processStepsElement: function (e, parentSteps) {
745     var steps = new JSTE.Steps ();
746     steps.parentSteps = parentSteps;
747 wakaba 1.1 this._stepsState.pushCloneOfLast ();
748     this._stepsState.getLast ().prevStep = null;
749 wakaba 1.10 this._processStepsContent (e, steps);
750 wakaba 1.1 this._stepsState.pop ();
751     }, // _processStepsElement
752    
753 wakaba 1.10 _processEntryElement: function (e, parentSteps) {
754 wakaba 1.1 if (JSTE.Element.hasAttribute (e, 'url')) {
755     this.setEntryPointByURL
756     (e.getAttribute ('url'), e.getAttribute ('step'));
757     } else if (JSTE.Element.hasAttribute (e, 'root-id')) {
758     this.setEntryPointById
759     (e.getAttribute ('root-id'), e.getAttribute ('step'));
760     } else if (JSTE.Element.hasAttribute (e, 'root-class')) {
761     this.setEntryPointByClassName
762     (e.getAttribute ('root-class'), e.getAttribute ('step'));
763     }
764     }, // _processEntryElement
765     setEntryPointByURL: function (url, stepName) {
766     this._entryPointsByURL[url] = stepName || '';
767     }, // setEntryPointByURL
768     setEntryPointById: function (id, stepName) {
769     this._entryPointsById[id] = stepName || '';
770     }, // setEntryPointById
771     setEntryPointByClassName: function (className, stepName) {
772     this._entryPointsByClassName[className] = stepName || '';
773     }, // setEntryPointByClassName
774     findEntryPoint: function (doc) {
775     var self = this;
776     var td = this._targetDocument;
777     var stepName;
778    
779     var url = doc.URL;
780     if (url) {
781     stepName = self._entryPointsByURL[url];
782     if (stepName) return 'id-' + stepName;
783     }
784    
785     var docEl = td.documentElement;
786     if (docEl) {
787     var docElId = JSTE.Element.getIds (docEl).forEach (function (i) {
788     stepName = self._entryPointsById[i];
789     if (stepName) return new JSTE.List.Return (stepName);
790     });
791     if (stepName) return 'id-' + stepName;
792    
793     stepName = JSTE.Element.getClassNames (docEl).forEach (function (c) {
794     stepName = self._entryPointsByClassName[c];
795     if (stepName) return new JSTE.List.Return (stepName);
796     });
797     if (stepName) return 'id-' + stepName;
798     }
799    
800     var bodyEl = td.body;
801     if (bodyEl) {
802     var bodyElId = JSTE.Element.getIds (bodyEl).forEach (function (i) {
803     stepName = self._entryPointsById[i];
804     if (stepName) return new JSTE.List.Return (stepName);
805     });
806     if (stepName) return 'id-' + stepName;
807    
808     stepName = JSTE.Element.getClassNames (bodyEl).forEach (function (c) {
809     stepName = self._entryPointsByClassName[c];
810     if (stepName) return new JSTE.List.Return (stepName);
811     });
812     if (stepName) return 'id-' + stepName;
813     }
814    
815     return this._initialStepUid;
816     }, // findEntryPoint
817    
818 wakaba 1.10 _processStepElement: function (e, parentSteps) {
819 wakaba 1.1 var step = new JSTE.Step (e.getAttribute ('id'));
820 wakaba 1.10 step.parentSteps = parentSteps;
821 wakaba 1.1 step.setPreviousStep (this._stepsState.getLast ().prevStep);
822     step.select = e.getAttribute ('select') || "";
823     step.nextEvents.append
824 wakaba 1.10 (JSTE.List.spaceSeparated (e.getAttribute ('next-event')));
825 wakaba 1.1 var msgEl = JSTE.Element.getChildElement (e, JSTE.WATNS, 'message');
826     if (msgEl) {
827     var msg = JSTE.Element.createTemplate (this._targetDocument, msgEl);
828     step.setMessageTemplate (msg);
829     }
830     var nextEls = JSTE.Element.getChildElements (e, JSTE.WATNS, 'next-step');
831     if (nextEls.length) {
832     nextEls.forEach (function (nextEl) {
833     step.addNextStep
834     (nextEl.getAttribute ('if'), nextEl.getAttribute ('step'));
835     });
836     this._stepsState.getLast ().prevStep = null;
837     } else {
838     this._stepsState.getLast ().prevStep = step;
839     }
840     /* TODO: @save */
841    
842     this._steps.setNamedItem (step.uid, step);
843     if (!this._initialStepUid) {
844     this._initialStepUid = step.uid;
845     }
846     }, // _processStepElement
847    
848 wakaba 1.10 _processJumpElement: function (e, parentSteps) {
849     var target = e.getAttribute ('target') || '';
850     var evs = JSTE.List.spaceSeparated (e.getAttribute ('event'));
851     var stepName = e.getAttribute ('step') || '';
852    
853     var jump = new JSTE.Jump (target, evs, 'id-' + stepName);
854     if (parentSteps) parentSteps._jumps.push (jump);
855 wakaba 1.1 }, // _processJumpElement
856    
857     getStep: function (uid) {
858     return this._steps.getNamedItem (uid);
859     } // getStep
860     }); // Course
861    
862 wakaba 1.9 JSTE.Class.addClassMethods (JSTE.Course, {
863     createFromDocument: function (doc, targetDoc) {
864     var course = new JSTE.Course (targetDoc);
865     var docEl = doc.documentElement;
866     if (!docEl) return course;
867     if (!JSTE.Element.match (docEl, JSTE.WATNS, 'course')) return course;
868 wakaba 1.10 course._processStepsContent (docEl, null);
869 wakaba 1.9 return course;
870     }, // createFromDocument
871     createFromURL: function (url, targetDoc, onload, onerror) {
872     new JSTE.XHR (url, function () {
873     var course = JSTE.Course.createFromDocument
874     (this.getDocument (), targetDoc);
875     if (onload) onload (course);
876     }, onerror).get ();
877     } // creatFromURL
878     }); // Course class methods
879 wakaba 1.1
880 wakaba 1.10 JSTE.Jump = new JSTE.Class (function (selectors, eventNames, stepUid) {
881     this.selectors = selectors;
882     this.eventNames = eventNames;
883     this.stepUid = stepUid;
884     // this.parentSteps
885     }, {
886     startObserver: function (doc, commandTarget) {
887     var self = this;
888     var observers = new JSTE.List;
889    
890     var onev = function () {
891     commandTarget.gotoStep (self.stepUid);
892     };
893    
894     JSTE.Node.querySelectorAll (doc, this.selectors).forEach
895     (function (el) {
896     self.eventNames.forEach (function (evName) {
897     var ob = new JSTE.Observer (evName, el, onev);
898     ob._stepUid = self.stepUid;
899     observers.push (ob);
900     });
901     });
902    
903     return observers;
904     } // startObserver
905     }); // Jump
906    
907     JSTE.Steps = new JSTE.Class (function () {
908     this._jumps = new JSTE.List;
909     this._jumpHandlers = new JSTE.List;
910     }, {
911     setCurrentStepByUid: function (uid) {
912     this._jumpHandlers.forEach (function (jh) {
913     if (jh._stepUid != uid && jh.disabled) {
914     jh.start ();
915     } else if (jh._stepUid == uid && !jh.disabled) {
916     jh.stop ();
917     }
918     });
919     }, // setCurrentStepByUid
920    
921     installJumps: function (doc, commandTarget) {
922     if (this._jumpHandlers.list.length) return;
923     var self = this;
924     this._jumps.forEach (function (j) {
925     self._jumpHandlers.append (j.startObserver (doc, commandTarget));
926     });
927     }, // installJumps
928    
929     uninstallJumps: function () {
930     this._jumpHandlers.forEach (function (jh) {
931     jh.stop ();
932     });
933     this._jumpHandlers.clear ();
934     } // uninstallJumps
935     }); // Steps
936    
937 wakaba 1.1 JSTE.Step = new JSTE.Class (function (id) {
938     if (id != null && id != '') {
939     this.uid = 'id-' + id;
940     } else {
941     this.uid = 'rand-' + Math.random ();
942     }
943     this._nextSteps = new JSTE.List;
944     this.nextEvents = new JSTE.List;
945     this.select = "";
946     }, {
947     setMessageTemplate: function (msg) {
948     this._messageTemplate = msg;
949     }, // setMessageTemplate
950     hasMessage: function () {
951     return this._messageTemplate ? true : false;
952     }, // hasMessage
953     createMessage: function (msg, doc, commandTarget) {
954     var msg;
955     if (this._messageTemplate) {
956     var clone = JSTE.Element.createTemplate (doc, this._messageTemplate);
957     msg = new msg (doc, clone, commandTarget);
958     } else {
959     msg = new msg (doc, null, commandTarget);
960     }
961     msg.select = this.select;
962     return msg;
963     }, // createMessage
964    
965     addNextStep: function (condition, stepId) {
966     this._nextSteps.push ([condition, stepId]);
967     }, // addNextStep
968     setPreviousStep: function (prevStep) {
969     if (!prevStep) return;
970     if (prevStep._defaultNextStepUid) return;
971     prevStep._defaultNextStepUid = this.uid;
972     }, // setPreviousStep
973    
974     getNextStepUid: function (doc) {
975     var m = this._nextSteps.getFirstMatch (function (item) {
976     var condition = item[0];
977     if (condition) {
978     return JSTE.Node.querySelector (doc, condition) != null;
979     } else {
980     return true;
981     }
982     });
983     if (m) {
984     return 'id-' + m[1];
985     } else if (this._defaultNextStepUid) {
986     return this._defaultNextStepUid;
987     } else {
988     return null;
989     }
990 wakaba 1.10 }, // getNextStepUid
991    
992     getAncestorStepsObjects: function () {
993     var steps = new JSTE.List;
994     var s = this.parentSteps;
995     while (s != null) {
996     steps.push (s);
997     s = s.parentSteps;
998     }
999     return steps;
1000     } // getAncestorStepsObjects
1001 wakaba 1.1 }); // Step
1002    
1003 wakaba 1.3 /* Events: load, error, cssomready */
1004 wakaba 1.9 JSTE.Tutorial = new JSTE.Class (function (course, doc, args) {
1005 wakaba 1.1 this._course = course;
1006     this._targetDocument = doc;
1007     this._messageClass = JSTE.Message;
1008     if (args) {
1009     if (args.messageClass) this._messageClass = args.messageClass;
1010     }
1011    
1012     this._currentMessages = new JSTE.List;
1013     this._currentObservers = new JSTE.List;
1014     this._prevStepUids = new JSTE.List;
1015 wakaba 1.10 this._currentStepsObjects = new JSTE.List;
1016 wakaba 1.1
1017     var stepUid = this._course.findEntryPoint (document);
1018     this._currentStep = this._getStepOrError (stepUid);
1019     if (this._currentStep) {
1020     var e = new JSTE.Event ('load');
1021     this.dispatchEvent (e);
1022    
1023 wakaba 1.3 var self = this;
1024     new JSTE.Observer ('cssomready', this, function () {
1025     self._renderCurrentStep ();
1026     });
1027     this._dispatchCSSOMReadyEvent ();
1028 wakaba 1.1 return this;
1029     } else {
1030     return {};
1031     }
1032     }, {
1033     _getStepOrError: function (stepUid) {
1034     var step = this._course.getStep (stepUid);
1035     if (step) {
1036     return step;
1037     } else {
1038     var e = new JSTE.Event ('error');
1039     e.errorMessage = 'Step not found';
1040     e.errorArguments = [this._currentStepUid];
1041     this.dispatchEvent (e);
1042     return null;
1043     }
1044     }, // _getStepOrError
1045    
1046     _renderCurrentStep: function () {
1047     var self = this;
1048     var step = this._currentStep;
1049    
1050     /* Message */
1051     var msg = step.createMessage
1052     (this._messageClass, this._targetDocument, this);
1053     msg.render ();
1054     this._currentMessages.push (msg);
1055    
1056     /* Next-events */
1057     var selectedNodes = JSTE.Node.querySelectorAll
1058     (this._targetDocument, step.select);
1059     var handler = function () {
1060     self.executeCommand ("next");
1061     };
1062     selectedNodes.forEach (function (node) {
1063     step.nextEvents.forEach (function (eventType) {
1064     self._currentObservers.push
1065     (new JSTE.Observer (eventType, node, handler));
1066     });
1067     });
1068 wakaba 1.10
1069     JSTE.List.getCommonItems (this._currentStepsObjects,
1070     step.getAncestorStepsObjects (),
1071     function (common, onlyInOld, onlyInNew) {
1072     common.forEach (function (item) {
1073     item.setCurrentStepByUid (step.uid);
1074     });
1075     onlyInOld.forEach (function (item) {
1076     item.uninstallJumps ();
1077     });
1078     onlyInNew.forEach (function (item) {
1079     item.installJumps (self._targetDocument, self);
1080     });
1081     self._currentStepsObjects = common.append (onlyInNew);
1082     });
1083 wakaba 1.1 }, // _renderCurrentStep
1084     clearMessages: function () {
1085     this._currentMessages.forEach (function (msg) {
1086     msg.remove ();
1087     });
1088     this._currentMessages.clear ();
1089    
1090     this._currentObservers.forEach (function (ob) {
1091     ob.stop ();
1092     });
1093     this._currentObservers.clear ();
1094     }, // clearMessages
1095 wakaba 1.10 clearStepsHandlers: function () {
1096     this._currentStepsObjects.forEach (function (item) {
1097     item.uninstallJumps ();
1098     });
1099     this._currentStepsObjects.clear ();
1100     }, // clearStepsHandlers
1101 wakaba 1.1
1102     executeCommand: function (commandName, commandArgs) {
1103     if (this[commandName]) {
1104     return this[commandName].apply (this, commandArgs || []);
1105     } else {
1106     var e = new JSTE.Event ('error');
1107     e.errorMessage = 'Command not found';
1108     e.errorArguments = [commandName];
1109     return null;
1110     }
1111     }, // executeCommand
1112 wakaba 1.7 canExecuteCommand: function (commandName, commandArgs) {
1113     if (this[commandName]) {
1114     var can = this['can' + commandName.substring (0, 1).toUpperCase ()
1115     + commandName.substring (1)];
1116     if (can) {
1117     return can.apply (this, arguments);
1118     } else {
1119     return true;
1120     }
1121     } else {
1122     return false;
1123     }
1124     }, // canExecuteCommand
1125 wakaba 1.1
1126     startTutorial: function () {
1127     this.resetVisited ();
1128    
1129     }, // startTutorial
1130     continueTutorial: function () {
1131    
1132     }, // continueTutorial
1133    
1134     saveVisited: function () {
1135    
1136     }, // saveVisited
1137     resetVisited: function () {
1138    
1139     }, // resetVisited
1140    
1141     back: function () {
1142     var prevStepUid = this._prevStepUids.pop ();
1143     var prevStep = this._getStepOrError (prevStepUid);
1144     if (prevStep) {
1145     this.clearMessages ();
1146     this._currentStep = prevStep;
1147     this._renderCurrentStep ();
1148     }
1149     }, // back
1150 wakaba 1.7 canBack: function () {
1151     return this._prevStepUids.list.length > 0;
1152     }, // canBack
1153 wakaba 1.1 next: function () {
1154     var nextStepUid = this._currentStep.getNextStepUid (this._targetDocument);
1155     var nextStep = this._getStepOrError (nextStepUid);
1156     if (nextStep) {
1157     this._prevStepUids.push (this._currentStep.uid);
1158     this.clearMessages ();
1159     this._currentStep = nextStep;
1160     this._renderCurrentStep ();
1161     }
1162 wakaba 1.3 }, // next
1163 wakaba 1.7 canNext: function () {
1164     return this._currentStep.getNextStepUid (this._targetDocument) != null;
1165     }, // canNext
1166 wakaba 1.10 gotoStep: function (uid) {
1167     var nextStep = this._getStepOrError (uid);
1168     if (nextStep) {
1169     this._prevStepUids.push (this._currentStep.uid);
1170     this.clearMessages ();
1171     this._currentStep = nextStep;
1172     this._renderCurrentStep ();
1173     }
1174     }, // gotoStep
1175 wakaba 1.3
1176     // <http://twitter.com/waka/status/1129513097>
1177     _dispatchCSSOMReadyEvent: function () {
1178     var self = this;
1179     var e = new JSTE.Event ('cssomready');
1180     if (window.opera && document.readyState != 'complete') {
1181     new JSTE.Observer ('readystatechange', document, function () {
1182     if (document.readyState == 'complete') {
1183     self.dispatchEvent (e);
1184     }
1185     });
1186     } else {
1187     this.dispatchEvent (e);
1188     }
1189     } // dispatchCSSOMReadyEvent
1190    
1191 wakaba 1.1 }); // Tutorial
1192 wakaba 1.9
1193     JSTE.Class.addClassMethods (JSTE.Tutorial, {
1194     createFromURL: function (url, doc, args) {
1195     JSTE.Course.createFromURL (url, doc, function (course) {
1196     new JSTE.Tutorial (course, doc, args);
1197     });
1198     } // createFromURL
1199     }); // Tutorial class methods
1200    
1201    
1202 wakaba 1.5
1203     if (JSTE.onLoadFunctions) {
1204     new JSTE.List (JSTE.onLoadFunctions).forEach (function (code) {
1205     code ();
1206     });
1207     }
1208    
1209     if (JSTE.isDynamicallyLoaded) {
1210     JSTE.windowLoaded = true;
1211     }
1212 wakaba 1.2
1213     /* ***** BEGIN LICENSE BLOCK *****
1214     * Copyright 2008-2009 Wakaba <w@suika.fam.cx>. All rights reserved.
1215     *
1216     * This program is free software; you can redistribute it and/or
1217     * modify it under the same terms as Perl itself.
1218     *
1219     * Alternatively, the contents of this file may be used
1220     * under the following terms (the "MPL/GPL/LGPL"),
1221     * in which case the provisions of the MPL/GPL/LGPL are applicable instead
1222     * of those above. If you wish to allow use of your version of this file only
1223     * under the terms of the MPL/GPL/LGPL, and not to allow others to
1224     * use your version of this file under the terms of the Perl, indicate your
1225     * decision by deleting the provisions above and replace them with the notice
1226     * and other provisions required by the MPL/GPL/LGPL. If you do not delete
1227     * the provisions above, a recipient may use your version of this file under
1228     * the terms of any one of the Perl or the MPL/GPL/LGPL.
1229     *
1230     * "MPL/GPL/LGPL":
1231     *
1232     * Version: MPL 1.1/GPL 2.0/LGPL 2.1
1233     *
1234     * The contents of this file are subject to the Mozilla Public License Version
1235     * 1.1 (the "License"); you may not use this file except in compliance with
1236     * the License. You may obtain a copy of the License at
1237     * <http://www.mozilla.org/MPL/>
1238     *
1239     * Software distributed under the License is distributed on an "AS IS" basis,
1240     * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
1241     * for the specific language governing rights and limitations under the
1242     * License.
1243     *
1244     * The Original Code is JSTE code.
1245     *
1246     * The Initial Developer of the Original Code is Wakaba.
1247     * Portions created by the Initial Developer are Copyright (C) 2008
1248     * the Initial Developer. All Rights Reserved.
1249     *
1250     * Contributor(s):
1251     * Wakaba <w@suika.fam.cx>
1252     *
1253     * Alternatively, the contents of this file may be used under the terms of
1254     * either the GNU General Public License Version 2 or later (the "GPL"), or
1255     * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
1256     * in which case the provisions of the GPL or the LGPL are applicable instead
1257     * of those above. If you wish to allow use of your version of this file only
1258     * under the terms of either the GPL or the LGPL, and not to allow others to
1259     * use your version of this file under the terms of the MPL, indicate your
1260     * decision by deleting the provisions above and replace them with the notice
1261     * and other provisions required by the LGPL or the GPL. If you do not delete
1262     * the provisions above, a recipient may use your version of this file under
1263     * the terms of any one of the MPL, the GPL or the LGPL.
1264     *
1265     * ***** END LICENSE BLOCK ***** */

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24