HTML templates inside JavaScript file... What am I doing wrong? - javascript

What I'm trying to achieve is to store HTML templates for everything that needs to be generated on the fly inside a separate js file (instead of rendering it in the page).
The buildHtml function how its currently setup works fine. Where I'm stuck is what if.. I've another variable inside the template object say 'input3' and its markup is something like <div>(exact markup from commonInput)</div>
I tried using it as input 3 : '<div>' + this.commonInput + '</div>' but it turns out you cannot refer object properties from inside using this (explained here).
I could create 'input3' with full html but for big html chunks this approach is not very useful.
Looking for
solution to this specific problem
reasons if this approach is a bad idea
better alternatives
$j(document).ready(function() {
var namespace = window.namespace || {};
namespace.feature = namespace.appName || {};
namespace.feature.templates = {
input1 : '<div>'+
'<p class="abc">Hey {username}</p>'+
'</div>',
input2 : '<div>'+
'<div class="description">{description}</div>'+
'</div>',
commonInput : '<div class="common">Common code</div>'
};
namespace.feature.module = function() {
var container = $j('#container'),
t = namespace.feature.templates;
var buildHtml = function(type) {
var tmpHtml = t.input1 + t.commonInput + t.input2;
container.append(tmpHtml);
}
var init = function() {
buildHtml();
}
return {
init : init,
};
}();
namespace.feature.module.init();
});

just on the top of my head.
You could write the templates as functions that build strings.
input3 : function(param) {
return '<div>' + param + '</div>'
}
then:
var tmpHTML = t.input1 + t.input2 + t.input3(t.commonInput);
Also, i like to build my own DOM objects when building HTML. and avoid the use of hardcoded strings.
http://mg.to/2006/02/27/easy-dom-creation-for-jquery-and-prototype

I'm not sure what your exact question is, but as far as better alternatives go,
I would suggest using jQuery templates.
Here is a benchmarking page for all the different templating engines and their performance:http://jsperf.com/dom-vs-innerhtml-based-templating
You can look at the revisions to find different combinations of engines as well as run them on different browsers to see speed differences.
Update: The original jquery template project is no longer active. This is the new home for the old project: https://github.com/BorisMoore/jquery-tmpl
I would no longer recommend jquery-templates since there are much better alternatives now. The last time I checked, dustJs by linkedIn fork seems to be the most promising one.

Related

Dynamically include JavaScript modules

I am trying to dynamically load (aka 'include') a series of additional .js "module" files from a parent script.
I am doing this as part of a D3 reusable charts library which, when I publish, I actually run a Makefile which concatenates said "modules" together into a single script (and therefore removes the need for the dynamic include method). But while testing I want to load these "modules" separately (mainly to save me having to run the Makefile script each time I make a small change to one of the files).
I have figured out 3 possible solutions, and written different 'include' functions, one for each solution:
https://github.com/jamesleesaunders/d3.ez/blob/master/js/d3.ez.js
// Solution 1 - Using document.write
function includeV1(file) {
var loc = document.currentScript.src
var path = loc.substring(0, loc.lastIndexOf("/") + 1);
var src = path + file;
var type = 'text/javascript';
document.write('<' + 'script src="' + src + '"' + ' type="' + type + '"><' + '/script>');
}
// Solution 2 - Using document.createElement
function includeV2(file) {
// Method 2 - Using document.createElement
var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
var loc = document.currentScript.src
var path = loc.substring(0, loc.lastIndexOf("/") + 1);
script.src = path + file;
script.type = 'text/javascript';
head.appendChild(script);
}
// Solution 3 - Using D3
function includeV3(file) {
var loc = document.currentScript.src
var path = loc.substring(0, loc.lastIndexOf("/") + 1);
var src = path + file;
var type = 'text/javascript';
d3.select('head')
.append("script")
.attr('src', src)
.attr('type', type);
}
Called like:
include('header.js');
include('radialBarChart.js');
include('circularHeatChart.js');
include('discreteBarChart.js');
Solution 1 above seems to work fine, and does exactly what I want to, however Solution 2 and 3 do not seem to work? Even though, as far as I can tell, they are doing the same thing.
I think the possible difference is that Solutions 2 and 3 insert the additional elements to the DOM after the page has loaded (and therefore not available to the main page script when it calls for one of the module functions). Whereas I am guessing that Solution 1 adds the tags at the point where it is called (and therefore module functions are available to the main page script when called for).
I am happy if Solution 1 is the only solution, as it works, but to satisfy my curiosity and also because I am writing a D3 library I would have preferred to have used Solution 3.
Can anyone advise why Solutions 2 and 3 do not work? Or offer any amendments?
I am not looking for a jQuery solution, but there may be some jQuery experts who may know?
For reference here is the Makefile which, when publishing, I run to concatenate the "module" files into a single script:
https://github.com/jamesleesaunders/d3.ez/blob/master/Makefile
Which then generates the concatenated:
https://github.com/jamesleesaunders/d3.ez/blob/master/d3.ez.js
This has now been resolved by moving to use of ES6 modules.

Best way to represent HTML part when writing a complex jQuery plugin

I have a complex jQuery plugin to write, It does have a lot of html to show on screen and I am supposed to create them. Well, what is the best way to do the job. I can already hard code them for sure , but is there any other elegant method? is there a way to use some kind of templating?. definitely I don't want to have lot more dependency either.
You can put them in an external file(s) and load them when you need but this means plugin users will have to download all your files, instead of a single js file. Another option I used before (not for a plugin though) is to have an object inside your plugin which holds all the html you want. This makes it easier to write the plugin as the html doesn't clutter the plugin code. Also when you need to edit the html, it's in a single place. You can also put your templating code inside this object.
var pi = function() {
var self = this;
self.getNewDiv = function(){
return repo.getDiv();
}
//..... all your plug in code goes here and at the bottom
var htmlRepository = function() {
var divCode = "<div></div>";
this.getDiv = function(){
return divCode;
};
this.getSpan = function(){
return "Span Content";
}
this.getDivWithSpan = function(){
return getSpan().wrap(divCode); //etc
}
};
var repo = new htmlRepository();
}

AngularJS directive copying with getElementById(myID).innerHTML

I am trying to copy/display the content of one div (which contains directives) into another.
When I execute the below the directives show up fine but none of the functionality, e.g. ng-click, work.
The ng-click and other options work in the source Div just not when it is copied into the destination.
Also I am not getting any console errors.
Please advise.
var srcDiv = document.getElementById(id + '-content'); //contains directives + all working fine
var dstDiv = document.getElementById(id + 'x'); //contains directives but no functionality or errors
dstDiv.innerHTML = srcDiv.innerHTML;
You need to tell Angular to $compile the contents. Angular will not recognise bits of HTML being inserted into the DOM at arbitrary times.
Inject the $compile service to wherever you are doing your DOM manipulation.
var srcDiv = angular.element(document.getElementById(id + '-content'));
var dstDiv = angular.element(document.getElementById(id + 'x'));
dstDiv.html(srcDiv.html());
$compile(dstDiv)(scope);
try
...
dstDiv.innerHTML = srcDiv.innerHTML;
scope.$apply();

Templating in a Javascript-Only Application

I will be developing a large, AMD-based javascript application, structured with backbone.js and potentially require.js. I am doing research on the best way to go, and one thing I would like to get into using is a template library, particularly handlebars.js.
My problem here is that I want to make javascript-only modules that can be loaded and implemented on the fly, well after the application is loaded. Templates are based on HTML tags, but I don't want to include html pages after the fact.
My question is: Is it stupid, or valid practice to mock up HTML templates as strings in your javascript, and then render them? I feel like it would kill the entire point in performance alone, but I really don't know.
This is an example of what I am talking about:
var render = function(html, model) {
var tmpl = Handlebars.compile(html);
return tmpl(model);
}
$(document).ready(function() {
var template = '<div class="entry">' +
'<h1>{{title}}</h1>' +
'<div class="body">{{body}}</div>' +
'</div>';
var model = {
title: 'I love templating,',
body: 'And so do you!'
}
template = render(template, model);
$(document.body).append(template);
});
Is this terrible practice, or is there a better way to implement this in a javascript-only application?
Template are used to separate html from javascript code.
I suggest you to look at requireJS text plugin to load your template code in an AMD environment.

Auto-load/include for JavaScript

I have file called common.js and it's included in each page of my site using <script />.
It will grow fast as my sites functionality will grow (I hope; I imagine). :)
Lets example I have a jQuery event:
$('#that').click(function() {
one_of_many_functions($(this));
}
For the moment, I have that one_of_many_functions() in common.js.
Is it somehow possible that JavaScript automatically loads file one_of_many_functions.js when such function is called, but it doesn't exist? Like auto-loader. :)
The second option I see is to do something like:
$('#that').click(function() {
include('one_of_many_functions');
one_of_many_functions($(this));
}
That not so automatically, but still - includes wanted file.
Is any of this possible? Thanks in an advice! :)
It is not possible to directly auto-load external javascripts on demand. It is, however, possible to implement a dynamic inclusion mechanism similar to the second route you mentioned.
There are some challenges though. When you "include" a new external script, you aren't going to be able to immediately use the included functionality, you'll have to wait until the script loads. This means that you'll have to fragment your code somewhat, which means that you'll have to make some decisions about what should just be included in the core vs. what can be included on demand.
You'll need to set up a central object that keeps track of which assets are already loaded. Here's a quick mockup of that:
var assets = {
assets: {},
include: function (asset_name, callback) {
if (typeof callback != 'function')
callback = function () { return false; };
if (typeof this.assets[asset_name] != 'undefined' )
return callback();
var html_doc = document.getElementsByTagName('head')[0];
var st = document.createElement('script');
st.setAttribute('language', 'javascript');
st.setAttribute('type', 'text/javascript');
st.setAttribute('src', asset_name);
st.onload = function () { assets._script_loaded(asset_name, callback); };
html_doc.appendChild(st);
},
_script_loaded: function (asset_name, callback) {
this.assets[asset_name] = true;
callback();
}
};
assets.inlude('myfile.js', function () {
/* do stuff that depends on myfile.js */
});
Sure it's possible -- but this can become painful to manage. In order to implement something like this, you're going to have to maintain an index of functions and their corresponding source file. As your project grows, this can be troublesome for a few reasons -- the 2 that stick out in my mind are:
A) You have the added responsibility of maintaining your index object/lookup mechanism so that your scripts know where to look when the function you're calling cannot be found.
B) This is one more thing that can go wrong when debugging your growing project.
I'm sure that someone else will mention this by the time I'm finished writing this, but your time would probably be better spent figuring out how to combine all of your code into a single .js file. The benefits to doing so are well-documented.
I have created something close to that a year ago. In fact, I have found this thread by search if that is something new on the field. You can see what I have created here: https://github.com/thiagomata/CanvasBox/blob/master/src/main/New.js
My project are, almost 100% OOP. So, I used this fact to focus my solution. I create this "Class" with the name "New" what is used to, first load and after instance the objects.
Here a example of someone using it:
var objSquare = New.Square(); // Square is loaded and after that instance is created
objSquare.x = objBox.width / 2;
objSquare.y = objBox.height / 2;
var objSomeExample = New.Stuff("some parameters can be sent too");
In this version I am not using some json with all js file position. The mapping is hardcore as you can see here:
New.prototype.arrMap = {
CanvasBox: "" + window.MAIN_PATH + "CanvasBox",
CanvasBoxBehavior: "" + window.MAIN_PATH + "CanvasBoxBehavior",
CanvasBoxButton: "" + window.MAIN_PATH + "CanvasBoxButton",
// (...)
};
But make this more automatic, using gulp or grunt is something what I am thinking to do, and it is not that hard.
This solution was created to be used into the project. So, the code may need some changes to be able to be used into any project. But may be a start.
Hope this helps.
As I said before, this still is a working progress. But I have created a more independent module what use gulp to keep it updated.
All the magic que be found in this links:
https://github.com/thiagomata/CanvasBox/blob/master/src/coffee/main/Instance.coffee
https://github.com/thiagomata/CanvasBox/blob/master/src/node/scripts.js
https://github.com/thiagomata/CanvasBox/blob/master/gulpfile.js
A special look should be in this lines of the Instance.coffee
###
# Create an instance of the object passing the argument
###
instaceObject = (->
ClassElement = (args) ->
window[args["0"]].apply this, args["1"]
->
ClassElement:: = (window[arguments["0"]])::
objElement = new ClassElement(arguments)
return objElement
)()
This lines allows me to initialize a instance of some object after load its file. As is used in the create method:
create:()->
#load()
return instaceObject(#packageName, arguments)

Categories

Resources