jQuery.fn.extend( {

	render: function( values, options )
	{
		var re_cache = {};
		var clone_list = null;
		options = options || [];
		values = $.makeArray(values);

		this.each( function (i)
		{
			var render = function (tvals,tmplnode,options) 
			{
				var tmp_node = $("<div></div>").append( tmplnode.clone() );
				var tmpl = tmp_node.html();
				var selectors = {};

				$.each(tvals, function(k,v)
				{
					//jquery selector, not string replace
					if ( k.indexOf( '$(' )==0 )
					{
						selectors[k] = v;
						return;
					}
					
					if (!re_cache[k])
					{
						re_cache[k] = new RegExp('{'+k+'}', 'gm'); 
					}

					var re = re_cache[k];
					tmpl = tmpl.replace( re, v );	
				});

				var node = $(tmpl);
				var selre = new RegExp('[\'"](.+)[\'"]','mg');

				//apply selector replaces, if any
				$.each(selectors, function(k,v)
				{
					res = selre.exec(k);
					node.find(res[1]).html(v);
				});

				if (options['beforeUpdate']) 
				{
					options['beforeUpdate'](node);		
				}

				if (options['clone'])
				{
					tmplnode.after(node);
				}
				else	
				{
					tmplnode.replaceWith(node);
				}

				if (options['afterUpdate']) 
				{
					options['afterUpdate'](node);
				}		

				return node;
			};

			var node = $(this);

			if (options['clone'])
			{
				$(values.reverse()).each( function()
				{ 
					var newnode = render(this,node,options); 

					if( !clone_list )
					{
						clone_list = $(newnode);
					}
					else
					{
						clone_list.push(newnode[0]);
					}

				});				
			}
			else
			{
				var repl = (values[i] || values[values.length-1]);
				render(repl,node,options);
			}
		});
	
		if (clone_list)
		{
			return this.pushStack(clone_list, 'render', this.selector );
		}

		return this;
	}
});
