Validate javascript inside attributes - javascript

I am using Knockout a lot and often times I have to write scripts inside the data-bind attributes. Is there any validation tools that I can use on these markup files to validate the javascript inside data-bind attributes? Would be nice if there is a grunt plugin.

There probably isn't (a prominent) one, because it's not common to have a lot of complex logic inside your view. With MVVM-like approaches it works best if you keep the View rather plain, and write out logic in your ViewModel where you can unit test it.
So do not do this:
var ViewModel = function() {
var self = this;
self.isCurrent = ko.observable(false);
self.item = ko.observable({ id: 42 });
}
ko.applyBindings(new ViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<!-- Don't do this! -->
<div data-bind="visible: !isCurrent() && !!item()">
Showing something!
</div>
Instead, do this:
var ViewModel = function() {
var self = this;
self.isCurrent = ko.observable(false);
self.item = ko.observable({ id: 42 });
self.shouldShowItem = ko.computed(function() {
return !self.isCurrent() && !!self.item();
});
}
ko.applyBindings(new ViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<!-- Don't do this! -->
<div data-bind="visible: shouldShowItem">
Showing something!
</div>
Because that would allow you to unit test the shouldShowItem logic, e.g. with QUnit:
QUnit.test("shouldShowItem is false if not isCurrent and item not set", function(assert) {
var vm = new ViewModel();
vm.isCurrent(false);
vm.item(null);
assert.strictEqual(vm.shouldShowItem(), false);
});
Bottom line, if you find yourself writing out lots of logic inside your view, you probably need to move some of it to your view models and make it testable.

Related

knockout data-bind html with having inner data-bind

Here is my html looks like:
<div class="xyz-template" data-bind="html:myViewModel().htmlTemplate"></div>
And I would like to have data-bind in htmlTemplate itself:
here is my knockout:
function myViewModel() {
self.htmlTemplate = ko.observable();
self.initialize = function ()
{
self.htmlTemplate(`
<p data-deId="de-box-5" data-bind="html: myViewModel().getDynamicContent()">
Initial content
</p>
`);
};
self.getDynamicContent = function()
{
return "dynamic content";
};
};
Well the return value is
Initial content
How can I have inner bind in binding html?
Whole trick is around rebinding your view. ko does not allow to call applyBindings twice on the same node, so you need to cleanNode from bindings and apply it to element.
Here is the working scenario:
function myViewModel() {
let self = this
self.htmlTemplate = ko.observable();
self.initialize = function ()
{
self.htmlTemplate(`
<p data-deId="de-box-5" data-bind="html: myViewModel().getDynamicContent()">
Initial content
</p>
`);
};
self.getDynamicContent = function()
{
return "dynamic content";
};
return self;
};
const vm = myViewModel();
const root = $('.xyz-template');
ko.applyBindings(vm, root[0]);
vm.initialize();
const templateHolder = root.find('p');
ko.cleanNode(templateHolder[0]);
ko.applyBindings(vm, templateHolder[0]);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<div class="xyz-template" data-bind="html: htmlTemplate"></div>
Keep in mind that in your particular case you cannot rebind root because everytime you initialize binding html binding from xyz div kicks in, so again <p> is somehow detached
Here you will also find better ways to solve that problem knockout custom html binding
Code which I posted is just to show the concept

how to call view events in loop in backbone js

i want to fire on_change events on dynamically created drop boxes.
but have no idea how to do it in backbone js
here is my html code creating a div tag
<div id="page">
<input type="button"id="btn1"value="ok">
</div>
and its my backbone code where i am dynamically adding drop down in
var btn2id ="";
var app = {};app.v1 = Backbone.View.extend({
el: '#page',
events: {
'click #btn1' : 'f1',
},
f1:function()
{
alert("Boom");
btn2id="btn2";
for(var j=0;j<3;j++) {
$('#page').append('<select id="selecty'+j+'"></select>');
for(var i=0;i<10;i++){
$('#selecty'+j+'').append('<option value="'+i+'">'+i+'</option>');
}
vv = new app.v2();}}
}
});
app.v2 =Backbone.View.extend({
el: '#page',
events:{
at this place i have no idea what to do
// for(int i=0;i<3;i++){
// 'change '#selecty'+i+'' : 'f2',
// }
},
f2:function() {
alert("Boom again");
}
v = new app.v1();
});
v = new app.v1();
In my opinion, reusable components should have their on view.
This practice lets you bind the recurring events easily, and in general matter cleans your code.
Note: in my code example I didn't use any template engine or practice, but I totally recommend you to do that.
So lets assume you have the main view with a button that creates new select elements:
var View = Backbone.View.extend({
el : "#main",
events : {
'click #add' : 'add',
},
add : function(){
var select = new SelectView();
this.$el.append(select.render().el);
}
});
As you can see, anytime #add is clicked, it creates a new SelectView which represents the select element.
And the select element itself:
var SelectView = Backbone.View.extend({
events:{
'change select' : 'doSomething'
},
doSomething: function(e){
$(e.currentTarget).css('color','red');
},
render: function(){
this.$el.html("<select />");
for(var i=0;i<10;i++)
{
this.$el.find('select').append("<option value='"+i+"'>"+i+"</option>")
}
return this;
}
});
In my dummy example I just change the color of the element when it is changed. You can do whatever.
So, it is now super easy to bind events to the select views.
In general, I would recommend you that when you are working with reusable components, you should always think of a practice which makes things make sense.
This is one of many ways to do that, but it is pretty simple to understand and implement.
You are welcome to see the "live" example: http://jsfiddle.net/akovjmpz/2/

backbonejs beginners issue - fetching and displaying json data

Im new to Backbone (dont hate me) but am pulling my hair out trying to do a very simple thing.
Im loading a json file (correctly as I can see it loading in firebug) and I just want to pull some info from it purely for testing (as its my first backbone code)
However, I cant get this working and end up with one blank li tag (code below)
<ul id="phones"></ul>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.4.2/underscore-min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/backbone.js/0.9.2/backbone-min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/handlebars.js/1.0.rc.1/handlebars.min.js"></script>
<script id="foo" type="text/template">
<li><%= name %></li>
</script>
<script>
var Phones = Backbone.Collection.extend({
url:'http://backbone.local/phones/phones.json'
})
var PhonesView = Backbone.View.extend({
el:'#phones',
initialize:function(){
this.collection = new Phones();
this.collection.fetch();
this.render();
},
template:_.template($('#foo').html()),
render:function(){
var foo = this.collection.toJSON();
$(this.el).html(this.template(foo));
return this;
}
})
var phonesView = new PhonesView();
</script>
Any pointers greatly appreciated.
Cheers,
UPDATE 1
I thought it may be due to fetch being async so i called render in success callback of fetch as below. The console.log fires fine but still no json data in rendered html (i also changed to using handlebars)
<script>
var Phones = Backbone.Collection.extend({
url:'http://backbone.local/phones/phones.json'
})
var PhonesView = Backbone.View.extend({
el:'#phones',
initialize:function(){
var self = this;
this.collection = new Phones();
this.collection.fetch({
success:function(){
console.log('json loaded');
self.render();
}
});
},
template: Handlebars.compile('<li>sdsadsadsadsad {{name}} dsfcdfd</li>'),
render:function(){
var foo = this.collection.toJSON();
$(this.el).html(this.template(foo));
return this;
}
})
var phonesView = new PhonesView();
</script>
With Handlebars, a collection template looks like this:
{{#items}} <li> {{name}} </li> {{/items}}
You also need to wrap your collection JSON in an items object so that the Handlebars template can reference it as above:
var foo = { items: this.collection.toJSON() };
Edit
There's actually one more issue ... collection.toJSON() doesn't convert each model to JSON. So you need to write:
this.collection.models.map(function(x) { return x.toJSON(); });
Fiddle Demo
On your view:
'initialize': function() {
this.template = _.template('<p><% model.text $></p>');
this.collection.fetch({error: function() { console.log(arguments); }, 'success': _.bind(this.onFetch,this) });
return this;
},
'onFetch': function(collection) {
this.collection.on("add", this.onAdd, this);
this.collection.each( _.bind(this.onAdd, this) );
return this;
},
'onAdd': function(model){
//do something like:
this.$el.find('.items').append(this.template({model: model}) )
);
To answer your explicit question of why you only get an empty li. You must send the template some name data. For example:
render:function(){
var firstModel = this.collection.at(0);
var firstName = firstModel.get("name");
$(this.el).html(this.template({name: firstName}));
return this;
}
Of course, the above code is only to understand what's missing, and not how a backbone application should be implemented. I really recommend that you go over the annotated TODO example linked from Backbone's website, and understand the basic patterns that are implemented there.
Update based on your comment:
Different ways of solving this. Really recommend reading: http://backbonejs.org/docs/todos.html
To continue on the "hacky path" of solving this so you can see something:
addOne: function(phone) {
this.$el.append(this.template(phone.toJSON()));
return this;
},
render: function() {
this.$el.html(); //clear everything in the view
this.collection.forEach(_.bind(this.addOne,this)); //Will call addOne for every model in the collection.
return this;
}

Is there a way to set the page title by data-binding using Knockout.js?

I have a viewModel with a Title property. I'd like to set the page title using that property. Here's what I tried already, which didn't work:
<html>
<head>
<title data-bind="text: Title"></title>
</head>
<body>
<span data-bind="text: Title"/> <!-- this displays the title properly -->
</body>
The browser title is blank/default instead of the value of my Title property.
Try giving your html element an id
<html id="htmlTop" xmlns="http://www.w3.org/1999/xhtml">
and applying your viewModel to it
ko.applyBindings(viewModel, document.getElementById("htmlTop"));
EDIT
This works for me; I just ran this page and the title said "Hello". Double check your code for typos.
<html id="htmlTop">
<head>
<title data-bind="text: title"></title>
<script type='text/javascript' src='jquery.min.js'></script>
<script type='text/javascript' src='knockout-1.2.1.js'></script>
<script type="text/javascript">
$(function () {
var viewModel = { title: "Hello" };
ko.applyBindings(viewModel, document.getElementById("htmlTop"));
});
</script>
</head>
<body>
</body>
</html>
Screenshot:
In my eyes, this situations begs for an observable subscription.
...
<title>{FALL BACK TEXT}</title>
...
View Model
ViewModel = function() {
var self = this;
self.PageTitle = ko.observable(null);
self.PageTitle.subscribe(function(newValue){ document.title = self.PageTitle() });
//change PageTitle to see your handy work in action
self.PageTitle("Hello World!");
};
EDIT: As an amendment to my previous answer, I'd like to put forth the following. Will my previous suggestion work? Yes, it works very nicely. However, DOM manipulation within the view model itself does not "exactly" follow the MVVM paradigm. The actual "best" approach would be to create a custom binding, which sets the document title on update of a particular observable.
...
<title data-bind="htmlDocumentTitle: PageTitle()">{FALLBACK TEXT}</title>
...
View Model
ViewModel = function() {
var self = this;
self.PageTitle = ko.observable(null);
self.init = function(){
self.PageTitle("My page title from an obersvable");
};
//init the viewmodel
self.init();
};
And finally our shiny custom binding to "listen" for changes to the observable (note the use of ONLY the update action)
ko.bindingHandlers.htmlDocumentTitle = {
update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
var fallbackTitle = "My document title";
var title = ko.unwrap(valueAccessor());
if(!title || title == null && title == "")
title = fallbackTitle;
document.title = title;
}
};
At first glance this solution may appear less scalable, but do keep in mind that we can create "pseudo-inheritance" with Knockout View Models using "call()".
You could create a custom binding handler which sets document.title on update, then add the binding to the body element.
You can use knockout subscribe:
function viewModel() {
var self = this;
self.title = ko.observable(null);
self.title.subscribe(function(newTitle) {
document.title = newTitle;
})
}
var vm = new viewModel();
ko.applyBindings(vm);
vm.title('Hello page');
As per #Douglas's suggestion, my solution was to add a hidden div somewhere in the body bound to a computed value:
<div data-bind="text: currentPageTitle()"></div>
Then, in the value computation, I set the document.title:
self.currentPageTitle = ko.computed(function() {
document.title = self.Title();
return self.Title();
}, this);
This works perfectly for me

How do I wait for Sproutcore 2.0 to have loaded all templates?

In my app, the <body> tag contains just a single <script type="text/x-handlebars> tag which contains all my views. Sproutcore 2.0 nicely adds a jQuery on-document-ready handler that parses those templates and renders them back into the DOM.
I'd like to call a function on one of the views as soon as it's rendered. The problem is that the re-insertion happens asynchronously, so I don't know when the view is available.
Example
Page
<body>
<script type="text/x-handlebars">
...
{{view "MyApp.TweetInputView"}}
...
</script>
</body>
View:
MyApp.TweetInputView = SC.View.extend({
init: function() {
// act like a singleton
MyApp.TweetInputView.instance = this;
return this._super();
},
focus: function() {
...
this.$().focus();
}
});
Initializer
// if the URL is /tweets/new, focus on the tweet input view
$(function() {
if (window.location.pathname === '/tweets/new') {
// doesn't work, because the view hasn't been created yet:
MyApp.TweetInputView.instance.focus();
}
});
I've also tried SC.run.schedule('render', function() { MyApp.TweetInputView.instance.focus(); }, 'call'); in the hopes that Sproutcore would run that after all the view rendering and insertion, but that does not seem to be the case.
Try this:
MyApp.TweetInputView = SC.View.extend({
didInsertElement: function() {
console.log("I've been rendered!");
}
});

Categories