git.fiddlerwoaroof.com
Browse code

initial working sample

fiddlerwoaroof authored on 26/06/2016 06:08:20
Showing 6 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,50 @@
1
+# JIM: The Javascript Interface Manager
2
+
3
+Simple implementation of commands and presentation types mimicing some of the
4
+nice features of CLIM.
5
+
6
+The idea is that you assign types to the various things displayed on the page
7
+and then define commands that operate on those types.
8
+
9
+When you execute a command, the page activates the types corresponding to the
10
+various arguments of the command in order.
11
+
12
+## API
13
+
14
+To make a new presentation type, either call `PresentationType(name)` directly
15
+or call it via `new`.  `name` specifies the new type's name, which is used to
16
+determine whether something is of that type.  This is also used as a element
17
+class name in order to determine which elements are of this type.
18
+
19
+```
20
+var myType = new PresentationType('mytype');
21
+```
22
+
23
+Next, the presentation type must be bound to the corresponding tags. This is
24
+done using `bindTags()` which returns the presentation type to allow for
25
+chaining. This function iterates through the elements having `this.name` as one
26
+of their classes and then attaches a presentation type to them using `data-bind`
27
+to determine which events to bind (defaults to 'click', if not specified) and
28
+uses either the value of the `value` attribute or of the `data-value` attribute,
29
+as the "value" of the new instance of the presentation stype, if either is
30
+specified. Otherwise, it defaults to the textContent of the tag.
31
+
32
+```
33
+myType.bindTags(); // bind the corresponding tags, returns myType
34
+```
35
+
36
+makeTag has any of these signatures:
37
+
38
+```
39
+makeTag(value, tagName)
40
+makeTag(value, tagName, content)
41
+makeTag(value, tagName, bindTypes, content)
42
+```
43
+
44
+`value` refers to the value associated with the object.  `content` represents
45
+the text displayed that represents this value, it defaults to value if `content`
46
+is not specified.  `tagName` specifies the kind of tag to be created.
47
+`bindTypes` is an Array of strings indicating which events the presentation type
48
+should handle.   If this is specified, you should either add functions with the
49
+corresponding name to the type or setup receivers that can handle the event
50
+using `addReceiver(receiver)`.
0 51
new file mode 100644
... ...
@@ -0,0 +1,57 @@
1
+/* Styles go here */
2
+h1 > li {
3
+  display: inline;
4
+}
5
+
6
+li.activated {
7
+  color: blue;
8
+  text-decoration: underline;
9
+  cursor: pointer;
10
+}
11
+
12
+.current {
13
+  font-weight: bold;
14
+}
15
+
16
+.listbox {
17
+  width: 32%;
18
+  float: left;
19
+}
20
+
21
+button {
22
+  clear: both;
23
+}
24
+
25
+.listbox:first-child {
26
+  clear: left;
27
+}
28
+
29
+.listbox:last-child {
30
+  float: right;
31
+}
32
+
33
+ul,li {
34
+  list-style: none;
35
+  margin: 0px;
36
+  padding: 0px;
37
+}
38
+
39
+.subject {
40
+  color: #880000;
41
+}
42
+
43
+.verb {
44
+  color: #888800;
45
+}
46
+
47
+.object {
48
+  color: #008800;
49
+}
50
+
51
+#sentence li {
52
+  display: inline;
53
+}
54
+
55
+#sentence li + li {
56
+  margin-left: 0.5em;
57
+}
0 58
new file mode 100644
... ...
@@ -0,0 +1,78 @@
1
+<!DOCTYPE html>
2
+<html>
3
+
4
+  <head>
5
+	 <link rel="stylesheet" href="css/style.css">
6
+	 <script src="lib/PresentationType.js"></script>
7
+	 <script src="lib/Command.js"></script>
8
+	 <script src="js/script.js" defer></script>
9
+  </head>
10
+
11
+  <body>
12
+	 <h1><li class="name" data-bind="click">JIM</li>: The Javsascript Interface Manager!</h1>
13
+	 <div>
14
+		<button id="randomSentence">Random Sentence!</button>
15
+		<button id="pickSentence">Pick a Sentence</button>
16
+	 </div>
17
+	 <!--
18
+	 <div class="listbox">
19
+		<h2>Names</h2>
20
+		<ul id="names">
21
+		  <li class="name">Mary</li>
22
+		  <li class="name">Jane</li>
23
+		  <li class="name">Anne</li>
24
+		  <li class="name">John</li>
25
+		</ul>
26
+	 </div>
27
+	 <div class="listbox">
28
+		<h2>Occupations</h2>
29
+		<ul id="occupations">
30
+		  <li class="occupation">builder</li>
31
+		  <li class="occupation">interface builder</li>
32
+		</ul>
33
+	 </div>
34
+	 -->
35
+
36
+	 <div id="sentence"></div>
37
+	 <div class="listbox">
38
+		<h2>Subject</h2>
39
+		<ul id="subjects">
40
+		  <li class="subject">Combat form</li>
41
+		  <li class="subject">Stock</li>
42
+		  <li class="subject">Application software</li>
43
+		  <li class="subject">The old st paul's stations</li>
44
+		  <li class="subject">The cat</li>
45
+		  <li class="subject">The dog</li>
46
+		  <li class="subject">Bob, the tomato,</li>
47
+		  <li class="subject">Midas</li>
48
+		</ul>
49
+	 </div>
50
+	 <div class="listbox">
51
+		<h2>Verb</h2>
52
+		<ul id="verbs">
53
+		  <li class="verb">is four-armed monitor lizard analog with</li>
54
+		  <li class="verb">is drawn from</li>
55
+		  <li class="verb">needs</li>
56
+		  <li class="verb">were acquired in</li>
57
+		  <li class="verb">shook hands with</li>
58
+		  <li class="verb">sat on</li>
59
+		  <li class="verb">barked at</li>
60
+		  <li class="verb">chased</li>
61
+		</ul>
62
+	 </div>
63
+	 <div class="listbox">
64
+		<h2>Object</h2>
65
+		<ul id="objects">
66
+		  <li class="object">Medusa.<li>
67
+		  <li class="object">the mat.</li>
68
+		  <li class="object">the cat.</li>
69
+		  <li class="object">Larry, the cucumber.</li>
70
+		  <li class="object">black skin.</li>
71
+		  <li class="object">european bloodline producers.</li>
72
+		  <li class="object">powerful processor.</li>
73
+		  <li class="object">1995 in poor state.</li>
74
+		</ul>
75
+	 </div>
76
+  </body>
77
+
78
+</html>
0 79
new file mode 100644
... ...
@@ -0,0 +1,108 @@
1
+// Code goes here
2
+
3
+subjectType = PresentationType('subject').bindTags();
4
+verbType = PresentationType('verb').bindTags();
5
+objectType = PresentationType('object').bindTags();
6
+
7
+pickSentence = new Command(['subject', 'verb', 'object']);
8
+pickSentence.execute = function(subject, verb, object) {
9
+  var parent = document.querySelector('#sentence');
10
+
11
+  if (parent.hasChildNodes()) {
12
+    document.querySelector('#subjects').appendChild(
13
+      parent.querySelector(subjectType.selector)
14
+    );
15
+
16
+    document.querySelector('#verbs').appendChild(
17
+      parent.querySelector(verbType.selector)
18
+    );
19
+
20
+    document.querySelector('#objects').appendChild(
21
+      parent.querySelector(objectType.selector)
22
+    )
23
+  }
24
+
25
+  [subject,verb,object].forEach(function (el) {
26
+    parent.appendChild(el.tag);
27
+  });
28
+
29
+}
30
+
31
+randomSentence = new Command([]);
32
+randomSentence.execute = function() {
33
+ var subjects = subjectType.selectAll();
34
+ var verbs = verbType.selectAll();
35
+ var objects = objectType.selectAll();
36
+
37
+ pickSentence.execute(
38
+  subjects[Math.floor(Math.random()*3)],
39
+  verbs[Math.floor(Math.random()*3)],
40
+  objects[Math.floor(Math.random()*3)]
41
+ );
42
+}
43
+
44
+document.querySelector('#pickSentence').addEventListener('click', pickSentence, false);
45
+document.querySelector('#randomSentence').addEventListener('click', randomSentence, false);
46
+
47
+/*
48
+nameType = PresentationType('name');
49
+nameType.bindTags();
50
+
51
+occupationType = PresentationType('occupation').bindTags();
52
+
53
+names = ['Bill', 'Bob', 'John'];
54
+
55
+names.forEach(function(name) {
56
+  var theTag = nameType.makeTag(
57
+    name.toLowerCase(),
58
+    'div',
59
+    ['click'],
60
+    name
61
+  );
62
+  document.querySelector('#names').appendChild(theTag.tag);
63
+});
64
+
65
+occupations = ['carpenter', 'weaver', 'fisher'];
66
+
67
+occupations.forEach(function(occupation) {
68
+  var theTag = occupationType.makeTag(
69
+    occupation.toLowerCase(),
70
+    'div',
71
+    ['click'],
72
+    occupation
73
+  );
74
+  
75
+  document.querySelector('#occupations').appendChild(theTag.tag);
76
+});
77
+
78
+getPhrase = new Command(['name', 'occupation']);
79
+
80
+getPhrase.execute = function (name, occupation) {
81
+  document.querySelectorAll('.current').forEach(function(el) {
82
+    el.classList.remove('current');
83
+  });
84
+
85
+  name.tag.classList.add('current');
86
+  occupation.tag.classList.add('current');
87
+  
88
+  var heading = document.querySelector('h1');
89
+  var oldname = heading.querySelector(nameType.selector);
90
+  var oldoccupation = heading.querySelector(occupationType.selector);
91
+  
92
+  heading.textContent = '';
93
+  
94
+  if (oldname !== null) {
95
+    document.querySelector('#names').appendChild(oldname);
96
+  }
97
+  heading.appendChild(name.tag);
98
+
99
+  heading.appendChild(document.createTextNode(' the '));
100
+
101
+  if (oldoccupation !== null) {
102
+    document.querySelector('#occupations').appendChild(oldoccupation);
103
+  }
104
+  heading.appendChild(occupation.tag);
105
+
106
+};
107
+*/
108
+
0 109
new file mode 100644
... ...
@@ -0,0 +1,114 @@
1
+function PresentationTypeError(msg, expectedType, actualVal) {
2
+  if (this instanceof TypeError) {
3
+    return new TypeError(msg, expectedType, actualVal);
4
+  } else {
5
+    this.msg = msg;
6
+    this.expectedType = expectedType;
7
+    this.actualVal = actualVal;
8
+  }
9
+
10
+
11
+}
12
+
13
+CursorAbort = {};
14
+Command = (function() {
15
+
16
+  function TypeCursor(types) {
17
+    if (! (this instanceof TypeCursor) ) {
18
+      return new TypeCursor(types);
19
+    }
20
+
21
+    this.types = types;
22
+    this.boundArgs = new Array(types.length);
23
+    this.currentIdx = 0;
24
+
25
+    var self = this;
26
+    this.promise = new Promise(function (resolve, reject) {
27
+      self.resolve = resolve;
28
+      self.reject = reject;
29
+    });
30
+  }
31
+
32
+  TypeCursor.prototype = {
33
+    abort() {
34
+      var currentType = this.types[this.currentIdx-1];
35
+      if (currentType !== undefined) {
36
+        currentType.deactivateAll();
37
+        currentType.removeReceiver(this);
38
+      }
39
+
40
+      //this.reject(CursorAbort);
41
+    },
42
+
43
+    activate() {
44
+      if (this.currentIdx >= this.types.length) {
45
+        this.resolve.bind(this.promise)(this.boundArgs);
46
+      } else {
47
+        var currentType = this.types[this.currentIdx++];
48
+        currentType.addReceiver(this);
49
+        currentType.activateAll();
50
+      }
51
+      return this.promise;
52
+    },
53
+
54
+    receive(event, presentationType, arg) {
55
+      var args = Array.prototype.splice(arguments);
56
+      args.shift();
57
+      presentationType.removeReceiver(this);
58
+      presentationType.deactivateAll();
59
+
60
+      if (presentationType.validate.apply(args)) {
61
+        this.boundArgs[this.currentIdx-1] = presentationType;
62
+        this.activate();
63
+      } else {
64
+        this.reject(args);
65
+      }
66
+    }
67
+  };
68
+
69
+  function Command(types) {
70
+    if (! (this instanceof Command)) {
71
+      throw "do \"new Command()\"";
72
+    }
73
+
74
+    this.types = types.map(function (type) {
75
+      // Lookup or create a presentation type
76
+      return PresentationType(type);
77
+    });
78
+
79
+    this.nargs = this.types.length;
80
+  }
81
+
82
+  Command.prototype = {
83
+    run(args) {
84
+      if (args === undefined) {
85
+        args = [];
86
+      } else if (arguments.length > 1 || (! args instanceof Array)) {
87
+        args = Array.prototype.slice.call(arguments);
88
+      }
89
+
90
+      for (var idx = 0; idx < args.length; idx++) {
91
+        var type = this.types[idx];
92
+        var arg = args[idx];
93
+        console.log(this.types[idx], args[idx]);
94
+        if ( type.name !== arg.name ) {
95
+          throw new PresentationTypeError('wrong type', this.types[idx], args[idx]);
96
+        }
97
+      }
98
+      this.execute.apply(this, args);
99
+    },
100
+
101
+    handleEvent(theEvent) {
102
+      if (this.cursor !== undefined) {
103
+        this.cursor.abort();
104
+      }
105
+
106
+      this.cursor = TypeCursor(this.types);
107
+      this.cursor.activate().then(this.run.bind(this));
108
+
109
+      return true;
110
+    }
111
+  };
112
+
113
+  return Command;
114
+})();
0 115
new file mode 100644
... ...
@@ -0,0 +1,181 @@
1
+PresentationType = (function () {
2
+  var presentationTypes = {};
3
+
4
+  // TODO: think through selector better: right now, I think we assume that it
5
+  //       is a class selector and things will break if it isn't we should,
6
+  //       however, probably let it be more general.
7
+  function PresentationType(name, selector) {
8
+    if (this instanceof PresentationType) {
9
+      this.name = name;
10
+      if (selector === undefined) {
11
+        selector = '.'+this.name;
12
+      }
13
+      this.selector = selector;
14
+    } else {
15
+      var result = presentationTypes[name];
16
+      if (result === undefined) {
17
+        result = new PresentationType(name);
18
+        presentationTypes[result.name] = result;
19
+        result.receivers = [];
20
+
21
+      }
22
+      return result;
23
+    }
24
+  }
25
+
26
+  PresentationType.prototype = {
27
+    presentationsTypes: presentationTypes,
28
+
29
+    addReceiver(receiver) {
30
+      this.receivers.push(receiver);
31
+      return this.receivers.length - 1;
32
+    },
33
+
34
+    removeReceiver(el) {
35
+      var elIdx = this.receivers.indexOf(el);
36
+      var result = elIdx !== -1;
37
+      if (result) {
38
+        this.receivers.splice(elIdx,1);
39
+      }
40
+      return result;
41
+    },
42
+
43
+    notifyReceivers(theEvent) {
44
+      var outerArgs = Array.prototype.slice.call(arguments);
45
+      outerArgs.unshift(theEvent, this, this.value);
46
+
47
+      this.receivers.forEach((function (theReceiver) {
48
+        theReceiver.receive.apply(theReceiver, outerArgs);
49
+      }));
50
+    },
51
+
52
+    selectAllTags() {
53
+      return document.querySelectorAll(this.selector);
54
+    },
55
+
56
+    selectAll() {
57
+      var allTags = this.selectAllTags();
58
+      return Array.prototype.map.call(allTags, function (el) {
59
+        return el.ptObject;
60
+      });
61
+    },
62
+
63
+    bindTags() {
64
+      var els = this.selectAllTags();
65
+
66
+      els.forEach(function (theEl) {
67
+        var content = theEl.textContent;
68
+        var value = theEl.value;
69
+
70
+
71
+        var bindTypes = theEl.dataset.bind;
72
+        if (bindTypes !== undefined) {
73
+          bindTypes = bindTypes.split(',');
74
+        } else {
75
+          bindTypes = ['click'];
76
+        }
77
+
78
+        console.log('bt',bindTypes);
79
+
80
+        if (! theEl.hasAttribute('value') ) {
81
+          var tmpVal = theEl.dataset.value;
82
+          value = tmpVal === undefined ? content : theEl.dataset.value;
83
+        }
84
+
85
+        this.wrapTag(theEl, value, bindTypes, content);
86
+      }, this);
87
+
88
+      return this;
89
+    },
90
+
91
+    wrapTag(tag, value, bindTypes, content) {
92
+      var result = Object.assign(Object.create(this), {
93
+        tag: tag,
94
+        value: value,
95
+        display: content,
96
+      });
97
+      tag.ptObject = result;
98
+
99
+      bindTypes.forEach(function (event) {
100
+        console.log(event);
101
+        tag.addEventListener(event, result, false);
102
+      });
103
+
104
+      if (result.validate()) {
105
+        return result;
106
+      } else {
107
+        this.doError(result);
108
+      }
109
+    },
110
+
111
+    makeTag(value, tagName, bindTypes, content) {
112
+      var newTag = document.createElement(tagName);
113
+
114
+      newTag.classList.add(this.name);
115
+
116
+      if (bindTypes === undefined) {
117
+        bindTypes = [];
118
+      } else if (!(bindTypes instanceof Array)) {
119
+        content = bindTypes;
120
+        bindTypes = [];
121
+      }
122
+
123
+      if (content === undefined) {
124
+        content = value;
125
+      }
126
+
127
+      newTag.textContent = content;
128
+      return this.wrapTag(newTag, value, bindTypes, content);
129
+    },
130
+
131
+    handleEvent(theEvent) {
132
+      console.log('handling: ',theEvent);
133
+      var handler = this[theEvent.type];
134
+      var result = true;
135
+      if (handler !== undefined) {
136
+        result = handler.bind(this)(theEvent);
137
+      }
138
+      this.notifyReceivers(theEvent);
139
+
140
+      return result;
141
+    },
142
+
143
+    doError(object) {
144
+      return;
145
+    },
146
+
147
+    validate() {
148
+      return true;
149
+    },
150
+
151
+    toggleAll() {
152
+      var els = this.selectAllTags();
153
+
154
+      els.forEach(function (el) {
155
+        el.classList.toggle('activated');
156
+      });
157
+    },
158
+
159
+    deactivateAll() {
160
+      var els = this.selectAllTags();
161
+
162
+      els.forEach(function (el) {
163
+        el.classList.remove('activated');
164
+      });
165
+    },
166
+
167
+    activateAll() {
168
+      var els = this.selectAllTags();
169
+
170
+      els.forEach(function (el) {
171
+        el.classList.add('activated');
172
+      });
173
+
174
+      return els;
175
+    },
176
+
177
+
178
+  };
179
+
180
+  return PresentationType;
181
+})();