Created
July 10, 2017 19:21
-
-
Save benjamingr/ef6533784aa0c1d5ed0d98973da3c381 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Mustache like logic less templating with reverse data binding | |
*/ | |
//HACK, do not use, EVEREVEREVEREVEREVEREVEREVEREVEREVEREVEREVEREVEREVEREVEREVEREVEREVEREVEREVEREVEREVEREVEREVEREVEREVEREVEREVEREVEREVEREVEREVER | |
//EVEREVEREVEREVEREVEREVEREVEREVEREVEREVEREVEREVEREVEREVEREVEREVEREVEREVEREVEREVEREVEREVEREVEREVEREVEREVEREVEREVER | |
//EVEREVEREVEREVEREVEREVEREVEREVEREVEREVEREVEREVEREVEREVEREVEREVEREVEREVER | |
// in production code | |
//templates a given view based on the given viewmodel, for more detail on how this works please check the unit tests | |
window.Mustache = {}; | |
var superSecretIndex = 0; | |
window.Mustache.render = function template(view, viewmodel) { | |
$("*").off("change", "[id*=bindAT" + superSecretIndex + "]"); | |
$("*").off("keyup", "[id*=bindAT" + superSecretIndex + "]"); | |
var deps = []; | |
superSecretIndex++; | |
function ec(str) { | |
return (str + "").replace(/[&<>"'\/]/g, function (backRef) { | |
return escapedCharacters[backRef]; | |
}); | |
}; | |
var escapedCharacters = { | |
'<': '>', | |
'>': '<', | |
'&': '&', | |
'"': """, | |
'\'': "&apos", | |
"/": '/' | |
}; | |
if (typeof view !== "string") { | |
throw "Only strings supported for templating atm"; | |
} | |
//regular expressions that match {{}}, {{#}} and {{{}}} | |
var tag = /\{\{.+?\}\}/g; | |
var funcTag = /\{\{&.+?\}\}/g; | |
var selectTag = /\{\{#(.+?)\}\}((.|\r|\n)*?)\{\{\/\1\}\}/g;//capturing groups backref ftw | |
var unescapedTag = /\{\{\{.+?\}\}\}/g; | |
var editorFor = /\{\{%.+?\}\}/g; | |
//checks the truthy value of an expression for templating, does not do nesting really | |
var ifTag = /\{\{if\((.+?)\)\}\}.*?\{\{endif\}\}/g; | |
//do all templating sessions, first if tags, then functions, then unescaped tags, then normal tags | |
view = view.replace(selectTag, function (match, offset, str) { | |
var nestedAttempt = match.substring(3, match.indexOf("}}")); | |
nestedAttempt = nestedAttempt.split("."); | |
var current = walkTheDotNotation(viewmodel, nestedAttempt); | |
if ((current === "" || !current)) { | |
return "";//means current is falsy and I should not render | |
} | |
var inner = match.substring(match.indexOf("}}") + 2, match.lastIndexOf("{{")); | |
if (current instanceof Array) { | |
var sb = []; | |
for (var elem in current) { | |
sb.push(template(inner, current[elem])); | |
} | |
return sb.join(""); | |
} | |
return inner; | |
}).replace(ifTag, function (match, cond, idx) { | |
var nestedAttempt = cond.split("."); | |
var current = walkTheDotNotation(viewmodel, nestedAttempt); | |
if (current) {//truthy | |
return match; | |
} else { | |
return ""; | |
} | |
}).replace(funcTag, function (match) { | |
var nestedAttempt = match.substring(7, match.length - 2).split("."); | |
var current = walkTheDotNotation(viewmodel, nestedAttempt); | |
return (typeof current === "function") ? current() : ((current) || ""); | |
}).replace(editorFor, function (match) { | |
var nestedAttempt = match.substring(3, match.length - 2).split("."); | |
var current = walkTheDotNotation(viewmodel, nestedAttempt); | |
nestedAttempt = "SEPERATOR" + superSecretIndex + "SEPERATOR" + nestedAttempt.join("SEPERATOR"); | |
deps.push("bind" + nestedAttempt); | |
if (typeof current === "function") { | |
var res = current(); | |
current = res; | |
} | |
if (typeof current === "object" && current instanceof Array) { | |
//drop down | |
var ret = "<select>"; | |
for (var i in current) { | |
ret += "<option value='" + current[i] + "'>" + current[i] + "</option>"; | |
} | |
return ret + "</select>"; | |
} | |
if (typeof current === "object" && current instanceof Date) { | |
//date picker | |
return "<input type='text' id='bind" + nestedAttempt + "' value='" + current + "' />"; | |
} | |
if (typeof current === "boolean") { | |
return "<input type='checkbox' id='bind" + nestedAttempt + "' " + (current ? "checked" : "") + "'/>"; | |
} | |
if (nestedAttempt.toLowerCase().indexOf("password") !== -1) { | |
return "<input type='password' id='bind" + nestedAttempt + "' value='" + current + "' />"; | |
} | |
return "<input type='text' id='bind" + nestedAttempt + "' value='" + current + "' />"; | |
}).replace(unescapedTag, function (match) { | |
var nestedAttempt = match.substring(3, match.length - 3).split("."); | |
var current = walkTheDotNotation(viewmodel, nestedAttempt); | |
return (typeof current === "function") ? "" : (current || ""); | |
}).replace(tag, function (match) { | |
var nestedAttempt = match.substring(2, match.length - 2).split("."); | |
var current = walkTheDotNotation(viewmodel, nestedAttempt); | |
return (typeof current === "function") ? "" : (ec(current) || ""); | |
}); | |
//return the view after templating the viewmodel in | |
for (var sel in deps) { | |
function dep() { | |
var idSep = $(this).attr("id").split("SEPERATOR"); | |
var idLast = idSep[idSep.length - 1]; | |
viewmodel[idLast] = $(this).val(); | |
$(viewmodel).trigger("change"); | |
} | |
$("body").on("change", "#" + deps[sel], dep).on("keyup", "#" + deps[sel], dep); | |
} | |
return view; | |
//helper function to walk the dot notation | |
function walkTheDotNotation(current, nestedAttempt) { | |
if (nestedAttempt.length === 2 && nestedAttempt[0] === "" && "" === nestedAttempt[1]) { | |
return current; | |
} | |
for (var i = 0; i < nestedAttempt.length; i++) { | |
if (!current.hasOwnProperty(nestedAttempt[i])) { | |
return ""; | |
} | |
current = (current[nestedAttempt[i]]); | |
} | |
return current; | |
} | |
}; |
Author
benjamingr
commented
Jul 10, 2017
//util.js :D
/*
*******************************************************************************
* Author: Benjamin Gruenbaum
* Date: 25/01/2013
* Version: 0.1.0
*******************************************************************************
*/// deps: mustache, jQuery
(function ($, mustache) {
var mvc,
viewUpdateQueue = [];
mvc = {
viewBind: viewBinder,
modelBind: modelBinder,
update: updateViewBindings,
bind: bindTogether
};
// MVC functions
function bindTogether(url, object, selector,template) {
var updateFunction;
var crud = modelBinder(url, object);
var elem = $(selector);
if (typeof template === "string") {
updateFunction = virtualViewBiner(elem, object,template);
} else {
updateFunction = viewBinder(elem, object);
}
$(crud).on("r", function () {
updateFunction();
});
var retObj = {
crud: crud,
el: elem,
watchedBy: function (sel) {
var viewFun = mvc.viewBind(sel, object);
$(crud).on("r", function () {
console.log("YOYO");
viewFun();
});
$(object).on("change", function () {
viewFun();
});
return retObj;
},
keepSyncedFromServer: function (frequency, id) {
//only syncs when idle
var time;
$("body").on("mousedown keydown", function () {
clearTimeout(time);
time = setTimeout(upd, frequency);
});
function upd() {
crud.retrieve(id).done(function () {
time = setTimeout(upd, frequency);
});
}
upd();
},
keepSyncedToServer: function (frequency, id) {
//there should be some logic here that only pushes when has to (check object changed)
var time;
$("body").on("mousedown keydown", function () {
clearTimeout(time);
time = setTimeout(upd, frequency);
});
function upd() {
crud.update(id).done(function () {
setTimeout(upd, frequency);
});
}
upd();
}
};
return retObj;
}
//binds an object to a url
function modelBinder(url, object) {
var crud = {
model: object,
create: function () {
return $.post(url, object).done(function () {
$(crud).trigger("c");
}).fail(function () {
$(crud).trigger("createfail");
});
},
retrieve: function (id) {
return $.getJSON(url, id || object.id || object).done(function (data) {
$.extend(object, data);
$(crud).trigger("r");
}).fail(function () {
$(crud).trigger("retrievefail");
});
},
update: function () {
return $.ajax({ url: url, data: JSON.stringify(object), type: "PUT",contentType:"application/json" }).done(function () {
$(crud).trigger("u");
}).fail(function () {
$(crud).trigger("updatefail");
});
},
del: function () {
$(crud).trigger("d");
return $.ajax({ type: "DELETE", url: url, data: object }).done(function () {
object = {};
}).fail(function () {
$(crud).trigger("deletefail");
});
}
};
return crud;
}
//binds a selector to an object
function viewBinder(domElem, object) {
var elem = $(domElem);
var template = $(elem).html();
var fun = function () {
elem.html(mustache.render(template, object));
};
viewUpdateQueue.push(fun);
return fun;
}
function virtualViewBiner(domElem, object,template) {
var fun = function () {
$(domElem).html(mustache.render(template, object));
console.log(domElem);
};
viewUpdateQueue.push(fun);
return fun;
}
function updateViewBindings() {
for (var item in viewUpdateQueue) {
viewUpdateQueue[item]();
}
}
//export result
if (typeof module === "object") {
module.exports = mvc;
} else {
window.m = mvc;
}
}($, Mustache));
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment