UNPKG

14.1 kBJavaScriptView Raw
1/*!
2* Srcerer, bubbles: client- side for srcerer
3* Copyright(c) 2010-2017 Jesse Tijnagel (Guilala)
4* MIT Licensed
5*/
6
7(function(){
8 "use strict";
9 var bubbles = {
10 // blob graphics require svg capable client
11 svgCapable: !!document.createElementNS && !!document.createElementNS("http://www.w3.org/2000/svg", "svg").createSVGRect,
12
13 // xhr request
14 xhr: function(url, done, post) {
15 var ctx = this, req = new XMLHttpRequest() || new ActiveXObject("Microsoft.XMLHTTP");
16 req.onreadystatechange = function () {
17 if(req.readyState === 4) {
18 done.call(ctx, req.responseText);
19 }
20 };
21
22 if (post) {
23 post = JSON.stringify(post);
24 req.open("POST", url, true);
25 req.setRequestHeader("Content-type", "application/json");
26 } else {
27 req.open("GET", url, true);
28 }
29
30 req.setRequestHeader("X-Requested-With", "XMLHttpRequest");
31 req.send(post || "");
32 },
33
34 // xhr request for json data
35 data: function(url, done, post){
36 bubbles.xhr(url, function(dataStr){
37 try {
38 done(JSON.parse(dataStr));
39 } catch(e) {
40 done({
41 error: e
42 });
43 }
44 }, post);
45 },
46
47 add: function () {
48 var org = arguments[0];
49 for(var t=1; t<arguments.length; t++){
50 var add = arguments[t];
51 for(var k in add) if(add.hasOwnProperty(k)) {
52 org[k] = add[k];
53 }
54 }
55 return org;
56 },
57
58 // wrap array when isn't array
59 arrayify: function (obj) {
60 if (Array.isArray(obj)) {
61 return obj;
62 } else {
63 return [obj];
64 }
65 },
66
67 // call async functions sequentially, finish when they are done
68 que: function (context) {
69 var self = this;
70 var row = [];
71
72 var next = function() {
73 if (row.length) {
74 new Promise(function(resolve, reject) {
75 row.shift().call(context || self, resolve, reject);
76 }).then(next);
77 }
78 };
79
80 this.add = function(step) {
81 if (step && step.call) {
82 row.push(step);
83 } else {
84 console.error("bubbles.que, add: step not a function", step);
85 }
86
87 return self;
88 };
89
90 this.then = function(action) {
91 self.add(action);
92 next();
93
94 return self;
95 };
96
97 return self;
98 },
99
100 // call async functions simultaneously, finish when they are done
101 all: function (context) {
102 var self = this;
103 var list = [];
104
105 this.add = function(action) {
106 if (action && action.call) {
107 list.push(action);
108 } else {
109 console.error("bubbles.all, add: action not a function", action);
110 }
111
112 return self;
113 };
114
115 this.then = function(finalAction) {
116 new Promise(function(resolve) {
117 var count = list.length;
118 if(count) {
119 list.forEach(function(action) {
120 action.call(context || self, function() {
121 // individual resolve
122 count--;
123 // total resolve
124 if (count === 0) {
125 resolve();
126 }
127 });
128 });
129 } else resolve();
130 }).then(function() {
131 if(finalAction && finalAction.call) {
132 finalAction.call(context || self);
133 }
134 });
135
136 return self;
137 };
138
139 return self;
140 },
141
142 elmApi: [
143 // dom target
144 {
145 name: "tg",
146 act: function () {
147 this.domElm = document.createComment(this.elm.tg);
148 this.ctx.tg[this.elm.tg] = this.domElm;
149 }
150 },
151
152 // svg
153 {
154 name: "svg",
155 act: function () {
156 if(bubbles.svgCapable && bubbles.imgCache.hasOwnProperty(this.elm.svg)){
157 this.domElm = bubbles.imgCache[this.elm.svg].cloneNode(true);
158 this.domElm.removeAttribute("id");
159 var className = this.domElm.getAttribute("class");
160 this.domElm.setAttribute("class", className + " " + this.elm.svg);
161 } else {
162 console.error("Sprite", this.elm.svg);
163 return;
164 }
165 }
166 },
167
168 // tag
169 {
170 name: "tag",
171 act: function () {
172 this.domElm = document.createElement(this.elm.tag);
173 }
174 },
175
176 // ref
177 {
178 name: "dom",
179 act: function () {
180 this.ctx.dom[this.elm.dom] = this.domElm;
181 }
182 },
183
184 // str
185 {
186 name: "str",
187 act: function () {
188 this.domElm.classList.add("pre");
189 this.domElm.appendChild(document.createTextNode(this.elm.str));
190 }
191 },
192
193 // html
194 {
195 name: "html",
196 act: function () {
197 this.domElm.innerHTML = this.ctx[this.elm.html];
198 }
199 },
200
201 // css class names
202 {
203 name: "css",
204 act: function () {
205 bubbles.arrayify(this.elm.css).forEach(function (css) {
206 this.domElm.classList.add(css);
207 }, this);
208 }
209 },
210
211 // custom styles
212 {
213 name: "style",
214 act: function () {
215 bubbles.add(this.domElm.style, this.elm.style);
216 }
217 },
218
219 // custom attributes
220 {
221 name: "att",
222 act: function () {
223 for (var key in this.elm.att) {
224 this.domElm.setAttribute(key, this.elm.att[key]);
225 }
226 }
227 },
228
229 // child blob
230 {
231 name: "blob",
232 act: function () {
233 bubbles.arrayify(this.elm.blob).forEach(function (blobSrc) {
234 if(blobSrc.name) {
235 // insert
236 bubbles.blob(blobSrc.name, this.domElm, blobSrc, function (ctx) {
237 if(blobSrc.ready && blobSrc.ready.call) {
238 blobSrc.ready.call(ctx, blobSrc);
239 }
240 });
241 }
242 }, this);
243 }
244 },
245
246 // child elm
247 {
248 name: "elm",
249 act: function () {
250 bubbles.elm(this.elm.elm, this.domElm, this.ctx);
251 }
252 }
253 ],
254
255 // dom element parser
256 elm: function (elms, tg, ctx) {
257 var newElms = [];
258 bubbles.arrayify(elms).forEach(function (elm) {
259 var proc = {
260 domElm: undefined,
261 ctx: ctx,
262 elm: elm,
263 };
264
265 // prepare type
266 if(!elm.tg && !elm.svg && !elm.tag) {
267 elm.tag = "div";
268 }
269
270 // run all elm api
271 bubbles.elmApi.forEach(function(elmApi){
272 if(proc.elm.hasOwnProperty(elmApi.name)) {
273 elmApi.act.call(proc);
274 }
275 });
276
277 // append to parent
278 if(tg.nodeType === 8 && tg.parentNode) {
279 tg.parentNode.insertBefore(proc.domElm, tg);
280 } else {
281 tg.appendChild(proc.domElm);
282 }
283
284 newElms.push(proc);
285 });
286
287 return newElms;
288 },
289
290 // mvc
291 mvc: function(tg, mvc, inherit, ready) {
292 // inheritance
293 var ctx = {
294 bubbles: bubbles,
295 mvc: mvc,
296 tg: {},
297 dom: {}
298 };
299
300 var que = new bubbles.que(ctx);
301
302 var rmDom = function(tgs){
303 var parent = tg.nodeType === 8 ? tg.parentNode : tg;
304 tgs.forEach(function (child) {
305 if (child.domElm.parentNode === parent){
306 parent.removeChild(child.domElm);
307 }
308 });
309 };
310
311 // configure model
312 if(mvc.model && mvc.model.call) {
313 que.add(function(next){
314 mvc.model.call(
315 this,
316 next,
317 ctx,
318 inherit
319 );
320 });
321 }
322
323 // configure view
324 if(mvc.view && mvc.view.call) {
325 que.add(function(next){
326 var root;
327
328 mvc.view.call(
329 this,
330 next,
331 function(viewElm){
332 root = bubbles.elm(viewElm, tg, ctx);
333 },
334 ctx
335 );
336
337 // create view destroyer
338 var destroyer = mvc.destroy;
339 mvc.destroy = function(){
340 if(root && root.length){
341 if (destroyer && destroyer.call) destroyer.call(ctx, function(){
342 rmDom(root);
343 });
344 else rmDom(root);
345 }
346 };
347 });
348 }
349
350 // configure controller
351 if(mvc.controller && mvc.controller.call) {
352 que.add(function(next){
353 mvc.controller.call(
354 this,
355 next,
356 ctx
357 );
358 });
359 }
360
361 // run model, then view, then controler, then call back
362 que.then(function(){
363 if(ready && ready.call) ready.call(ctx);
364 });
365 },
366
367 loadBinScript: function(name) {
368 var elm = document.createElement("script");
369 var url = "./bin/" + name + ".js";
370 var payload;
371 var resolver;
372
373 if(!window.bubbleSet) window.bubbleSet = {};
374
375 window.bubbleSet[name] = function(construct) {
376 resolver(construct);
377 delete window.bubbleSet[name];
378 document.head.removeChild(elm);
379 };
380
381 return new Promise(function(resolve, reject) {
382 resolver = resolve;
383
384 elm.setAttribute("src", url);
385 elm.setAttribute("type", "text/javascript");
386 elm.setAttribute("charset", "utf-8");
387 elm.setAttributeNode(document.createAttribute("async"));
388
389 elm.onerror = function(evt) {
390 reject("Error while loading: " + url);
391 };
392
393 document.head.appendChild(elm);
394 });
395 },
396
397 blobCache: {},
398
399 svgCache: {},
400
401 cssCache: {},
402
403 imgCache: {},
404
405 blob: function(name, tg, ctx, ready) {
406 var parse = function (mvcContext) {
407 var blob = new bubbles.all(new mvcContext());
408
409 // css
410 blob.add(function(ready){
411 if(this.css && bubbles.cssCache[name] !== true) {
412 var style = document.createElement("link");
413 style.setAttribute("rel", "stylesheet");
414 style.setAttribute("href", "bin/" + name + ".css");
415 style.onload = function(){
416 bubbles.cssCache[name] = true;
417 ready();
418 };
419 document.head.appendChild(style);
420 } else {
421 ready();
422 }
423 });
424
425 // svg sprite
426 blob.add(function(ready){
427 if(this.svg && bubbles.svgCache[name] !== true) {
428 bubbles.xhr("bin/" + name + ".svg", function(svgData){
429 var imgs = new DOMParser().parseFromString(svgData, "image/svg+xml").getElementsByTagName("svg");
430 for(var i = 0; i < imgs.length; i++){
431 var img = imgs[i];
432 if(img.id) bubbles.imgCache[img.id] = img;
433 }
434 bubbles.svgCache[name] = true;
435 ready();
436 });
437 } else {
438 ready();
439 }
440 });
441
442 blob.then(function(){
443 // init mvc
444 if (this.model || this.view || this.controller) {
445 this.name = name;
446 bubbles.mvc(tg, this, ctx, ready);
447 } else if(ready && ready.call) {
448 ready.call(this);
449 }
450 });
451 };
452
453 // console.log("name", name, bubbles.blobCache[name], bubbles.blobCache);
454 if (!bubbles.blobCache.hasOwnProperty(name)) {
455 bubbles.blobCache[name] = bubbles.loadBinScript(name, ctx);
456 }
457
458 bubbles.blobCache[name].then(parse, function(err){
459 console.log(err);
460 ready();
461 });
462 },
463
464 // acquire polyFills
465 acquire: function (coll, done) {
466 var ctx = this, polyFill = 0, isReady = function () {
467 if (!polyFill && done) done();
468 };
469
470 var newBlob = function(name) {
471 polyFill++;
472 ctx.blob(name, false, ctx, function (blob) {
473 coll[name] = blob;
474 polyFill--;
475 isReady();
476 });
477 };
478
479 for (var name in coll) {
480 if (coll.hasOwnProperty(name) && !coll[name]) {
481 newBlob(name);
482 }
483 }
484 isReady();
485 },
486 };
487
488 // the chicken or the egg
489 window.addEventListener("load", function(){
490 // polyfill missing dependencies
491 bubbles.acquire({
492 classList: "classList" in document.createElement("a"),
493 forEach: Array.prototype.forEach,
494 promise: window.Promise
495 }, function () {
496 bubbles.blob("main", document.body, {
497 bubbles: bubbles
498 });
499 });
500 });
501})();
502