Skip to content

Instantly share code, notes, and snippets.

@brettz9

brettz9/jsl.html

Last active Aug 29, 2015
Embed
What would you like to do?
Proof-of-concept for XSL-like JavaScript class to transform HTML (the demo is JS 1.8 (Firefox only?); arrow functions won't work in this approach due to the non-binding of "this" and the short form for methods is apparently not yet implemented at least in Firefox)
<!DOCTYPE html>
<html>
<head><meta charset="utf-8" />
<script src="jsl.js"></script>
</head>
<body>
<a>test</a>
<a>test2</a>
<div><span>woohoo!</span></div>
<div></div>
<script>
/*
var result = JSL().match({
'/' : function () {
return '<body>' + this.applyTemplates('mode2') + '</body>';
},
'a' : function () {
return '<a/>';
},
'div' : {
'mode1' : function () {
return '<div class="_1"/>';
},
'mode2' : function () {
return '<div class="_2"/>';
}
}
});*/
function string (node) {
return node.textContent;
}
var result = JSL().
variables({
'myVar1' : 'someValue'
}).
named({
'italicize' : function (node) '<i>' + node + '</i>',
'bold' : {
'b' : function (node) '<b>' + node + '</b>',
'span' : function (node) '<span style="font-weight:bold">' + node + '</span>'
}
}).
match({
'/' : function ()
'<body>' +
this.call('bold', 'span', ['text' + this.vars.myVar1]) +
/* this.foreach('a', function () // Could make foreach chainable using valueOf, etc. in subsequent chain
'<span>' + this.call('italicize', [this.valueOf('.')]) + '</span>'
) + */
this.apply('mode2') +
'</body>'
,'a' : function (a)
'<a class="as">' + this.valueOf('.') + '</a>'
,'div' : {
'mode1' : function ()
'<div class="_1"/>'
,'mode2' : function ()
'<div class="_2"/>' + this.foreach('. span', function () // Could make foreach chainable using valueOf, etc. in subsequent chain
'<span>' + this.call('italicize', [this.valueOf('.')]) + '</span>'
)
}
}
);
//*/
// Fix: need to be able to stop templates!!! (and apply within copy(), etc.)
// Fix: need to be able to sort inside foreach's or applyTemplate's (second argument to these)
//alert((document.documentElement.textContent || document.documentElement.innerText));
alert(result);
</script>
</body>
</html>
/*
TO-DOS
0. Clarify distinction with transforming supplied DOM and creating new DOM
1. Need to be able to stop templates!!! (and apply within copy(), etc.)
2. Need to be able to sort inside foreach's or applyTemplate's (second argument to these)
*/
/**
* JavaScript Stylesheet Language (Transformations)
* Can iterate over XML/XHTML/HTML, JSON, etc.
* Can build strings, DOM elements, JSON objects, JSON objects representing elements, etc.; use output() to determine
* @param {Element} [ctx] A DOM element on which to iterate; defaults to document.body
*/
function JSL (ctx) {
if (!(this instanceof JSL)) {
return new JSL(ctx);
}
this.output = '';
this.addContext(ctx);
}
JSL.prototype.addContext = function (ctx) {
this.ctx = this.applyCtx = ctx || document.body;
};
// Takes object (since no need for particular order) with properties representing the 'match's as strings,
// and those properties taking either a function or another object with properties representing the modes as strings
JSL.prototype.match = function (templates) {
this.templates = templates;
return this.execute(); // Could be optional
};
JSL.prototype.execute = function () {
var root = this.templates['/']; // Fix: can root have a mode?
this.output += root.call(this);
return this.output;
};
JSL.prototype.apply = JSL.prototype.applyTemplates = function (mode) {
// Here we'll do the DOM way, but we should implement to allow JSON traversal
var treeWalker = document.createTreeWalker(
this.applyCtx,
NodeFilter.SHOW_ELEMENT, {
acceptNode: function(node) {
return NodeFilter.FILTER_ACCEPT;
}
},
false
);
while (treeWalker.nextNode()) {
var elem = treeWalker.currentNode.nodeName.toLowerCase();
var match = this.templates[elem];
if (match) {
this.ctx = treeWalker.currentNode;
if (typeof match === 'object' && match[mode]) {
this.output += match[mode].call(this);
}
else if (typeof match !== 'object') {
this.output += match.call(this);
}
}
}
return this.output;
};
JSL.prototype.named = function (namedTemplates) {
this.namedTemplates = namedTemplates;
return this;
};
JSL.prototype.call = JSL.prototype.callTemplate = function (elem, mode, params) {
if (!params && mode && typeof mode !== 'string') {
params = mode;
mode = undefined;
}
var match = this.namedTemplates[elem], output = '';
if (match) {
if (typeof match === 'object' && match[mode]) {
output += match[mode].apply(this, params);
}
else if (typeof match !== 'object') {
output += match.apply(this, params);
}
}
return output;
};
JSL.prototype.text = function (text) {
return text; // Not so necessary in string mode!
};
JSL.prototype.variables = function (obj) {
this.vars = obj;
return this;
};
JSL.prototype.valueOf = function (sel, s) {
// for now, implement as s string
var node;
if (sel === '.') {
node = this.ctx;
}
else {
node = this.ctx.querySelector(sel);
}
return node.cloneNode(true).textContent;
};
JSL.prototype.copyOf = function (sel) {
var node;
if (sel === '.') {
node = this.ctx;
}
else {
node = this.ctx.querySelector(sel);
}
// for now, implement as s string
var div = document.createElement('div');
div.appendChild(node.cloneNode(true));
return div.innerHTML;
};
JSL.prototype.copy = function () {
// for now, implement as s string
var div = document.createElement('div');
div.appendChild(this.ctx.cloneNode(true));
return div.innerHTML;
};
// Can also just use Array.reduce
JSL.prototype.foreach = function (sel, cb) {
var nodes = sel;
if (typeof sel == 'string') {
if (sel.slice(0, 2) === '. ') {
nodes = this.ctx.querySelectorAll(sel.slice(2));
}
else {
nodes = document.querySelectorAll(sel);
}
}
var output = '';
for (var i=0, ct = nodes.length; i < ct; i++) {
this.ctx = nodes[i];
output += cb.call(this, nodes[i]);
}
return output;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.