Knockout Template with jQote2 - javascript

I've started looking into using Knockout for my team's use; I've been very pleased with the framework so far, with the exception of its ties to the jQuery Templates plugin, whose syntax I hate. Additionally (and more importantly), we've been using jQote2 for our client-side templating, so I wanted to look into creating a template engine that uses it.
I'm having difficulty with the renderTemplate function; the jQote2 library doesn't seem to know how to locate the variables passed in through the data argument. Has anyone dealt with this sort of thing before? I'm assuming it's more of a jQote2 quirk than anything with how Knockout renders its templates...
The code:
jqoteTemplateEngine = function () {
var templates = {};
this.createJavaScriptEvaluatorBlock = function (script) {
return '<%= ' + script + ' %>';
}
this.getTemplateNode = function (template) {
var templateNode = document.getElementById(template);
if (templateNode == null)
throw new Error("Cannot find template with ID of " + template);
return templateNode;
}
this.renderTemplate = function(templateId, data, options) {
var renderedMarkup = jQuery.jqote(templates[templateId], data);
return ko.utils.parseHtmlFragment(renderedMarkup);
}
this.rewriteTemplate = function (template, rewriterCallback) {
var templateNode = this.getTemplateNode(template);
templates[template] = rewriterCallback(templateNode.text);
}
this.isTemplateRewritten = function (templateId) {
return templateId in templates;
}
};
jqoteTemplateEngine.prototype = new ko.templateEngine();
ko.setTemplateEngine(new jqoteTemplateEngine());
The Gist: http://gist.github.com/1341737

I would take a look at KO 1.3. It is in beta, but is quite stable. Two important things in 1.3. There is now a native template engine, so you could choose to avoid having any dependency on an external template engine (and even jQuery). The control-flow bindings with anonymous templates make this much simpler option.
Secondly, it simplified the way that custom template engines are defined. So, if you still want to use an external engine, then it is a bit easier to get it working.
Basically, you only need to define a renderTemplate method and a createJavaScriptEvaluatorBlock if you want to be able to data-bind to variables available to the template engine. The rest is defined on a ko.templateEngine base "class".
So, your engine might simply look like:
ko.jqoteTemplateEngine = function() {};
ko.jqoteTemplateEngine.prototype = new ko.templateEngine();
ko.jqoteTemplateEngine.prototype.createJavaScriptEvaluatorBlock = function(script) {
return '<%= ' + script + ' %>';
};
ko.jqoteTemplateEngine.prototype.renderTemplateSource = function(templateSource, bindingContext, options) {
var templateText = templateSource.text();
return ko.utils.parseHtmlFragment($.jqote(templateText, bindingContext.$data));
};
ko.setTemplateEngine(new ko.jqoteTemplateEngine());
Here is a sample in use: http://jsfiddle.net/rniemeyer/yTzcF/

Related

How Does Dependency Parameter Injection Work in AngularJS?

When you initialize a controller, service, factory in Angular, you pass in an anonymous function as the the second parameter in the following format:
myApp.controller('myController', function($scope, $q){
});
How does Angular determine from the above that it needs to initialize an instance of $scopeand $q for our new controller?
Does it grab the parameter names you pass in and find the corresponding objects from the Angular library?
Here's the critical part of Injector (source):
var ARROW_ARG = /^([^\(]+?)=>/;
var FN_ARGS = /^[^\(]*\(\s*([^\)]*)\)/m;
var FN_ARG_SPLIT = /,/;
var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/;
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
function extractArgs(fn) {
var fnText = fn.toString().replace(STRIP_COMMENTS, ''),
args = fnText.match(ARROW_ARG) || fnText.match(FN_ARGS);
return args;
}
As you see, the key is analyzing the function's source(returned by Function.prototype.toString()). First, all the comments are removed. It's necessary, as one can define a function like this:
function (el /* DOMElement */, b) {...}
Next, the meaningful parts are matched both against the arrow syntax (ES6) and the traditional one. Essentially, both patterns are matching all the things within the first pair of parentheses in the function source.
This code is used, among other things, in annotate function - filling up the $inject collection:
argDecl = extractArgs(fn);
forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg) {
arg.replace(FN_ARG, function(all, underscore, name) {
$inject.push(name);
});
});
You also need to know that the Angular devs recommend to avoid the shown approach (so called Implicit Annotation), as it's not compatible with minifiers/obfuscators without tools like ng-annotate.

How to compile inline HTMLBars templates?

I have an inline template (template in JavaScript) that I used to compile like so (where temp is a Handlebars string):
var template = Handlebars.compile(temp);
template({model: results}); // Gets the HTML
I'm trying to use HTMLBars instead, but can't quite figure it out. I did the following:
var template = Ember.HTMLBars.compile(temp);
template({model: results}); // Throws an error that template isn't a function
How do I go about getting the HTML back from the HTMLBars template. I also tried:
var template = Ember.HTMLBars.compile(temp);
Ember.HtmlBars.template(template, {model: results});
Which doesn't error, but also doesn't use the model when rendering the HTML. I think I'm close, but can't quite figure out how to inject the model.
HTMLBars doesn't produce functions like Handlebars did. Handlebars was a string templating language: you compile a string to a template function, then run that function to produce a string. HTMLBars compiles a string into a template but the template doesn't produce a string, it produces DOM nodes. The simple answer is that you're not going to be able to do the same things with HTMLBars as you did with Handlebars. I suggest either adding another string templating library to your code (maybe Handlebars), or letting a view handle the HTMLBars template as seen in this question.
And if you're curious, here's what an HTMLBars template object looks like after being compiled (gotten from a JSBin console dump):
[object Object] {
blockParams: 0,
build: function build(dom) {
var el0 = dom.createDocumentFragment();
var el1 = dom.createTextNode("");
dom.appendChild(el0, el1);
var el1 = dom.createTextNode("");
dom.appendChild(el0, el1);
return el0;
},
cachedFragment: null,
hasRendered: false,
isHTMLBars: true,
isMethod: false,
isTop: true,
render: function render(context, env, contextualElement) {
var dom = env.dom;
var hooks = env.hooks, content = hooks.content;
dom.detectNamespace(contextualElement);
var fragment;
if (env.useFragmentCache && dom.canClone) {
if (this.cachedFragment === null) {
fragment = this.build(dom);
if (this.hasRendered) {
this.cachedFragment = fragment;
} else {
this.hasRendered = true;
}
}
if (this.cachedFragment) {
fragment = dom.cloneNode(this.cachedFragment, true);
}
} else {
fragment = this.build(dom);
}
if (this.cachedFragment) { dom.repairClonedNode(fragment,[0,1]); }
var morph0 = dom.createMorphAt(fragment,0,1,contextualElement);
content(env, morph0, context, "foo");
return fragment;
}
}

Durandal List with Composition (ASP .NET MVC)

I am trying to use Durandal to generate a list of data. Because this data will be updated by a dialog I need the list item to represent a view model. For this, view composition seems appropriate, but I must be missing something with how to do this. Note: I am using ASP .NET MVC so my pathing is a bit different. However, at this point, I am not seeing any web requests for my view nor are the debugger; lines in my View Model being hit.
Here is the code:
The HTML:
<tbody data-bind="foreach: workItems">
<!-- ko compose: {model: 'viewmodels/project/workItem', view: 'views/project/workitem'} -->
<!-- /ko -->
</tbody>
Here is the JS for the view model intended to represent each line in the list:
define(['knockout'], function(ko) {
var vm = function () {
var that = this;
this.activate = function(ctx) {
debugger;
};
};
return vm;
});
Here is the code which handles the GET request returning the data:
this.activate = function (ctx) {
var q = null;
if (ctx == undefined) {
q = that._getCurrent();
} else {
q = that._getPath(ctx);
}
q.then(function (response) {
that.workItems(response);
app.trigger('viewmodel:loadingComplete');
});
};
I have tried using system.acquire so as to add VMs to my observable array, this failed to show any results. My thinking was that I need composition so I get the binding to fire for each line. (this may be wrong). I am really looking for an example of this working. Quite perplexed that I have not been able to find anything on SO or Google.
Thanks in advance
1) Check that workItems really does contain data. Use a simple div foreach to output the results.
2) Is there a reason you are not using the Singleton pattern?
3) Return a promise from the activate to make sure the data is loaded before starting the composition.
define(['knockout'], function (ko) {
var items = ko.observableArray([]);
var activate = function() {
console.log("calling composelist:activate");
items.push({name:"Hello"});
items.push({name:"world"});
};
return {
activate: activate,
items: items
};
});
Are you trying to load different v/vm based on a url parameter? If so why not use the basic shell /routes?
Can you submit your code?

How to use JST with underscore.js?

I'm having troubles using variables that would normally be no problem with understand.js, but seemingly when you combine JST with underscore.js it seems to struggle.
var something= SD.defaultView.extend({
el: 'page',
template: JST['app/www/js/templates/sex.ejs'],
data: {
header: 'some information!!!',
image: '/img/path.jpg'
},
render: function () {
var compiled = _.template(this.template(), this.data); //I pass in the complied JST template
this.$el.html(compiled);
}
});
JST File rendered
this["JST"]["app/www/js/templates/sex.ejs"] = function (obj) {
obj || (obj = {});
var __t, __p = '', __e = _.escape;
with (obj) {
__p += ((__t = ( header )) == null ? '' : __t) + '<sexform>Hello There</sexform>';
}
return __p
};
Error
ReferenceError: header is not defined - templates.js (line 21)
...obj = {});var __t, __p = '', __e = _.escape;with (obj) {__p +=((__t = ( header )...
sex.ejs
<%= header %><sexform>Hello There</sexform>
Background Information
As expected, header is not available at the time of the reader, which is happening via grunt file with each change to my JST templates. I feel I must be implement JST's the wrong way.
But, to me this seems like the correct way of doing everything.
Of course, I am trying to use variables with underscore inside of sex.ejs
All of this code can be seen here: http://m.sexdiaries.co.uk/#wank
NB:
I can assure that this is safe for work and contains no images, even though as misleading as the url is its really not adult material, its an educational app.
You have this to define the view's template:
template: JST['app/www/js/templates/sex.ejs'],
And JST contains functions (which is, more or less, the whole point of using JST-style precompiled templates):
this["JST"]["app/www/js/templates/sex.ejs"] = function (obj) {
Then you do this:
var compiled = _.template(this.template(), this.data);
// function call ----------------------^^
Two things are wrong there:
You've already called _.template to compile the template.
this.template is the compiled template function that expects to be fed this.data.
The fix is quite simple:
var compiled = this.template(this.data);

Get calling arguments for getter in javascript

Given a javascript object like this:
var myThing = {};
Object.defineProperty(myThing, 'gen', {
'get' : function() {
// access caller name here, so I can return cool/neat stuff
}
});
I want to be able to get children of myThing.gen, but know what is being asked for in the getter.
for example:
var coolThing = myThing.gen.oh.cool;
var neatThing = myThing.gen.oh.neat;
I want the "oh.cool" or "oh.neat" part in getter, so I can make decisions based on this, and return something specific to it. I am ok with solution not working in IE, or old browsers, as it is primarily for node.
The actual purpose of this is so that I can request myThing.gen.oh.neat and have the myThing.gen getter resolve to require('./oh/neat.js') and return it.
Since require cache's, this is an efficient way to dynamically load modular functionality, and have a tidy interface (rather than just dynamically building the require where needed) without having to know the structure ahead of time.
If there is no introspection-of-name function that can get this for me, I could just do something less elegant, like this:
myThing.gen = function(name){
return require('./' + name.replace('.', '/') + '.js');
}
and do this:
neatThing = myThing.gen('oh.neat');
I don't like this syntax as much, though. I looked at chai's dynamic expect(var).to.not.be.empty stuff, but couldn't figure out how to do it completely dynamically. Maybe there is not a way.
without actually solving the problem of dynamically discovering the caller, I can do this:
var myThing = {};
Object.defineProperty(myThing, 'gen', {
'get' : function() {
return {
'oh':{
'cool': require('./oh/cool.js'),
'neat': require('./oh/neat.js')
}
};
}
});
Is there a way to do this dynamically?
You can't see what the property gen will be used for in the future, so you would need to return an object with properties that react to what the object is used for when it actually happens:
var myThing = {};
Object.defineProperty(myThing, 'gen', {
'get' : function() {
var no = {};
Object.defineProperty(no, 'cool', {
get: function(){ alert('cool'); }
});
Object.defineProperty(no, 'neat', {
get: function(){ alert('neat'); }
});
return { oh: no };
}
});
Demo: http://jsfiddle.net/UjpGZ/1/

Categories