sajari-website
Version:
Website extensions for the Sajari API. Automatically index site content, add user profiles, render search and recommendations, etc.
296 lines (274 loc) • 9.98 kB
JavaScript
/*
* dom.js - Handles DOM manipulation, reading, writing, etc
*/
/**
* Query constructor
*/
function dom(opts) {
opts = opts || {};
if (opts.prefix) {
this.prefix = opts.prefix;
}
this.nodes = {};
if (opts.nodes) {
this.nodes = opts.nodes;
}
this.targets = {};
if (opts.targets) {
this.targets = opts.targets;
}
}
dom.prototype = {
/**
* Bind to a DOM event
*/
bind : function(node, eventType, handler) {
if (node.addEventListener) {
node.addEventListener(eventType, handler, false);
} else if (node.attachEvent) {
// IE8/9 compatibility
if (eventType == 'input') {
eventType = 'onpropertychange';
}
node.attachEvent('on' + eventType, function (e) { handler.apply(node, [e]); });
}
},
/**
* Retrieves content from a document meta tag
*/
getMeta : function(metaName) {
var i,
metas = document.getElementsByTagName('meta');
for (i = 0; i < metas.length; i++) {
if (metas[i].getAttribute('name') == metaName) {
return metas[i].getAttribute('content');
}
}
return '';
},
/**
* Imports a CSS file to the page
*/
importCss : function(path) {
var css = document.createElement('link')
css.setAttribute('rel', 'stylesheet');
css.setAttribute('type', 'text/css');
css.setAttribute('href', path);
document.getElementsByTagName('head')[0].appendChild(css);
},
/**
* Return the html entitised version of a text string
*/
entitise : function(text) {
var div = document.createElement('div');
div.appendChild(document.createTextNode(text));
return div.innerHTML;
},
/**
* Propagates data parameters from the 'from' node to the 'to' node
*/
copyDynamicProperties : function(from, to) {
if ((from !== undefined) && (to !== undefined)) {
var attrs = this.dynamicAttrs(from);
if (typeof attrs === 'object') {
for (var key in attrs) {
if (attrs.hasOwnProperty(key)) {
// Propagate relevant data parameters from the query input to the results div
to.setAttribute(this.prefix+key, attrs[key]);
}
}
}
}
},
/**
* Try to get all the target elements from the DOM
* Polyfill for browsers that don't have getElementsByAttribute
*/
scanShim : function(scope) {
// Try to use the built in if it is supported
if (document.getElementsByAttribute !== undefined) {
for (var i = 0; i < this.targets.length; i++) {
elements = document.getElementsByAttribute(this.prefix + this.targets[i], '*');
if (elements.length > 0) {
dom.nodes[this.targets[i]] = elements;
}
}
return
}
// Not supported, run the polyfill instead
if (scope.nodeType === 1) {
for (var i = 0; i < this.targets.length; i++) {
if (scope.hasAttribute(this.prefix + this.targets[i])) {
if (this.nodes[this.targets[i]] === undefined) {
this.nodes[this.targets[i]] = [];
}
this.nodes[this.targets[i]].push(scope);
}
}
if (scope.childNodes !== undefined) {
for (var j = 0; j < scope.childNodes.length; j++) {
this.scanShim(scope.childNodes[j], this.targets, this.nodes);
}
}
}
},
/**
* Returns the value of a this.prefix attribute or the default value
*/
attrVal : function(node, attr, defaultVal) {
if (node.hasAttribute(this.prefix+attr)) {
return node.getAttribute(this.prefix+attr);
} else {
return defaultVal;
}
},
/**
* Returns an object of the dynamic attributes (e.g. custom parameters to send with the request)
* Optionally add a prefix to get only specific params
*/
dynamicAttrs : function(node, prefixSuff) {
if (prefixSuff === undefined) {
prefixSuff = "";
}
var m,
attrs = {};
for (var i in node.attributes) {
var attrib = node.attributes[i];
if (attrib && attrib.specified) {
var reg = new RegExp(this.prefix + prefixSuff + "(.+)$", "g");
m = reg.exec(attrib.name);
if (m && this.targets.indexOf(m[1]) === -1) {
attrs[m[1]] = attrib.value;
}
}
}
return attrs;
},
/**
* Returns all the meta and data-sj-meta params from the DOM. Not the most
* efficient function but stuck with this for now and it only runs once.
*
* Now also supports data-sj-field based meta. Priority is based on the "priority"
* array in the function below then falls back to the innerText value
* Sample tests:
* <meta data-sj-field="sj-field-content" content="content" />
* <meta data-sj-field="sj-field-name" name="name" />
* <meta data-sj-field="sj-field-and-value" data-sj-value="value" />
*/
dynamicMeta : function(node, attrs) {
if (attrs === undefined) {
attrs = {};
}
// Standard meta first
var metas = document.getElementsByTagName('meta');
for (var i=0; i<metas.length; i++) {
var prop = metas[i].getAttribute("property");
var name = metas[i].getAttribute("name");
var content = metas[i].getAttribute("content");
if (name != "" && name != null) {
attrs[name] = content;
} else if (prop != "" && prop != null) {
attrs[prop] = content;
}
}
// Scan for sj meta
var all = document.getElementsByTagName("*");
for (var i=0, max=all.length; i < max; i++) {
// data-sj-meta
var matches = this.dynamicAttrs(all[i], 'meta-');
for (m in matches) {
if (matches[m] !== undefined &&matches[m] !== "") {
attrs[m] = matches[m];
continue;
}
if (all[i].content !== undefined && all[i].content !== "") {
attrs[m] = all[i].content;
continue;
}
if (all[i].innerText !== undefined &&all[i].innerText !== "") {
attrs[m] = all[i].innerText;
}
}
// data-sj-field
var field = all[i].getAttribute(this.prefix+"field");
if (field !== undefined && field !== null) {
var priority = [this.prefix + 'value', 'name', 'property', 'content'];
for (var j = 0; j < priority.length; j++) {
var val = all[i].getAttribute(priority[j]);
if (val !== undefined && val !== null) {
if (val !== "") {
if (typeof attrs[field] === 'undefined') {
attrs[field] = val;
} else if (typeof attrs[field] === 'object') {
attrs[field].push(val);
} else if (typeof attrs[field] === 'string') {
var old = attrs[field];
attrs[field] = [old, val];
}
break
}
}
}
if (all[i].innerText !== undefined &&all[i].innerText !== "") {
attrs[field] = all[i].innerText;
}
}
}
return attrs
},
/**
* Returns the URI parameters of dynamic this.prefix attributes
*/
dynamicAttrsUri : function(node) {
var uri = [],
attrs = this.dynamicAttrs(node);
for (var i in attrs) {
uri.push(encodeURIComponent(i)+'='+encodeURIComponent(attrs[i]));
}
return uri.join('&');
},
/**
* Call the callback for each node with the given attribute.
* First argument to the callback is the node, second is the attribute we looked for.
*/
eachNode : function(attr, callback) {
if (this.hasNode(attr)) {
for (var i = 0; i < this.nodes[attr].length; i++) {
callback(this.nodes[attr][i], this.prefix + attr);
}
}
},
/**
* Return the first node for the given attribute, or undefined if there isn't one.
*/
hasNode : function(attr) {
return (this.nodes[attr] !== undefined && this.nodes[attr].length > 0);
},
/**
* Return the first node for the given attribute, or undefined if there isn't one.
*/
firstNode : function(attr) {
if (!this.hasNode(attr)) {
return undefined;
}
return this.nodes[attr][0];
},
/**
* Return the last node for the given attribute, or undefined if there isn't one.
*/
lastNode : function(attr) {
if (!this.hasNode(attr)) {
return undefined;
}
return this.nodes[attr][this.nodes[attr].length];
},
/**
* Return the attribute value of the first node for the given attribute.
* Returns undefined if there is no node for that attribute.
*/
firstNodeVal : function(attr, defaultVal) {
var node = this.firstNode(attr);
return (node !== undefined ? node.getAttribute(this.prefix + attr) : defaultVal);
}
}
module.exports = dom;