I am trying to build a template builder using http://ejohn.org/blog/javascript-micro-templating
My html has this script tag
<script type="text/html" id="item_tmpl">
<div>
<div class="grid_1 alpha right">
</div>
<div class="grid_6 omega contents">
<p><b><%=AdTitle%>:</b> <%=AdTitle%></p>
</div>
</div>
</script>
<script src="${URLUtils.staticURL('/js/shoptheAd.js')}"type="text/javascript"></script>
The Script contains the following code
(function(app){
if (app) {
var cache = {};
this.tmpl = function tmpl(str, data){
// Figure out if we're getting a template, or if we need to
// load the template - and be sure to cache the result.
var fn = !/\W/.test(str) ?
cache[str] = cache[str] ||
tmpl(document.getElementById(str).innerHTML) :
// Generate a reusable function that will serve as a template
// generator (and which will be cached).
new Function("obj",
"var p=[],print=function(){p.push.apply(p,arguments);};" +
// Introduce the data as local variables using with(){}
"with(obj){p.push('" +
// Convert the template into pure JavaScript
str
.replace(/[\r\t\n]/g, " ")
.split("<%").join("\t")
.replace(/((^|%>)[^\t]*)'/g, "$1\r")
.replace(/\t=(.*?)%>/g, "',$1,'")
.split("\t").join("');")
.split("%>").join("p.push('")
.split("\r").join("\\'")
+ "');}return p.join('');");
// Provide some basic currying to the user
return data ? fn( data ) : fn;
};
var sitecoresuggestions = {
"suggestions": [
{
"AdTitle": "CheckAd",
"AdDescription": "",
"AdImageUrl": "http://demo-kiehls.loreal.photoninfotech.com/~/media/Advertisement Images/emma-watson-3.ashx",
"Count": 2,
"Hit": 0
},
{
"AdTitle": "CheckAd",
"AdDescription": "",
"AdImageUrl": "http://demo-kiehls.loreal.photoninfotech.com/~/media/Advertisement Images/kate2.ashx",
"Count": 2,
"Hit": 0
}
]
} ;
var show_user = tmpl("item_tmpl"), html = "";
for ( var i = 0; i < sitecoresuggestions.suggestions.length; i++ ) {
html += show_user( sitecoresuggestions.suggestions[i] );
}
console.log(html);
} else {
// namespace has not been defined yet
alert("app namespace is not loaded yet!");
}
})(app);
When the show_user = tmpl("item_tmpl") is executed
i get the error TypeError: document.getElementById(...) is null
on debugging i have figured out that due to some reason
<script type="text/html" id="item_tmpl">
<div>
<div class="grid_1 alpha right">
</div>
<div class="grid_6 omega contents">
<p><b><%=AdTitle%>:</b> <%=AdTitle%></p>
</div>
</div>
</script>
does not get loaded in the browser any ideas why it is not getting loaded even though it is included inside the head tag or any other pointers for the cause of the error
Per the post:
Quick tip: Embedding scripts in your page that have a unknown content-type (such is the case here - >the browser doesn't know how to execute a text/html script) are simply ignored by the browser - and >by search engines and screenreaders. It's a perfect cloaking device for sneaking templates into >your page. I like to use this technique for quick-and-dirty cases where I just need a little >template or two on the page and want something light and fast.
So the page doesn't actually render the HTML, and I would assume you would only have reference to it in the page so that you can extract and apply to other objects or items. And as the blogger states you would use it like:
var results = document.getElementById("results");
results.innerHTML = tmpl("item_tmpl", dataObject);
Related
I'm building an app with ES5 JS just for practice and "fun" where I store websites in localStorage then print them out on the page, i.e. a bookmarker application.
I'm getting a
TypeError: Cannot set property 'innerHTML' of null
error in the console when I run the following code:
index.html
<body onload="fetchBookmarks()">
<div class="container">
...some code
</div>
<div class="jumbotron">
<h2>Bookmark Your Favorite Sites</h2>
<form id="myForm">
...some code
</form>
</div>
<div class="row marketing">
<div class="col-lg-12">
<div id="bookmarksResults"></div> /* problem code */
</div>
</div>
<footer class="footer">
<p>© 2018 Bookmarker</p>
</footer>
</div>
<link rel="stylesheet" href="main.css">
<script src="./index.js"></script>
</body>
index.js
...someJScode that stores the websites in localStorage
function fetchBookmarks() {
var bookmarks = JSON.parse(localStorage.getItem('bookmarks'));
//Get output id
var bookmarksResults = document.getElementById('#bookmarksResults');
bookmarksResults.innerHTML = '';
for(var i = 0; i < bookmarks.length; i++) {
var name = bookmarks[i].name;
var url = bookmarks[i].url;
bookmarksResults.innerHTML += name;
}
}
now, the error is obviously because I am loading the <body> before the <div id="bookmarksResults"></div> so innerHTML responds with null
But two things here:
1) When I assign onload="fetchBookmarks()" to the <footer> element, the function doesn't run.
2) The tututorial I am following has this code almost exactly and it runs there.
I've also tried running the fetchBookmarks() function like this:
window.onload = function() {
fetchBookmarks();
function fetchBookmarks(){
...some JS code
};
}
But that returned the same
TypeError: Cannot set property 'innerHTML' of null
So I'm a bit lost here and am much more interested in figuring out why this isn't working and the theory behind it so I understand JS better (the whole point of building this app in the first place).
Any help would be appreciated! Thanks SO team.
The problem is with this line:
document.getElementById('#bookmarksResults')
You don't need to prefix the ID with # when you're using it with document.getElementById. Either you may remove the # from the method call, or use document.querySelector(), which works the same way, but support CSS-like selectors to select elements from DOM.
document.getElementById('bookmarksResults');
// OR
document.querySelector('#bookmarksResults');
You need to pass the value of the id without the #
Update from
var bookmarksResults = document.getElementById('#bookmarksResults');
to
var bookmarksResults = document.getElementById('bookmarksResults');
I am using a framework called Framework7.
In my index.html, I have some Template7 code, like this format
<script type="text/template7" id="commentsTemplate">
{{#each this}}
<div> test this template 7 code </div>
</script>
However, I want to have this part of code into an another separated file (Just like I can have many other *.js files in, say, a static folder and refer to the file by "static/*.js).
I have tried to use a typical way to import js
<script type="text/template7" id="storiesTemplate" src="js/template.js"></script>
But it doesn't work, there is also no demo/sample code in the documentation.
Any help is appreciated!
You can do it. The idea behind is to include a HTML file in a HTML file. I can tell at least 3 ways that this can happen, but personally I fully validated only the third.
First there is a jQuery next sample is taken from this thread
a.html:
<html>
<head>
<script src="jquery.js"></script>
<script>
$(function(){
$("#includedContent").load("b.html");
});
</script>
</head>
<body>
<div id="includedContent"></div>
</body>
</html>
b.html:
<p> This is my include file </p>
Another solution, I found here and doesn't require jQuery but still it's not tested: there is a small function
My solution is a pure HTML5 and is probably not supported in the old browsers, but I don't care for them.
Add in the head of your html, link to your html with template
<link rel="import" href="html/templates/Hello.html">
Add your template code in Hello.html. Than use this utility function:
loadTemplate: function(templateName)
{
var link = document.querySelector('link[rel="import"][href="html/templates/' + templateName + '.html"]');
var content = link.import;
var script = content.querySelector('script').innerHTML || content.querySelector('script').innerText;
return script;
}
Finally, call the function where you need it:
var tpl = mobileUtils.loadTemplate('hello');
this.templates.compiledTpl = Template7.compile(tpl);
Now you have compiled template ready to be used.
=======UPDATE
After building my project for ios I found out that link import is not supported from all browsers yet and I failed to make it work on iphone. So I tried method number 2. It works but as you might see it makes get requests, which I didn't like. jquery load seems to have the same deficiency.
So I came out with method number 4.
<iframe id="iFrameId" src="html/templates/template1.html" style="display:none"></iframe>
and now my loadTemplate function is
loadTemplate: function(iframeId, id)
{
var iFrame = document.getElementById(iframeId);
if ( !iFrame || !iFrame.contentDocument ) {
console.log('missing iframe or iframe can not be retrieved ' + iframeId);
return "";
}
var el = iFrame.contentDocument.getElementById(id);
if ( !el ) {
console.log('iframe element can not be located ' + id );
return "";
}
return el.innerText || el.innerHTML;
}
How about lazy loading and inserting through the prescriptions?
(function (Template7) {
"use strict";
window.templater = new function(){
var cache = {};
var self = this;
this.load = function(url)
{
return new Promise(function(resolve,reject)
{
if(cache[url]){
resolve(cache[url]);
return true;
}
if(url in Template7.templates){
resolve(Template7.templates[url]);
return true;
}
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onload = function() {
if(this.status == 200 && this.response.search('<!DOCTYPE html>') == -1){
cache[url] = Template7.compile(this.response);
resolve(cache[url]);
}else{
reject(`Template ${url} not found`);
}
};
xhr.send();
})
}
this.render = function(url, data)
{
return self.load(url)
.then(function(tpl){
return tpl(data) ;
});
}
this.getCache = function()
{
return cache;
}
}
})(Template7);
Using :
templater.render('tpl.html').then((res)=>{ //res string })
Or :
templater.load('tpl.html').then( tpl => { Dom7('.selector').html( tpl(data) ) } )
It is possible to define your templates in .js-files. The template just needs to be a string.
Refer to this [JSFiddle] (https://jsfiddle.net/timverwaal/hxetm9rc/) and note the difference between 'template1' and 'template2'
var template1 = $$('#template').html();
var template2 = '<p>Hello, my name is still {{firstName}} {{lastName}}</p>'
template1 just extracts the content of the <script> and puts it in a string.
template2 directly defines the string
I have one handlebars template but I want to include variables from two different sources in this template.
<script id="notification-menu-item" type="text/x-handlebars-template">
I have tried to make both of the sources go to the same template id. Both files have this:
var source = $("#notification-menu-item").html();
var template = Handlebars.compile(source);
But only one of sources' variable come through to the template. Is there anyway to have one template get its {{variables}} from two different sources?
Edit: The code
This is the template:
<script id="notification-menu-item" type="text/x-handlebars-template">
<div id="navmenucontainer" class="container">
<div id="navmenuv">
<ul class="nav">
<li>Topics</li>
<li>Help</li>
{{#if logged_user}}
<li>Notifications</li>
{{#if pro}}
<li>My Data</li>
{{/if}}
{{/if}}
</ul>
</div>
</div>
</script>
pro comes from one .js file and logged_user comes from a separate .js file. Is there a way for both of these variable to be used in the same template?
You'll have to centralize the rendering of the template into one function somehow if you want to composite the data before passing it into the Handlebars.compile() function. I guess you're going to have to somehow guarantee the order in which these "plugin" js files call this new function. Otherwise it turns into something really janky like this:
Example:
Class1.js
var source = $("#notification-menu-item").html();
var html = Notification.renderNotification(source, logged_user, undefined);
if (typeof html !== 'undefined') {
$('body').prepend(html);
}
Class2.js
var source = $("#notification-menu-item").html();
var html = Notification.renderNotification(source, undefined, pro);
if (typeof html !== 'undefined') {
$('body').prepend(html);
}
Notification.js
window.Notification = function() {
var logged_user = undefined;
var pro = undefined;
return {
renderNotification: function(source, user, isPro) {
if (typeof user !== 'undefined') {
logged_user = user;
}
if (typeof pro !== 'undefined') {
pro = isPro;
}
if(typeof logged_user !== 'undefined'
&& typeof pro !== 'undefined') {
var template = Handlebars.compile(source);
var html = template({logged_user: logged_user, pro: pro});
return html;
}
}
}
Obviously this is not elegant and far from maintainable. Without getting into the specifics of how Discourse works though, I'm not sure what to tell you. At render time of the template, a full object containing all the relevant data should be passed. Subsequent calls to Handlebars.compile() would require the full set of data. Maybe should consider finding a way to split these templates up and render them into separate page elements asynchronously, or look into Partials
Disclaimer: I'm not an expert on JS or logicless templates.
I'm writing a twitter aggregator and I need some help on solving the error 'Uncaught ReferenceError: sqTweetData is not defined.' It looks like console is pointing me to my for loop. I have set up a partial that is compiled and loaded in #main-content using underscore js.
For Loop Code
<!-- Main Content -->
<main class="main">
<div class="container-flex" id="main-content"></div>
</main> <!-- End Main Content -->
<!-- Current Tweet Partials -->
<script id="active-tweet-partial" type="underscore/template">
<section class="tweetFlexItem">
<% for (var i = 0; i < sqTweetData.length; i++) { %>
<div class="activeTweet">
<div class="activeTweet__avatar"><img src="<%= sqTweetData[ i ].user.profile_image_url %>"></div>
<div class="activeTweet__wrapper">
<div class="activeTweet__name"> <%= sqTweetData[ i ].user.name %> </div>
<div class="activeTweet__message"><%= sqTweetData[ i ].text %></div>
</div>
</div>
<% } %>
</section>
</script>
home.js Compiling Code
var Home = (function() {
var sqTweetData = {
user: [{
profile_image_url : "assets/avatar.png",
name : "#johnsnow"
}],
text : "Someone once said that I know nothing..."
};
console.log("this is sqTweetData", sqTweetData);
// Partials
var tweetPartial = $('#active-tweet-partial').html();
tweetPartialCompiled = _.template( tweetPartial );
// DOM Handlers
// KICKSTART VIEW
function initHome() {
// load main content
$('#main-content').html(tweetPartialCompiled( sqTweetData ));
// bind events
}
return {
init: initHome
};
})();
The console.log on line 11 works just fine, so I'm assuming my variable object is set up correctly. There just seems to be a disconnect between the partial and the rest of the javascript.
Any thoughts?
This is a scoping issue. sqTweetData says it's undefined because it's exactly that. window["sqTweetData"] does not exist. When you declare a variable outside of a function it's inserted into the global namespace, in this case the browser window is the namespace.
Since you're declaring the variable inside of home using the var keyword, the variable will only be accessible within the Home function. So you'd have to add it as either a this.sqTweetdata and return it with the object, or add a separate getTweetData() that return the variable, or something along those lines.
Check out this answer that covers scoping very comprehensively:
https://stackoverflow.com/a/500459/3629438
Yours falls under:
Advanced: Closure
var a = 1;
var six = (function() {
var a = 6;
return function() {
// JavaScript "closure" means I have access to 'a' in here,
// because it is defined in the function in which I was defined.
alert(a);
};
})();
EDIT:
In your case you would do something along the lines of
var Home = (function() {
// ....... //
function getTweetData() {
return sqTweetData;
}
return {
init: initHome,
get: getTweetData
};
})();
There are several similar questions, so I hope this is a unique problem. None of the proposed solutions on those similar questions have solved my issue. Humble apologies from this beginner if I messed up somehow.
I have an empty div on my page with I am loading using javascript with strings from an array. Currently, I have a script running on a button which reloads the entire page. I would like for that button to just reload the div with items from my javascript array.
Here is my code:
<html>
<head>
<link rel="stylesheet" href="obliqueStyle.css">
<style></style>
</head>
<link href='http://fonts.googleapis.com/css?family=Open+Sans' rel='stylesheet' type='text/css'>
<body>
<div id="wrapper">
<div id="strategyBox"></div>
<div id="button">
<a class="againbutton" onclick="buttonReload()">Again</a>
<script>
var buttonReload = function() {
document.getElementById("strategyBox").innerHTML = '<p id="strategyText">' + randomStrategy + '</p>';
}
</script>
</div>
</div>
<script src="os.js"></script>
</body>
Here is a snippet of my array and the JS (coming from the os.js file referenced in index.html) I am using to load the div initially/on refresh:
var obliqueStrategy = ["Abandon normal instruments",
"Accept advice",
"Accretion",
"A line has two sides"];
var randomStrategy = obliqueStrategy[Math.floor(Math.random() * obliqueStrategy.length)];
document.getElementById("strategyBox").innerHTML = '<p id="strategyText">' + randomStrategy + '</p>';
I've tried calling the same javascript as a function in script in the html like this:
<div id="button">
<a class="againbutton" onclick="buttonReload()">Again</a>
<script>
var buttonReload = function() {
document.getElementById("strategyBox").innerHTML = '<p id="strategyText">' + randomStrategy + '</p>';
}
</script>
</div>
I've tried using the jQuery AJAX load function like this:
<script>
$(function() {
$("#againbutton").on("click", function() {
$("#strategyBox").load("index.html")
return false;
})
})
</script>
I've played around with variations of the above and tried a couple other things that I'm forgetting exactly how and what I did, so I can't include them. I've really hit a wall on this even though it seems profoundly simple.
Thanks in advance for any help.
Here's one method: http://jsfiddle.net/kxqcws07/
HTML
<div id="wrapper">
<div id="strategyBox"><p id="strategyText"></p></div>
<div>
<input type="button" class="againbutton" value="Again">
</div>
</div>
Javascript
//wrapping your logic in a namespace helps reduce the chances of naming collisions of functions and variables between different imported js files
var localNameSpace = function() {
//private array containing our strings to randomly select
var obliqueStrategy = [
"Abandon normal instruments"
, "Accept advice"
, "Accretion"
, "A line has two sides"
];
var api = {
//bindButtonAction binds the generateRandomStrategy function to the click event of the againbutton
bindButtonAction: function() {
$('#wrapper .againbutton').click(api.generateRandomStrategy);
}
, generateRandomStrategy: function() {
//get the position of one of the string randomly
//Math.random() returns a float value < 1 so multiplying it by 100 gets us a range of (0.* - 99.*)
//then we Math.floor() that to get rid of the float value and keep just the integer part
//finally we modulus it with the length of the string array
//if you are unfamiliar with modulus, what it does is gives you the remainder of a division. for instance 10 / 3 gives you 3 with a remainder of 1, so 10 % 3 would be just 1.
//what this does for us is keeps the random offset of our within the bounds of the array length (0 to length -1)
var randomOffset = Math.floor(Math.random() * 100) % obliqueStrategy.length;
//finally once we have the offset, we set the html to the string at the position in the array
$('#wrapper #strategyBox #strategyText').html( obliqueStrategy[randomOffset] );
}
};
return api;
}();
$(document).ready(function() {
//here we call the bind action so the button will work, but we also explicitly call the generateRandomStrategy function so the page will preload with a random string at the start
localNameSpace.bindButtonAction();
localNameSpace.generateRandomStrategy();
});