//vcc.js by dandavis. a simple View Component Creator. [CCBY4] (function (root, factory) { if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module. define([], factory); } else if (typeof exports === 'object') { // Node. Does not work with strict CommonJS, but // only CommonJS-like environments that support module.exports, // like Node. module.exports = factory({}); } else { // Browser globals (root is window) root.VCC = factory(root); } }(this, function (pub) { function assign(o, x){ for (var k in x) if (assign.hasOwnProperty.call(x, k)) o[k] = x[k]; return o; } //util for perf: function forEach(r,f){var m=r.length, i=0;for(; iarguments.length))for(var c=a.length,n=0;n";(a=a._||[],!Array.isArray(a))&&(a=[a]),q=0,g;for(v=a.length;q"+p.join("")+""}function L(a,d,p,c){function n(b,a,c){return b.textContent!==b.fsdhjklghdklg}var q=(p=c.timing)?performance.now():0,g=c.debug,v=z(a.path.concat(a.index||a.key),function(b,a,c){return b!=a.xsdgdfg}),r=v.slice,k=z(v,function(b,a,c){return b!=c.xsdgdfg}).slice(-1)[0],t=G(v,c.dest),e=J(v,c.vdom),f=z(t.parents,Boolean),b={type:a.type,index:d,path:v,key:k,elm:t.node||t.parents.slice(-1)[0],elmParents:f,elmParent:f.slice(-1)[0],dest:c.dest,parents:e,parent:e.parents.slice(-1)[0],isAttrib:!(k-.1)&&"_"!=k&&"$"!=k,change:a},y,A,h,m,x;c=b.elm;var l,w;"function"==typeof b.elm&&(b.elm=b.elmParent),g&&console.info("CHANGE: "+d,b);switch(a.type){case"set":if(!b.isAttrib&&("string"==typeof a.val||Array.isArray(a.val))){b.elmParent.childNodes||(b.elmParent=b.elmParent[b.key]),b.elm||(b.elm=b.elmParent),c=b.elm,r=a.val,Array.isArray(r)||(r=[r]),l=0;for(v=r.length;l=b.key&&(h=b.elm[0].parentNode),h instanceof NodeList&&(h=z(b.elmParents,function(b,a,c){return b.textContent!==b.fsdhjklghdklg}).pop()),h!==m&&(3!=h.nodeType?h.appendChild(m):(w=h.parentNode,w.insertBefore(m,h),w.insertBefore(h,m))))}));break;case"rm":if(b.elmParent.childNodes&&(b.elmParent=b.elmParent.childNodes),0===b.elmParent.length&&(b.elmParent=b.elmParents.slice(-3)[0]),b.elmParent.childNodes&&(b.elmParent=b.elmParent.childNodes),b.parent._&&(b.parent=b.parent._),k=r.call(b.elmParent,a.index-a.num+1,a.index+1),0===a.index)for(g&&console.log("removing many from zero",r.call(b.parent),b.elmParent,"|||",b.parent[0],a.index,a.num),e=a.index,t=e+a.num;e",g;a.childNodes.length||(a.innerHTML=" "),c||(c=a);if(c instanceof Element){c=c.innerHTML.replace(/<\!\-\-[\s\S]+?\-\->/g,"");if(p===c)return{update:Boolean};c=D(q+c+"",n)}return"string"==typeof c&&(c=D(c,n)),p=u.blnTiming?performance.now():0,g={dest:a,vdom:c,debug:u.blnDebug,timing:u.blnTiming,initTime:p-d,update:function(a){var c=u.blnTiming,k,t=0,e;c&&(k=performance.now()),"string"==typeof a&&(a=D(q+a+"",n)),c&&(g.parseTime=performance.now()-k),g.vdom2=a,c&&(k=performance.now()),g.changes=I(g.vdom,a),c&&(g.diffTime=performance.now()-k,k=performance.now());for(e=g.changes.length;te||f===a.length)return!1;f++}return!0}if("object"==typeof a){if("object"!=typeof c)return!1;var b=g(q(Object.keys(a)),q(Object.keys(c))),d=Object.keys(b).length,e=d/15,f=0,p;for(p in b)if(!n(a[p],c[p])){if(2<=f&&f>e||f+1===d)return!1;f++}return!0}return Number.isNaN(a)&&Number.isNaN(c)}function n(a,c){if(a===c)return!0;if(Array.isArray(a)){if(Array.isArray(c)&&a.length===c.length&&a[0]===c[0]&&String(a)===String(c)){for(var e=0,f=a.length;ex.a&&m>x.b;)if(c(a[l],d[m]))v(a[l],d[m],e,f.concat([l])),l--,m--;else{var h=p(c,a,d,l,m,x.a+1,x.b+1),l=l-h.a,w=m-h.b;1===l&&1===w?b(e,f.concat(h.a+1),d[h.b+1]):1===l&&2===w?(u(e,f,h.a+2,d.slice(h.b+2,m+1)),b(e,f.concat(h.a+1),d[h.b+1])):2===l&&1===w?(r(e,f,h.a+2,1,"I"),b(e,f.concat(h.a+1),d[h.b+1])):2===l&&2===w?(b(e,f.concat(h.a+2),d[h.b+2]),b(e,f.concat(h.a+1),d[h.b+1])):(0x.a?r(e,f,l,l-x.a,"Z"):m>x.b&&u(e,f,l+1,d.slice(x.b+1,m+1)),h=x.a,m=x.b}0<=h?r(e,f,0,h+1):0<=m&&u(e,f,0,d.slice(0,m+1))}else if("object"==typeof a&&"object"==typeof d)for(m in x=g(q(Object.keys(a)),q(Object.keys(d))),x)v(a[m],d[m],e,f.concat([m]));else b(e,f,d)},r=[];return v(a,d,r,[]),r},B=function(a,d){var p=document.createElement(d&&-1===d.indexOf("-")?d:"div");return p._intraDirty=!0,p.innerHTML=a,p},K={area:1,base:1,br:1,col:1,command:1,embed:1,hr:1,img:1,input:1,keygen:1,link:1,meta:1,param:1,source:1,track:1,wbr:1};return u.elementFromString=B,u.fromHTML=D,u.toHTML=C,u.odiff=I,u.updater=H,u.resolvePath=G,u.blnTiming=!1,u.blnDebug=!1,E.jQuery&&(E.jQuery.fn.intraHTML=function(a){return this.each(function(d,p){u(p,a)}),this}),u}(pub); function VCC(def) { // guard input object and displayName property: if(typeof def !== "object") throw new TypeError("VCC Expects a definition object"); if(typeof def.displayName !== "string") throw new TypeError("VCC Definition needs a String displayName property"); if(def._static===true || def._static==="content") return VCCstatic(def); // use slimmer micro core for statics, good for simple sub-components function call(fn, that, a, b){ if(!fn || typeof fn !== "function") return; return arguments.length===4 ? fn.call(that, a, b) : fn.call(that, a); } var EVENTS="reset,invalid,focus,blur,select,keydown,keypress,keyup,mousedown,mouseup,click,dblclick,change,submit,input,paste,update".split(","), fnCache={}, tagName = "vcc-" + def.displayName; if(VCC[tagName]) return; // don't define a component twice VCC[tagName] = def; // add this def to the collective //allow "inheritance from array of mixins left to right: if(def.mixins) forEach( (Array.isArray(def.mixins) ? def.mixins : [def.mixins]), function(mixin) { forEach(Object.keys(mixin), function(k){ var old = def[k]; if(typeof old==="function"){ def[k]= function(a){ mixin[k].call(this, a); return old.call(this, a); }; }else{ if(typeof old==="undefined") def[k]= mixin[k]; } }); }); class vcc_proto extends HTMLElement { constructor() { super(); if(this.parentNode && this.parentNode._intraDirty) return; var that = this, oldState, oldProps; assign(this, VCC.prototype); // implement inheritance (since VCC is a function instead of object, it can't be auto) this.VCC=VCC; // allow pure render functions when using utilities like VCC.classes(), VCC.show(), etc this.state = assign({}, (typeof def.getInitialState === "object" ? def.getInitialState : call(def.getInitialState, this, VCC)) || {}); this._def = def; this.props = assign({}, (typeof def.getDefaultProps === "object" ? def.getDefaultProps : call(def.getDefaultProps, this, VCC)) || {}); //allow "inheritance from array of mixouts left to right: if(def.mixouts) forEach(Array.isArray(def.mixouts) ? def.mixouts : [typeof def.mixouts ==="function"?def.mixouts.call(that):def.mixouts], function(mixout) { forEach(Object.keys(mixout), function(k){ that[k]= mixout[k]; }); }); // mixins apply to def, mixouts apply to elm isntance itself [].forEach.call(this.attributes, function(attr, index){ if(EVENTS.indexOf(attr.name.replace(/^on\-/,""))!==-1) return; //console.log("attr", this, attr, attr.name, attr.value); var val=attr.value; try{ val=Function("return "+ val).call(that); }catch(y){} this.props[attr.name]=val; }, this); var noBubbles=",focus,blur,select,keydown,keyup,mousedown,mouseup,submit,input,paste,update,"; // bind any events to the actual tag: EVENTS.forEach(function(evt) { this.addEventListener(evt, function(e) { var elm = e.target, cmd = elm.getAttribute("on-"+evt), blnDelegate = this._def._delegate && noBubbles.indexOf(","+e.type+",")===-1; while(blnDelegate && elm != this && elm){ cmd = elm.getAttribute("on-"+evt); if(cmd) break; elm=elm.parentNode; } if(!cmd) return; var fn=(fnCache[cmd] || (fnCache[cmd]=Function("event", "var $0=event.target;return " + cmd))).call(that,e); if(typeof fn==="function") setTimeout(fn.bind(that, e), 0); }, true); }, this); // bind all methods: Object.keys(def).forEach(function(method, _, __) { if(typeof def[method] !== "function" || {render: 1}[method] ) return; if(!this[method]) this[method] = def[method].bind(this); }, this); // bind any specified events to the actual tag and sub-tags using delegation: if(typeof def.events==="function") def.events = def.events.call(this); if(typeof def.events==="object"){ var m=that.matches||that.webkitMatchesSelector||that.mozMatchesSelector||that.msMatchesSelector; Object.keys(def.events).forEach(function(evt){ var r=evt.trim().split(/\s+/), name=r.shift(), sel=r.join(" "), val=def.events[evt]; if(that[val] || val.call) that.addEventListener(name, function(e){ if(sel && !m.call(e.target, sel)) return; return (that[val]||val).call(that, e); }); }, this); }//end if events? // add css (if any) if(def.css) VCC.css(String(def.css).replace(/\$0/g, "vcc-"+def.displayName )); function renderer(blnNow) { function _render() { var temp = def.render.call(that, VCC)||""; if(_render.unD3f1n3d==temp) temp = ""; if(Array.isArray(temp)) temp = temp.join(""); temp=""+temp; forEach(Object.keys(VCC.statics), function(k){ temp = VCC.statics[k](temp); }); if(renderer.oldView != temp ) VCC.intraHTML(that, renderer.oldView = ""+temp); if( that._attached) call(def.componentDidUpdate, that, that.props, oldState); } if(blnNow === true) return _render(); clearTimeout(renderer.timer); renderer.timer = setTimeout(_render, 25); } this.setState = function(state, blnReplaceState) { if(def.shouldComponentUpdate) { if(call(def.shouldComponentUpdate, this, this.props, state)) { renderer(); } } else { renderer(); } oldState = assign({}, that.state); if(blnReplaceState === true){ that.state = state; }else{ assign(that.state, state); } call(def.componentWillUpdate, this, this.props, state); VCC.trigger(that, "update"); }; this.replaceState=function(state){ return this.setState(state, true); }; this._orig = this.innerHTML; if(typeof def.render !== "function") def.render = function(){return this._orig;}; //make render optional by subbin in Boolean if missing: //if(typeof def.render !== "function") def.render = this._revert; this._renderer= renderer; this._render= this.setState.bind(this, {}); call(def.componentWillMount, this, VCC, def); renderer(true); if(typeof def.renderTrigger === "function") def.renderTrigger(this._renderer.bind(this)); } //end constructor() connectedCallback(){ if(!this.isConnected) return; if(this._attached) return; this._attached=true; [].forEach.call(this.querySelectorAll("[ref]"), function(elm){ var ref=elm.getAttribute("ref"), ref2=eval("0||"+ref); if(ref2.call) ref2.call(this, elm); }, this); call(def.componentDidMount, this, VCC); VCC.trigger(this, "update"); }//end attached // watch attribs for changes to sync attribs + defined props attributeChangedCallback(e,old,v){ if(e=="class"){return; } // class is too noisy to make a useable prop var newProps = assign({}, this.props); // shallow copy of props if(def.propTypes && def.propTypes[e])try{ v=def.propTypes[e](v);}catch(y){} //coerce if needed newProps[e]=v; call(def.componentWillReceiveProps, this, newProps); assign(this.props, newProps); // copy new props into current if(def.shouldComponentUpdate){ if(call(def.shouldComponentUpdate, this, newProps, this.state)){ call(def.componentWillUpdate, this, newProps, this.state); VCC.trigger(this, "update"); this._renderer(true); } }else{ call(def.componentWillUpdate, this, newProps, this.state); VCC.trigger(this, "update"); this._renderer(true); } }//end attr changed detachedCallback(){ if(typeof def.componentWillUnmount === "function") def.componentWillUnmount.call(this); }//end detached }// end class vcc_proto return customElements.define(tagName, vcc_proto); }; //end VCC() // returns a function that transforms a string, turning inline bare tags into a innerHTML-populated form function VCCstatic(def) { function build(o, k, val) { if (typeof o[k] === "object") o[k] = Object.bind(this, o[k]); o[k] = o[k] || Object.bind(this, val); } build(def, "getInitialState", {}); build(def, "getDefaultProps", {}); function htmlDecode(input) { return input.indexOf("&") === -1 ? input : input.replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>'); } // setup mixins: if (typeof def.mixins === "object") { forEach(Array.isArray(def.mixins) ? def.mixins : [def.mixins], function(mixin) { forEach(Object.keys(mixin), function(k) { def[k] = mixin[k]; }); //end forEach }); //end forEach } //end if mixins? var tagRx = RegExp("]+\s?)?>([\\w\\W]+?)?<\/vcc-" + def.displayName + ">", "ig"), rxs = [/([\w\-]+)="([^"]*)"/g, /([\w\-]+)='([^']*)'/g, /([\w\-]+)=([^\s>\/]+)/g, /([\w\-]+)()/g]; function fnReppr(j, k, v, x, y) { v = htmlDecode(v); this[k] = v === '0' ? 0 : (+v || v || true); //may have to de-encode (html) this value since its never been parsed return ""; } return VCC.statics[def.displayName] = function transformer(str) { return str.replace(tagRx, function(whole, attribs, kids) { var sa = (attribs||"").trim(), content = (kids||"").trim(), props = {}, that = { displayName: def.displayName, VCC: VCC }, tag = {}; //populate object tag with html-set attribs to over-ride default props if (sa !== "") for (b = 0; b < 4; b++) sa = sa && sa.replace(rxs[b], fnReppr.bind(tag)); //setup props+state: that.props = assign(assign({}, def.getDefaultProps()), tag); that.state = assign({}, def.getInitialState()); // bu/pub orig tag contents if (content) that.content = content; // call cWM: if (typeof def.componentWillMount === "function") def.componentWillMount.call(that, VCC); // build "innerHTML": content = def.render.call(that, VCC) || kids; // return string representing component if(def._static==="content") return content; return "" + content + "<\/vcc-" + def.displayName + ">"; }); }; } VCC.attrs=function(o){ return Object.keys(o).map(function(k){ var v=o[k]; if(v===false) return " "; if(v===true) return k+" "; return k+"="+JSON.stringify(v); }).join(" "); }; VCC.data = function _(elm, obj) { if(!obj && elm instanceof Element) return JSON.parse(JSON.stringify(elm.dataset)); if(!obj) return Object.keys(elm).map(function(k){ return " data-"+k+"=\"" + VCC.escape(""+elm[k]) + "\" "; }).join(""); Object.keys(obj).map(function(k) { if(obj[k] === false) { elm.removeAttribute("data-" + k); } else { elm.dataset[k] = obj[k] === true ? "" : obj[k]; } }); return _(elm); }; VCC.select = function(obj, arrKeys){ var out= {}; arrKeys.forEach(function(k){ out[k] = obj[k]; }); return out; }; VCC.pluck = function(key, obj){ return obj[key]; }; VCC._=function(r){ return [].slice.call(r); }; VCC.$=function(selector, base){ base = base || document.documentElement; return VCC._(base.querySelectorAll(selector)); }; VCC.orderBy=function(key){ return function _sorter(a,b){return a[key]>b[key] ? 1 : ( a[key] === b[key] ? 0 : -1); }; }; VCC.json = function(json){ return typeof json.json === "function" ? json.json() : // fetch response usage (typeof json==="string" ? // raw data usages JSON.parse(json) : JSON.stringify(json) ); }; VCC.elm=function ht(ob){ if(!ob) ob= "div"; if(typeof ob==="string") return document.createElement(ob); return VCC.intraHTML.elementFromString(VCC.intraHTML.toHTML(ob)).firstChild; }; VCC.ht = VCC.intraHTML.toHTML; VCC.assign = assign; VCC.hasRoute=function(route, fillIn ){ return location.hash ? (location.hash.search(route)!=-1) : fillIn; }; VCC.getRoute=function(route){ if(typeof route === "string") route = RegExp(route+"=([^&]+)"); return location.hash.split(route).slice(1).shift(); }; VCC.classes = function(a){ return Object.keys(a).filter(function(k){return a[k];}).join(" "); }; VCC.checked=function(v){ return v ? ' checked="" ' : ''; }; VCC.show= VCC.if= function(v){ return v ? '' : ' hidden ' ; }; VCC.hidden= VCC.else= function(v){ return v ? ' hidden ' : ' '; }; VCC.filter=function(strElmContent, strTerm){ return (strTerm && strElmContent.indexOf(strTerm)===-1) ? " hidden " : "" }; VCC.paginate= function(index, page, perPage){ return (perPage * (page+1) > index && perPage * (page) < index) ? "" : " hidden "; }; VCC.escape=function(str){ return str.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); }; VCC.trigger = function(elm, strEvent) { var event = document.createEvent('Event'); event.initEvent(strEvent, true, true); return elm.dispatchEvent(event); }; VCC.fade = function fadeIn(elms, period){ period = period || 40; VCC.css("[style*=opacity]{transition: "+(period*2.5)+"ms opacity;}"); if(typeof elms === "string") elms = VCC.$(elms); if(!Array.isArray(elms)) elms=VCC._(elms); elms.map(function(elm, index){ elm.style.opacity = 0; setTimeout(function(){ elm.style.opacity = 1;}, period * index); }); }; VCC.css = function css(strCSS, blnRemove) { var text, doc = document; css.cache = css.cache || {}; css.heap = css.heap || []; if(!VCC._style){ VCC._style = VCC._style || doc.head.appendChild(doc.createElement("style")); VCC._style.id = "VCC_style"; } if (blnRemove === true) { var old = css.cache[strCSS]; if (!old) return false; if (old.parentNode) { VCC._style.removeChild(old); } else { var ind = css.heap.indexOf(old); if (ind !== -1) css.heap.splice(ind, 1); } delete css.cache[strCSS]; return true; } if (!css.cache[strCSS]) { clearTimeout(css.timer); css.timer = setTimeout(function() { css.heap.forEach(function(a) { VCC._style.appendChild(a); }); css.heap.length = 0; }, 15); text = doc.createTextNode(strCSS); css.heap.push(text); return css.cache[strCSS] = text; } return false; }; VCC.statics={}; VCC.keys={}; forEach(( "||||||||BACK_SPACE|TAB|||CLEAR|RETURN|ENTER||SHIFT|CONTROL|ALT|PAUSE|CAPS_LOCK|||||||ESCAPE|CONVERT|NONCONVERT|ACCEPT|MODECHANGE|SPACE|PAGE_UP|PAGE_DOWN|END|HOME|LEFT|UP|RIGHT|DOWN|SELECT|PRINT||PRINTSCREEN|INSERT|DELETE||0|1|2|3|4|5|6|7|8|9|COLON|SEMICOLON|LESS_THAN|EQUALS|GREATER_THAN|QUESTION_MARK|AT|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z|WIN||CONTEXT_MENU||SLEEP|0|1|2|3|4|5|6|7|8|9|MULTIPLY|ADD|SEPARATOR|SUBTRACT|DECIMAL|"+ "DIVIDE|F1|F2|F3|F4|F5|F6|F7|F8|F9|F10|F11|F12|F13|F14|F15|F16|F17|F18|F19|F20|F21|F22|F23|F24|||||||||NUM_LOCK|SCROLL_LOCK|||||||||||||||CIRCUMFLEX|EXCLAMATION|DOUBLE_QUOTE|HASH|DOLLAR|PERCENT|AMPERSAND|UNDERSCORE|OPEN_PAREN|CLOSE_PAREN|ASTERISK|PLUS|PIPE|HYPHEN_MINUS|OPEN_CURLY_BRACKET|CLOSE_CURLY_BRACKET|TILDE|||||VOLUME_MUTE|VOLUME_DOWN|VOLUME_UP|||||COMMA||PERIOD|SLASH|BACK_QUOTE|||||||||||||||||||||||||||OPEN_BRACKET|BACK_SLASH|CLOSE_BRACKET|QUOTE||META|ALTGR" ).split("|"), function(a,i){VCC.keys["DOM_VK_"+a]=VCC.keys[a]=i;VCC.keys["_"+i]=a;}); // Tiny evented state store inspired by https://github.com/rackt/redux/blob/master/src/createStore.js // changes: uses an object of methods instead of switch(e.type), no error checking, can pass a string action name ("TEST") instead of ({type:"TEST"}) VCC.Store = Store; function Store(reductions, state, pool) { if(typeof reductions != "object") throw "Missing reduction definitions Object"; state = state || {}; pool = pool || []; var ret={ history: [], getState: function(){ return assign({}, state); }, unsubscribe: function(fnReducer){ return pool = pool.filter(function(fn){return fn !== fnReducer;}); }, subscribe: function(fnReducer) { pool.push(fnReducer); return this.unsubscribe.bind(this, fnReducer); }, dispatch: function(action){ // allows reducer return values to be fed to handlers via this: action = action.type ? action : {type: action}; if(!reductions[action.type] && action.type!="_INIT_" ) throw new ReferenceError("Unknown reducer group: "+action.type); this.history.push(action); pool.forEach(function(fn){fn.call(this, state)} , action.type === "_INIT_" ? state : reductions[action.type]( state, action )); } }; setTimeout( ret.dispatch.bind(ret, "_INIT_"), 0); return ret; }; // end Store() // default plugins: VCC.LinkedStateMixin={ componentDidMount: function(){ var hits=[].slice.call(this.querySelectorAll("[link-state]")), rxCheck = /((radio)|(checkbox))/i; if(!hits.length) return; hits.forEach(function(inp){ var prop=inp.getAttribute("link-state"); this["_handle_"+prop]= function(value){ var ob={}; ob[prop]= value; this.setState(ob); }; var domprop = rxCheck.test(inp.type) ? "checked" : "value"; inp.setAttribute("on-change", "this._handle_"+prop+"($0."+domprop+")"); inp[domprop] = this.state[prop]; }, this); }, componentWillUpdate: function(){ var hits=[].slice.call(this.querySelectorAll("[link-state]")). rxCheck = /((radio)|(checkbox))/i; if(!hits.length) return; hits.forEach(function(inp){ var prop=inp.getAttribute("link-state"); var domprop = rxCheck.test(inp.type) ? "checked" : "value"; inp[domprop] = this.state[prop]; }, this); } }; VCC.shallowCompare = function shallowCompare(ob, p, s){ var p2=ob.props, s2=ob.state; return Object.keys(p2).some(function(k, i){ return p2[k]!==p[k]; }) || Object.keys(s2).some(function(k, i){ return s2[k]!==s[k]; }); }; VCC.PureRenderMixin = { shouldComponentUpdate: function(nextProps, nextState) { return VCC.shallowCompare(this, nextProps, nextState); } }; return VCC; }));