/* TODO :
- invent syntax for IF blocks (first/last, odd/even)
*/
GvaScript.Repeat = {
//-----------------------------------------------------
// Public methods
//-----------------------------------------------------
init: function(elem) {
this._init_repeat_elements(elem);
},
add: function(repeat_name, count) {
if (count == undefined) count = 1;
// get repeat properties
var placeholder = this._find_placeholder(repeat_name);
var repeat = placeholder.repeat;
var path_ini = repeat.path;
// regex substitutions to build html for the new repetition block (can't
// use Template.replace() because we need structured namespaces)
var regex = new RegExp("#{" + repeat.name + "\\.(\\w+)}", "g");
var replacement = function ($0, $1){var s = repeat[$1];
return s == undefined ? "" : s};
while (count-- > 0 && repeat.count < repeat.max) {
// increment the repetition block count and update path
repeat.ix = repeat.count++; // invariant: count == ix + 1
repeat.path = path_ini + "." + repeat.ix;
// compute the HTML
var html = repeat.template.replace(regex, replacement);
// insert into the DOM
placeholder.insert({before:html});
var insertion_block = $(repeat.path);
// repetition block gets an event
placeholder.fireEvent("Add", insertion_block);
// deal with nested repeated sections
this._init_repeat_elements(insertion_block, repeat.path);
// restore initial path
repeat.path = path_ini;
}
return repeat.count;
},
remove: function(repetition_block, live_update) {
// default behavior to live update all blocks below
// the removed block
if(typeof live_update == 'undefined') live_update = true;
// find element, placeholder and repeat info
var elem = $(repetition_block);
elem.id.match(/(.*)\.(\d+)$/);
var repeat_name = RegExp.$1;
var remove_ix = RegExp.$2;
var placeholder = this._find_placeholder(repeat_name);
var max = placeholder.repeat.count;
// fire onRemove event
// remove block from DOM
var block = $(repeat_name + "." + remove_ix);
placeholder.fireEvent("Remove", block);
block.remove();
// if live_update
if(live_update) {
// update repeat.count as first block has been deleted
placeholder.repeat.count -= 1;
// remove all blocks below
var _start_ix = remove_ix;
while(++_start_ix < max) {
block = $(repeat_name + "." + _start_ix);
block.remove();
placeholder.repeat.count -= 1;
}
// re-add the blocks above so that they will be renumbered
var n_add = max - remove_ix - 1;
if (n_add > 0) this.add(placeholder, n_add);
}
},
//-----------------------------------------------------
// Private methods
//-----------------------------------------------------
_find_placeholder: function(name) {
if (typeof name == "string" && !name.match(/.placeholder$/))
name += ".placeholder";
var placeholder = $(name);
if (!placeholder) throw new Error("no such element: " + name);
return placeholder;
},
_init_repeat_elements: function(elem, path) {
elem = $(elem);
if (elem) {
var elements = this._find_repeat_elements(elem);
for (var i = 0; i < elements.length; i++) {
this._init_repeat_element(elements[i], path);
}
}
},
_find_repeat_elements: function(elem) {
var result = [];
// navigate DOM, do not recurse under "repeat" nodes
for (var child = elem.firstChild; child; child = child.nextSibling) {
var has_repeat = child.nodeType == 1 && child.getAttribute('repeat');
result.push(has_repeat ? child : this._find_repeat_elements(child));
}
return result.flatten();
},
_init_repeat_element: function(element, path) {
element = $(element);
path = path || element.getAttribute('repeat-prefix');
// number of initial repetition blocks
var n_blocks = element.getAttribute('repeat-start');
if (n_blocks == undefined) n_blocks = 1;
// hash to hold all properties of the repeat element
var repeat = {};
repeat.name = element.getAttribute('repeat');
repeat.min = element.getAttribute('repeat-min') || 0;
repeat.max = element.getAttribute('repeat-max') || 99;
repeat.count = 0;
repeat.path = (path ? path + "." : "") + repeat.name;
// create a new element (placeholder for new insertion blocks)
var placeholder_tag = element.tagName.match(/^(TR|TD|TBODY|THEAD|TH)$/i)
? element.tagName
: 'SPAN';
var placeholder = document.createElement(placeholder_tag);
placeholder.id = repeat.path + ".placeholder";
placeholder.fireEvent = GvaScript.fireEvent;
element.parentNode.insertBefore(placeholder, element);
// take this elem out of the DOM and into a string ...
{
// a) force the id that will be needed in the template)
element.id = "#{" + repeat.name + ".path}";
// b) remove "repeat*" attributes (don't want them in the template)
var attrs = element.attributes;
var repeat_attrs = [];
for (var i = 0; i < attrs.length; i++) {
var name = attrs[i].name;
if (name.match(/^repeat/i)) repeat_attrs.push(name);
}
repeat_attrs.each(function(name){element.removeAttribute(name, 0)});
// c) keep it as a template string and remove from DOM
repeat.template = Element.outerHTML(element);
element.remove();
}
// store all properties within the placeholder
placeholder.repeat = repeat;
// create initial repetition blocks
this.add(placeholder, n_blocks);
}
};