I am using lodash templates to render html templates client side. There are many html templates which are being repeated. So, I decided to call the repeating template in another template. For example:
dummy.html
<%= _.template(templates['button'])({ title: "click me" }) %>
The above approach works but as I am calling _.template to render a button again and again, I thought to make a global function as shown below:
dummy.js
var sb = {
setButton: function(data){
data = data || {};
return _.template(templates['button'])(data);
},
/* other functions */
}
and then call in the dummy.html as:
<%= sb.setButton({ title: "click me" }) %>
But this isn't working. (It just doesn't render)
What I am doing wrong here?
EDIT:
I placed console.log(this) in setButton function. it was not logging anything in chrome console. Then I removed = from lodash template syntax, then it logged sb global variable.
<% sb.setButton({ title: "click me" }) %>
But still the above one is not rendering the button.
Your compiled templates don't know what sb is. Assuming you're calling your main template with something like _.template(src)(), Lodash chokes on it with an error looking like ReferenceError: sb is not defined. Pass your partials hash as an option and you will get your button:
var templates = {
button: '<button><%= title %></button>'
};
var sb = {
setButton: function(data){
data = data || {};
return _.template(templates['button'])(data);
}
};
var src = '<%= sb.setButton({ title: "click me" }) %>'
document.getElementById('result').innerHTML = _.template(src)({sb: sb});
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.0/lodash.min.js"></script>
<div id='result'></div>
If you prefer to have you partials directly available without passing them to your template, attach your global to _, for example as _.sb:
var templates = {
button: '<button><%= title %></button>'
};
_.sb = {
setButton: function(data){
data = data || {};
return _.template(templates['button'])(data);
}
};
var src = '<%= _.sb.setButton({ title: "click me" }) %>'
document.getElementById('result').innerHTML = _.template(src)();
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.0/lodash.min.js"></script>
<div id='result'></div>
You need extend data pass to template with sb, some like this:
var sb = {
setButton: function(data){
data = data || { name: "default" };
return _.template("<%= name %>")(data);
},
}
var tmpl = _.template( '<%= setButton({ name: name }) %>' );
var data = _.extend({name:"click"}, {sb});
tmpl(data)
http://jsfiddle.net/stdob/bjdw63wb/2/
Related
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
};
})();
It is literally fifth day I try to solve this.
I try to invoke a method by a button in Razor View, no redirections to other views, just invoke a simple method when button is clicked.
The script looks like:
<script>
function SubmitClick () {
var pid = $(this).data('personid');
var sid = $(this).data('surveyid');
var url = '#Url.Action("SubmitSurvey", "Person")';
$.post(url, { personid: pid, surveyid: sid }, function (data) {
alert('updated');
});
};
</script>
The button looks like:
<button class='mybutton' type='button' data-personid="#Model.Item1.Id" data-surveyid="#survey.Id" onclick="javascript:SubmitClick()">Click Me</button>
The PersonController method looks like:
public void SubmitSurvey(int personId, int surveyId) {
System.Diagnostics.Debug.WriteLine("UPDATING DATABASE");
}
The full view (this is PartialView):
<script>
function SubmitClick () {
var pid = $(this).data('personid');
var sid = $(this).data('surveyid');
var url = '#Url.Action("SubmitSurvey", "Person")';
$.post(url, { personid: pid, surveyid: sid }, function (data) {
alert('updated');
});
};
</script>
#using WebApplication2.Models
#model System.Tuple<Person, List<Survey>>
<hr />
<h1>Surveys</h1>
<input type="button" id="Coll" value="Collapse" onclick="javascript:CollapseDiv()" />
#*<p>
Number of Surveys: #Html.DisplayFor(x => Model.Item2.Count)
</p>*#
#{int i = 1;}
#foreach (var survey in Model.Item2) {
using (Html.BeginForm()) {
<h2>Survey #(i)</h2>
<p />
#Html.EditorFor(x => survey.Questions)
<button class='mybutton' type='button' data-personid="#Model.Item1.Id" data-surveyid="#survey.Id" onclick="javascript:SubmitClick()">Click Me</button>
}
i++;
<hr style="background-color:rgb(126, 126, 126);height: 5px" />
}
<hr />
The problem is that when I click the button:
I get runtime error saying that there is no definition of: "SubmitClick".
I don't see any obvious problems in your code, but given that you're handling this in a sub-optimal way, refactoring your code may solve the problem just by improving the setup.
First, don't embed your scripts directly in the view. I understand that you need to include a URL generated via one of the Razor helpers, but what I'm talking about here is using sections so that your scripts get included in a standard location in the document:
So, in your view:
#section Scripts
{
<script>
// your code here
</script>
}
And then in your layout:
<!-- global scripts like jQuery here -->
#RenderSection("Scripts", required: false)
</body>
This ensures that 1) all your JavaScript goes where it should, right before the closing body tag and 2) all your JavaScript gets run after the various global scripts that it will likely depend on (jQuery).
Second, it's usually a bad idea to define things in the global scope, such as you are doing with your SubmitClick function. If another script comes along and defines it's own SubmitClick function in the global scope, then yours gets hosed or vice versa. Instead, you want to use namespaces or closures.
Namespace
var MyNamespace = MyNamespace || {};
MyNamespace.SubmitClick = function () {
...
}
Closure
(function () {
// your code here
})();
Of course, if you use a closure like this, then you SubmitClick function truly won't exist, as it's no longer in the global scope, which brings me to...
Third, don't use the on* HTML attributes. It's far better to bind functionality to elements dynamically, for example:
(function () {
$('.mybutton').on('click', function () {
var pid = $(this).data('personid');
var sid = $(this).data('surveyid');
var url = '#Url.Action("SubmitSurvey", "Person")';
$.post(url, { personid: pid, surveyid: sid }, function (data) {
alert('updated');
});
});
})();
Now, you've got zero scope pollution and behavior is bound where behavior is defined, instead of tightly-coupling your HTML and JavaScript.
I'm trying to use grunt-contrib-jst to compile my underscore templates, but it seems to not be rendering / preserving the variables properly. Here's what a template looks like normally:
<script id="header-template" type="text/template">
<h4><%= subhead %></h4>
<h1><span class="head-text"><%= head %></span>
<span class="subtext"><%= subtext %></span>
</h1>
<p></p>
</script>
and here's what gets rendered via grunt:
this["JST"] = this["JST"] || {};
this["JST"]["templates/header.html"] = function(obj) {
obj || (obj = {});
var __t, __p = '', __e = _.escape;
with (obj) {
__p += '<h4>' +
((__t = ( subhead )) == null ? '' : __t) +
'</h4>\n<h1><span class="head-text">' +
((__t = ( head )) == null ? '' : __t) +
'</span>\n <span class="subtext">' +
((__t = ( subtext )) == null ? '' : __t) +
'</span>\n</h1>\n<p></p>';
}
return __p
};
Here's how I set up my grunt task:
jst: {
compile: {
files: {
"scripts/templates/all.js": ["templates/*.html"]
}
}
}
and when I attempt to utilize the template:
var app = app || {};
app.HeaderView = Backbone.View.extend({
el: '#header-container',
//template: _.template( $( '#header-template' ).html() ),
template: JST['templates/header.html'](), //http://stackoverflow.com/questions/8366733/external-template-in-underscore
initialize: function( templateContent ) {
this.render(templateContent);
},
render: function(templateContent) {
this.$el.html(this.template(templateContent));
return this;
}
});
I get this error:
Uncaught ReferenceError: subhead is not defined
Any idea what's wrong and how to maintain the formatting of my original templates?
You say that you are
[...] trying to use grunt-contrib-jst to compile my underscore templates
That's exactly what's happening. If you look at the _.template docs, you'll see this:
The source property is available on the compiled template function for easy precompilation.
If you do this with that <script>:
var t = _.template($('#header-template').html());
console.log(t.source);
you'll see that ugly function in the console.
Demo: http://jsfiddle.net/ambiguous/WjNGC/
So your JST task is simply compiling your templates using _.template and then dump the compiled template function's source attribute to a file; then, when the browser loads that JavaScript file, you get the compiled template back.
The result is that you can say this:
var html = JST['templates/header.html'](data);
and get the filled-in template in html without having to compile the template in the browser.
I have been trying to pass a model object to be evaluated in my template but had no luck. I tried the following but had no luck
dashboardmodel.js
var myMod = Backbone.Model.extend({
defaults: {
name: "mo",
age: "10"
}
});
myview.js
var dashView = Backbone.View.extend({
el: '.content-area',
this.mymodel = new myMod({}),
template: _.template(dashBoardTemplate, this.mymodel),
initialize: function() {
},
render: function() {
this.$el.html(this.template);
return this;
}
// more javascript code.............
dahboard.html
<p> Hello <%= name %> </p>
PS: I am using the underscore template engine
In addition , your way to pass a model to a view is not flexible, because you would pass an instance of your model, instead of a default model. Thus, you might want to single out
this.mymodel = new myMod({}),
(btw, above line gives me error message in my chrome browser because of "=" sign)
Then, suppose you have an instance A:
A = new myMod({"name": "World", "age":100})
Then pass it to your view as:
myview = new dashView({mymodel: A})
One more step, you have to do is to call render function:
myview.render();
Here's a complete solution:
<html>
<script src="jquery-1.10.2.min.js"></script>
<script src="underscore-min.js"></script>
<script src="backbone.js"></script>
<body>
<script type="text/template" id="dashBoardTemplate">
<p> Hello <%= name %> </p>
</script>
<div class="content-area">
</div>
<script type="text/javascript">
var myMod = Backbone.Model.extend({
defaults: {
name: "mo",
age: "10"
}
});
var dashView = Backbone.View.extend({
el: '.content-area',
template: _.template($("#dashBoardTemplate").html()),
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
mymod = new myMod({"name": "World", "age":100});
myview = new dashView({model:mymod});
myview.render();
</script>
</body>
</html>
If you want to study backone.js, please read this open source book which get me started:
http://addyosmani.github.io/backbone-fundamentals/
You need to get properties of a Backbone Model with the getter syntax, so you need to rewrite your template to:
<p> Hello <%= obj.get('name') %> </p>
Or you need to convert your model into a plain JS object when calling _.template what you can do with the .toJSON() (which creates a clone of the model) or .attributes property:
template: _.template(dashBoardTemplate, this.mymodel.toJSON())
Side note: you should consider to move the template rendering logic into your view. Because your current code render your template when your view is declared and not when you call the render method. So you might get unexpected result. So your code you look like this:
template: _.template(dashBoardTemplate), //only compile the template
render: function() {
this.$el.html(this.template(this.mymodel.toJSON()));
return this;
}
I am using Backbone.js and underscore.js with Requirejs. However when i try to load my view template, it gives me (8 out of range 6) error in Underscore.js line 8.
Please tell me what i am doing wrong.
Here is my code:
var imageView = new ImageView({model: item});
define(['jquery','underscore','backbone','imageview','text!../templates/template_image.html'],
function($, _, Backbone, ImageView, template){
var ImageView = Backbone.View.extend({
initialize: function(){
this.showImageTemplate = _.template(template);
},
render: function(){
var html = this.showImageTemplate(this.model);
this.$el.html(html);
return this;
}
});
return ImageView;
});
And my Template file:
<img id="frameImg" src="<%= DocumentPath %>/<%= DocumentName %>" alt="image" title="image"/>
You're passing the raw Backbone.Model object as data to your template, so you're working with something like
{
_changing: false,
_pending: false,
_previousAttributes: {}
attributes: {
DocumentPath: "",
DocumentName: ""
}
...
}
You probably only want the attributes, which you can obtain via model.toJSON for example. Try :
var html = this.showImageTemplate(this.model.toJSON());