1
0
mirror of https://github.com/Jermolene/TiddlyWiki5 synced 2024-07-07 12:34:22 +00:00
TiddlyWiki5/core/modules/treenodes/macro.js
2012-06-19 18:01:39 +01:00

236 lines
6.7 KiB
JavaScript

/*\
title: $:/core/modules/treenodes/macro.js
type: application/javascript
module-type: treenode
Macro node, used as the base class for macros
\*/
(function(){
/*jshint node: true, browser: true */
/*global $tw: false */
"use strict";
var Node = require("./node.js").Node;
/*
Construct a renderer node representing a macro invocation
macroName: name of the macro
options: see below
The options available are:
srcParams: a string or a hashmap of parameters (each can be a string, or a fn(tiddler,wiki) for evaluated parameters)
content: optional array of child nodes
wiki: reference to the WikiStore associated with this macro
dependencies: optional Dependencies object representing the dependencies of this macro
isBlock: true if this macro is being used as an HTML block
classes: array of classes to be assigned to the macro
Note that the dependencies will be evaluated if not provided.
*/
var Macro = function(macroName,options) {
options = options || {};
var MacroClass = options.wiki ? options.wiki.macros[macroName] : null; // Get the macro class
if(this instanceof Macro) {
// Save the details
this.macroName = macroName;
this.srcParams = options.srcParams || {};
this.content = options.content || [];
this.wiki = options.wiki;
this.dependencies = options.dependencies;
this.isBlock = options.isBlock;
this.classes = options.classes;
// Parse the macro parameters if required
if(typeof this.srcParams === "string") {
this.srcParams = this.parseMacroParamString(this.srcParams);
}
// Evaluate the dependencies if required
if(macroName && !this.dependencies) {
this.dependencies = this.evaluateDependencies();
}
// Get a reference to the static information about this macro
if(MacroClass) {
this.MacroClass = MacroClass;
this.info = MacroClass.prototype.info;
}
} else {
// If Macro() has been called without 'new' then instantiate the right macro class
if(!MacroClass) {
throw "Unknown macro '" + macroName + "'";
}
return new MacroClass(macroName,options);
}
};
Macro.prototype = new Node();
Macro.prototype.constructor = Macro;
/*
Evaluate the dependencies of this macro invocation. If the macro provides an `evaluateDependencies` method
then it is invoked to evaluate the dependencies. Otherwise it generates the dependencies based on the
macro parameters provided
*/
Macro.prototype.evaluateDependencies = function() {
// Figure out the dependencies from the metadata and parameters
var dependencies = new $tw.Dependencies();
if(this.info.dependentAll) {
dependencies.dependentAll = true;
}
if(this.info.dependentOnContextTiddler) {
dependencies.dependentOnContextTiddler = true;
}
for(var m in this.info.params) {
var paramInfo = this.info.params[m];
if(m in this.srcParams && paramInfo.type === "tiddler") {
if(typeof this.srcParams[m] === "function") {
dependencies.dependentAll = true;
} else {
dependencies.addDependency(this.srcParams[m],!paramInfo.skinny);
}
}
}
return dependencies;
};
Macro.prototype.parseMacroParamString = function(paramString) {
/*jslint evil: true */
var params = {},
args = new $tw.utils.ArgParser(paramString,{defaultName: "anon", cascadeDefaults: this.info.cascadeDefaults}),
self = this,
insertParam = function(name,arg) {
if(arg.evaluated) {
params[name] = eval("(function(tiddler,wiki) {return " + arg.string + ";})");
} else {
params[name] = arg.string;
}
};
for(var m in this.info.params) {
var param = this.info.params[m],
arg;
if("byPos" in param && args.byPos[param.byPos] && (args.byPos[param.byPos].n === "anon" || args.byPos[param.byPos].n === m)) {
arg = args.byPos[param.byPos].v;
insertParam(m,arg);
} else {
arg = args.getValueByName(m);
if(!arg && param.byName === "default") {
arg = args.getValueByName("anon");
}
if(arg) {
insertParam(m,arg);
}
}
}
return params;
};
Macro.prototype.hasParameter = function(name) {
return $tw.utils.hop(this.params,name);
};
Macro.prototype.cloneContent = function() {
var contentClones = [];
for(var t=0; t<this.content.length; t++) {
contentClones.push(this.content[t].clone());
}
return contentClones;
};
Macro.prototype.clone = function() {
return new this.MacroClass(this.macroName,{
srcParams: this.srcParams,
content: this.cloneContent(),
wiki: this.wiki,
isBlock: this.isBlock,
dependencies: this.dependencies,
classes: this.classes
});
};
Macro.prototype.execute = function(parents,tiddlerTitle) {
parents = parents || [];
// Evaluate macro parameters to get their values
this.params = {};
var tiddler = this.wiki.getTiddler(tiddlerTitle);
if(!tiddler) {
tiddler = {title: tiddlerTitle};
}
for(var p in this.srcParams) {
if(typeof this.srcParams[p] === "function") {
this.params[p] = this.srcParams[p](tiddler.fields,this.wiki);
} else {
this.params[p] = this.srcParams[p];
}
}
// Save the context info for use when we're refreshing it
this.tiddlerTitle = tiddlerTitle;
this.parents = parents;
// Execute the macro to generate its node and children, and recursively execute them
this.child = this.executeMacro.call(this);
};
Macro.prototype.render = function(type) {
return this.child ? this.child.render(type) : "";
};
Macro.prototype.renderInDom = function(parentDomNode,insertBefore) {
if(this.child) {
this.child.renderInDom(parentDomNode,insertBefore);
this.postRenderInDom();
}
};
Macro.prototype.postRenderInDom = function() {
// Do nothing, individual macros can override
};
Macro.prototype.refresh = function(changes) {
var t,
self = this;
// Check if any of the dependencies of this macro node have changed
if(this.dependencies.hasChanged(changes,this.tiddlerTitle)) {
// Re-execute the macro if so
this.execute(this.parents,this.tiddlerTitle);
} else {
// Refresh the child node
if(this.child) {
this.child.refresh(changes);
}
}
};
/*
Macros that need special refreshing should override this function
*/
Macro.prototype.refreshInDom = function(changes) {
// Check if any of the dependencies of this macro node have changed
if(this.dependencies.hasChanged(changes,this.tiddlerTitle)) {
// Re-execute the macro if so
// Macros can only auto-refresh if on of their descendent child has a DOM node
var child = this.child;
while(!child.domNode && child.child) {
child = child.child;
}
var parentDomNode = child.domNode.parentNode,
insertBefore = child.domNode.nextSibling;
parentDomNode.removeChild(child.domNode);
this.execute(this.parents,this.tiddlerTitle);
this.renderInDom(parentDomNode,insertBefore);
} else {
// Refresh any child
if(this.child) {
this.child.refreshInDom(changes);
}
}
};
Macro.prototype.addClass = function(className) {
this.classes = this.classes || [];
$tw.utils.pushTop(this.classes,className.split(" "));
};
exports.Macro = Macro;
})();