Extend HTML templates? - javascript

I am creating a web application framework to be used by other groups in my department to standardize the UI of our web apps. It's written in javascript using HTML templating through underscore.js. In order for the app to be totally extensible however, I'd like them to be able to extend HTML templates as they see fit without modifying the source.
Source
templates.html
...
<script type="text/template" id="fooTemplate">
<div class="foo">
</div>
</script>
<script type="text/template" id="barTemplate">
<p>Bar!</p>
</script>
...
Implementation
newTemplates.html
...
<!-- Only overwrite foo, not bar !-->
<script type="text/template" id="fooTemplate">
<ul class="foo">
<li class="bar">Blah!</li>
</ul>
</script>
...
Is there a way to intuitively enable users to extend HTML templates without forcing them to overwrite the file and copy/paste the templates they're not modifying?

You're not going to be able to use id attributes to identify your templates without a bunch of server-side processing to take care of the uniqueness issues. But you can use classes to identify your templates.
If you mash all your template HTML files together in override order ("base" templates first, "subtemplates" after) and use class attributes to identify the templates:
<!-- templates.html -->
<script type="text/template" id="fooTemplate">
<div class="foo">
</div>
</script>
<script type="text/template" id="barTemplate">
<p>Bar!</p>
</script>
<!-- newTemplates.html -->
<script type="text/template" id="fooTemplate">
<ul class="foo">
<li class="bar">Blah!</li>
</ul>
</script>
Then you can use things like
var foo = _.template($('.fooTemplate:last').html());
var bar = _.template($('.barTemplate:last').html());
to access your templates.
Demo: http://jsfiddle.net/ambiguous/gYHkF/
You could also stick with ids and try to load templates from newTemplates.html first and fallback to templates.html if you don't find it. If you load the template files into two separate variables but don't insert them into the DOM:
var $base = $('stuff from templates.html');
var $subs = $('stuff from newTemplates.html');
Then add a simple function to look for templates in $subs before $base:
function tmpl(id) {
var $t = $subs.filter('#' + id);
if($t.length)
return _.template($t.html());
return _.template($base.filter('#' + id).html());
}
Then you could do this:
var foo = tmpl('fooTemplate');
var bar = tmpl('barTemplate');
and The Right Thing would happen.
Demo: http://jsfiddle.net/ambiguous/EhhsL/
This approach also makes it easy to cache the compiled templates and not only avoid double lookups but avoid compiling the same thing over and over again:
function tmpl(id) {
if(tmpl.cache.hasOwnProperty(id))
return tmpl.cache[id];
var $t = $subs.filter('#' + id);
if($t.length)
return tmpl.cache[id] = _.template($t.html());
return tmpl.cache[id] = _.template($base.filter('#' + id).html());
}
tmpl.cache = { };
Demo: http://jsfiddle.net/ambiguous/YpcJu/

Related

jQuery selection problems

I'm trying to use the code of this example (cropper example) of images cropper application in an AngularJS view, but it didn't work. I think the problem is in the elements selection.
This is what I'm doing:
Main application HTML
<div class="view-container layer">
<div class="container content" ng-view>
<!-- load the content of the view--!>
</div>
</div>
The view
It is the code in the body section of the example (view). I omit the whole code; I'm just including the main parts.
<div class="container" id="crop-avatar">
<!-- the div content --!>
</div>
The JavaScript selectors
function CropAvatar($element) {
this.$container = $element;
this.$avatarView = this.$container.find('.avatar-view');
this.$avatar = this.$avatarView.find('img');
this.$avatarModal = this.$container.find('#avatar-modal');
this.$loading = this.$container.find('.loading');
this.$avatarForm = this.$avatarModal.find('.avatar-form');
this.$avatarUpload = this.$avatarForm.find('.avatar-upload');
this.$avatarSrc = this.$avatarForm.find('.avatar-src');
this.$avatarData = this.$avatarForm.find('.avatar-data');
this.$avatarInput = this.$avatarForm.find('.avatar-input');
this.$avatarSave = this.$avatarForm.find('.avatar-save');
this.$avatarBtns = this.$avatarForm.find('.avatar-btns');
this.$avatarWrapper = this.$avatarModal.find('.avatar-wrapper');
this.$avatarPreview = this.$avatarModal.find('.avatar-preview');
this.init();
}
And CropAvatar is initialized as:
$(function () {
return new CropAvatar($('#crop-avatar'));
});
If I used the code in this form, it didn't work at all, but if I put all the HTML code into the <body></body> tags of mi app.html it works fine. It's for this reason that I think it is a selectors problems.

Backbone: getting the html code of a view?

In order to write the HTML code of social icons (Twitter, Linkedin, etc) to a textarea so that the user can use that code elsewhere, I would like to get the HTML code of the view element, but I'm having some issues. To help illustrate this better, here is the code that creates the view:
define(function(require, exports, module) {
var _ = require('underscore');
var GridControlView = require('pb/views/grid-control');
var SocialiconsControlDialog = require('pb/views/socialicons-control-dialog');
var template = require('text!pb/templates/socialicons-grid-control.html');
var SocialiconsGridControlView = GridControlView.extend({
template: _.template(template)
,templateVars: {
partials: {
facebook: require('text!pb/templates/socialicons-grid-control-facebook.html')
,twitter: require('text!pb/templates/socialicons-grid-control-twitter.html')
,googleplus: require('text!pb/templates/socialicons-grid-control-googleplus.html')
,pinterest: require('text!pb/templates/socialicons-grid-control-pinterest.html')
,linkedin: require('text!pb/templates/socialicons-grid-control-linkedin.html')
}
}
,control_dialog: SocialiconsControlDialog
});
return SocialiconsGridControlView;
});
And, for example, the Linkedin template looks like this:
<script src="//platform.linkedin.com/in.js?<%- t.cache_buster %>" type="text/javascript">lang: en_US</script>
<script type="IN/Share" data-counter="<%- t.linkedin_option_countmode %>"></script>
What I would like to retrieve, is the parsed template code as text, something such as:
<script type="text/javascript" src="//platform.linkedin.com/in.js?0.4670609195438331">
<script data-counter="top" type="IN/Share+init">
But using something such as:
control_view.render().$el.innerHTML;, control_view.render().$el.html().text() or control_view.render().$el.html().replace(/<\/?[a-z][a-z0-9]*[^<>]*>/ig, ""); doesn't return text; it returns the full HTML, and produces a Linkedin icon (when I just want the text to be written to a textarea).
Any thoughts?
Update **
I noticed that the code control_view.render().$el is working correctly on other places of the application, and returning HTML code, but for some reason in this view where I'm trying it doesn't. The code seems to break at:
$control = control_view.render().el;
and in the console I get an error which is:
TypeError: t is undefined - underscore-min.js (line 3)
Use the .outerHTML property of the $el.
var html = $('<script type="text/javascript" src="//platform.linkedin.com/in.js?0.4670609195438331">' +
'<script data-counter="top" type="IN/Share+init">');
var text = html[0].outerHTML;
$('textarea').val(text);
jsFiddle

External Javascript File: Correct way to pass configuration parameters

I have an external javascript file called test.js as seen below. This file needs user configuration parameters passed to it, in this case user and show values.
<script type="text/javascript">
<!--//
user = '123';
show = 'appts';
//-->
</script>
<script src="{{ STATIC_URL }}js/widgets/test.js" type="text/javascript"></script>
Above is currently how I tell 3rd parties to add the script to their own site. However, I cannot help to feel this is a bad way to pass these values i.e. clashes.
Is the way I have done it acceptable? Is there a better way?
A simple answer would be to append something to your variable names, such as:
<script type="text/javascript">
<!--//
widget_test_12331_user = '123';
widget_test_12331_show = 'appts';
//-->
</script>
You can stick to namespace convention "reverted domain":
<script type="text/javascript">
<!--//
if (!org) var org = {};
if (!org.mylibrary_domain) org.mylibrary_domain = {};
if (!org.mylibrary_domain.settings) org.mylibrary_domain.settings = {};
org.mylibrary_domain.settings.user = '123';
org.mylibrary_domain.settings.show = 'appts';
//-->
</script>
<script src="{{ STATIC_URL }}js/widgets/test.js" type="text/javascript"></script>
You can use the concept of javascript namespace (How do I declare a namespace in JavaScript?).
so you can add another js file named testConfig.js, which include :
var yourAppNamespaceTestConfig = {
user: function(){ return '123' ;} ,
show: function(){ return 'appts';}
};
then inside test.js, you can read the config by:
var user = yourAppNamespaceTestConfig.user();
var show = yourAppNamespaceTestConfig.show();
And if you're more about OO, try Coffeescript (http://coffeescript.org/). They introduce OO to your javascript.

How to load external html template?

Given HTML code such :
<!-- 2. Anchor -->
<div id="anchor">This div is the <b>#anchor</b>.</div>
<!-- 3. Template -->
<script id="tpl" type="text/template">
{{#people}}
<div><img src="{{photo}}"><b>{{family}} {{name}}</b> — {{title}}, {{place}} : {{introduction}}.</div>
{{/people}}
</script>
Given JS/Handlebars such as :
<!--4. Handlebars.js slingshot -->
//4a.function creation
var slingshot = function (url, tplId, anchor) {
$.getJSON(url, function(data) {
var template = $(tplId).html();
var stone = Handlebars.compile(template)(data);
$(anchor).append(stone);
});
}
slingshot('data.json', '#tpl', '#anchor'); // since url = 'data.json' , we can use both notations.
How to externalize the my 3. Template (#tpl) into a proper .txt text file (or other extension) ? How to load it back ? so I may use the same template into various .html webpages.
Full code : http://bl.ocks.org/hugolpz/8075193 / http://bl.ocks.org/hugolpz/raw/8075193/
Put the following template content into a file named test.handlebars
{{#people}}
<div><img src="{{photo}}">
<b>
{{family}} {{name}}
</b> — {{title}},
{{place}} : {{introduction}}.
</div>
{{/people}}
Write a function which will use the template as below
function getTemplate(name) {
if (Handlebars.templates === undefined || Handlebars.templates[name] === undefined) {
$.ajax({
url : name + ".handlebars",
success : function(data) {
if (Handlebars.templates === undefined) {
Handlebars.templates = {};
}
Handlebars.templates[name] = Handlebars.compile(data);
},
async : false
});
}
return Handlebars.templates[name];
}
In the main program you can write the below statement to insert the template contents into div with id="anchor", as shown below
var Template = getTemplate("test")
this.$("#anchor).append(Template(data));
where data is the contents of a json file or some db query output which will give you the values meant for the following attributes in json format
people, twitter, name, family, photo, title, place, introduction
I'm assuming you have already compiled your template. So you can use the technique I have described in Bootstrapping Multiple Instances of an HandlebarsJS Template Into a Page.
Hook and libs
Place this in your index.html:
<div class="hook" data-json="data/whatever.json"></div>
and the JavaScript libs
<!-- Helper to inject data-set in templates instance -->
<script src="scripts/template-loader.js"></script>
<!-- Get the (compiled) template -->
<script src="scripts/myTemplate.hbs.js"></script>
template-loader.js helper
$(function(){
'use strict';
var compiledTemplate = myApp.Templates['app/templates/myTemplate.hbs'];
$('.hook').each(function(i, h){ # h = current hook
var url = $(h).data('json'); # data-set's url
$.getJSON(url).then(function (json) { # fetch data-set
var tpl = compiledTemplate( json ); # inject data into template
$(h).html(tpl); # inflate template in page
});
});
});
Please read the complete article for further details.

Trying to run a function on an array in a, for loop, using jsrender

Trying to convert very complex jQuery template code to jsRender. I have this each loop in the old code:
<script id = "imagesTemplate" type="text/x-jquery-tmpl">
{{each(i,imgUrl) twoAcross_filterOutMainImages(OfferGroup.Images)}}
<img src="{{= imgUrl}}" />
{{/each}}
</script>
<script id = "largeTemplate" type="text/x-jquery-tmpl">
{{tmpl "#imagesTemplate"}}
</script>
<div id="LARGE" class="mainContent"></div>
<script>
currentOffer = offerGroups[0].Groups[0];
$( "#LARGE" ).html( $( "#largeTemplate" ).render( currentOffer ) );
</script>
I have edited it to look like this:
{{for ~filterOutMainImages(Images) tmpl="#imagesTemplate"/}}
<div id="LARGE" class="mainContent"></div>
<script>
currentOffer = offerGroups[0].Groups[0];
$( "#LARGE" ).html( $( "#largeTemplate" ).render( currentOffer ) );
</script>
But it does not work. If I change it to be:
<script id = "imagesTemplate" type="text/x-jquery-tmpl">
<img src="{{= imgUrl}}" />
</script>
<script id = "largeTemplate" type="text/x-jquery-tmpl">
{{for Images tmpl="#imagesTemplate"/}}
</script>
<div id="LARGE" class="mainContent"></div>
<script>
currentOffer = offerGroups[0].Groups[0];
$( "#LARGE" ).html( $( "#largeTemplate" ).render( currentOffer ) );
</script>
It draws the images to display but the function does not run on the image array. But if I leave the Images array without being wrapped in a function and move the for loop to inside the template it breaks.
How would I convert this scenario?
An important difference between JsRender and jQuery Templates is that JsRender does not let you access global functions or variables directly within template markup.
This is a design choice, related to security concerns, and separation of concerns. But unlike "logicless" template languages, such as Mustache, JsRender provides very powerful and flexible support for logic within the template - while still preventing random mixing of code and markup.
One way to include logic is to encapsulate it outside the template in helper functions, but in that case you need to register the helper function, either globally, or for the specific template, or else pass it in on an options object with the render call.
See www.jsviews.com/#helpers and www.jsviews.com/#samples/jsr/helpers for documentation. (There are many other samples also on the www.jsviews.com site showing use of helper functions)
So in your case you can do
function filterOutMainImages(images) { ... }
// Register helper
$.views.helpers({
filterImages: filterOutMainImages
});
var html = $("#largeTemplate").render(currentOffer);
$("#LARGE").html(html);
Or
function filterOutMainImages(images) { ... }
var html = $("#largeTemplate").render(
currentOffer,
{filterImages: filterOutMainImages} // Pass in helper
);
$("#LARGE").html(html);
Assuming your filterOutMainImages() function returns a filtered array, and with your helper registered or passed in as above, then the following templates should work:
<script id = "imagesTemplate" type="text/x-jsrender">
<img src="{{>imgUrl}}" />
</script>
<script id = "largeTemplate" type="text/x-jsrender">
{{for ~filterImages(Images) tmpl="#imagesTemplate"/}}
</script>

Categories

Resources