/*! demo-x - v0.1.6 - 2014-12-04 * http://esha.github.io/demo-x/ * Copyright (c) 2014 ESHA Research; Licensed MIT */ (function(window, D) { "use strict"; var DemoXProto, DemoX; if (D.registerElement) { DemoXProto = Object.create(HTMLElement.prototype); DemoX = {}; // wait to register until all is ready } else { DemoXProto = {}; DemoX = window.DemoX = function DemoX(el) { if (!el.createdCallback) { for (var prop in DemoXProto) { Object.defineProperty(el, prop, Object.getOwnPropertyDescriptor(DemoXProto, prop)); } el.createdCallback(); } }; DemoX.prototype = DemoXProto; DemoX.load = function() { D.queryAll('demo-x').each(DemoX); }; DemoX.load();// early availability D.addEventListener('DOMContentLoaded', DemoX.load);// eventual consistency } DemoXProto.timing = { intent: 1000, backspace: 25, comment: 10, code: 50, tick: 250, minTicks: 8 }; DemoXProto.createdCallback = function() { var self = this; self.display = self.query('demo-dom'); self.input = self.query('demo-in'); self.output = self.query('demo-out'); self.intent(self.input); self._exec = function() { self.execute(); }; self.input.setAttribute('style', 'white-space: pre;'); if (self.input.children.length) { self.initStory(); } if (self.display) { self.doc = DemoX.docify(self.display.children); for (var i=0; i<self.display.attributes.length; i++) { var attr = self.display.attributes[i]; self.doc.body.setAttribute(attr.name, attr.value); } self.initDisplay(); } else { // a document w/no body content self.doc = DemoX.docify(new DOMxList()); } self.initControls(); }; DemoXProto.initDisplay = function() { var self = this; function update() { self.display.innerHTML = self.doc.body.stringify(true); } update(); self._observer = new MutationObserver(update) .observe(self.doc.html, { childList: true, subtree: true, attributes: true, characterData: true }); }; DemoXProto.initStory = function() { var self = this; self._next = function(){ self.next(); }; self.story = self.input.children.each('textContent'); self.input.innerHTML = ''; this._tick = function() { if (self.index){ self.execute(); } setTimeout(self._next, self.calcPause()); }; this._tick(); }; DemoXProto.initControls = function() { var self = this, stop = self.query('[stop]'), start = self.query('[start]'); self._stop = function() { self.stopped = true; self.classList.add('stopped'); }; self._start = function() { self.classList.remove('stopped'); if (!(self.index in self.story)) { self.index = 0; } self.stopped = false; self.next(); }; self.input.addEventListener('keydown', self._stop); if (stop) { stop.addEventListener('click', self._stop); } if (start) { start.addEventListener('click', self._start); } }; DemoXProto.next = function() { var self = this, code = self.story[self.index]; if (code && !self.stopped) { var input = self.input; self.animate(self.input.value, code, function(s){ input.value = s; }, self._tick); self.index++; } else if (!code) { self._stop(); } }; DemoXProto.calcPause = function() { // base pause of current line, not next line var code = this.story[this.index-1] || ''; // first line and comments go instantly return !code || (code.indexOf('//') === 0 && code.indexOf('\n') < 0) ? 0 : // others default to 250ms per symbol, with a minimum of 2s Math.max(code.replace(/\w|\s/g, '').length, this.timing.minTicks) * this.timing.tick; }; DemoXProto.intent = function(el) { var timeout, self = this; el.addEventListener("keydown", function() { if (timeout){ clearTimeout(timeout); } timeout = setTimeout(self._exec, self.timing.intent); }); }; DemoXProto.execute = function() { /*jshint unused:false */ var document = this.doc, code = this.input.value, result; if (code) { try { result = eval(code); DemoX.flash(result); } catch (e) { e.code = code; result = e; } if (this.output) { var log = this.output.innerHTML; this.output.innerHTML = '<p class="line">'+ DemoX.describe(result)+'</p>' + log; if (result instanceof Error) { console.error(result); } } else { console.log(code); console.log(result); } } }; DemoXProto.animate = function(text, next, update, finish) { var i = text.length, self = this, action = 'code'; (function _step() { if (!self.stopped) { if (next.indexOf(text) < 0) { action = 'backspace'; text = text.substr(0, --i); } else if (i < next.length) { action = 'code'; text = next.substr(0, ++i); } else { return finish(); } if (text.indexOf('\n') < text.indexOf('//') || text.indexOf('*/') < text.indexOf('/*')) { action = 'comment'; } update(text); setTimeout(_step, self.timing[action]); } })(); }; DemoXProto.index = 0; DemoX.docify = function(dom) { var d = D.createDocumentFragment(); d.html = d.documentElement = D.createElement('html'); d.appendChild(d.html); d.html.appendChild(d.body = D.createElement('body')); dom.each(function(el) { el.remove(); d.body.append(el); }); d.html.dot(); try { delete d.parentNode; d.parentNode = window; } catch (e) {} return d; }; DemoX.describe = function(el) { if (D.x._.isList(el) && el.each) { return el.each(DemoX.describe).join(', '); } if (el instanceof HTMLElement) { var id = el.getAttribute('id'), classes = el.getAttribute('class'); return '<'+ el.tagName.toLowerCase()+ (id ? '#'+id : '')+ (classes ? '.'+classes.split(' ').join('.') : '')+ '>'; } if (el instanceof Node) { return el.value; } if (typeof el === "object") { if (el instanceof Error) { return 'Error: "'+el.message + (el.code ? '" from "'+el.code : '') + '"'; } return JSON.stringify(el); } return el && el.value || (el+''); }; // this all hitches on css animations and domx-stringify's _attr support DemoX.highlight = function(el) { if (el.setAttribute) { el.setAttribute('_highlight', 'true'); } }; DemoX.unhighlight = function(el) { if (el.removeAttribute) { el.removeAttribute('_highlight'); } }; var flashTimeout; DemoX.flash = function(el) { if (el && el.each) { if (flashTimeout){ clearTimeout(flashTimeout); } flashTimeout = setTimeout(function() { el.each(DemoX.highlight); setTimeout(function() { el.each(DemoX.unhighlight); }, 1000); }, DemoX.flash.time || 100); } }; // register only after everything is ready if (D.registerElement) { var utils = DemoX; DemoX = window.DemoX = D.registerElement('demo-x', { prototype: DemoXProto }); for (var prop in utils) { DemoX[prop] = utils[prop]; } } })(window, document);