I am wondering if there is a better way of doing this.
I have some HTML that needs some events attaching to it.
Question 1:
There is no data, models or collections behind it so I assume no need for a render method?
Question 2:
I am assuming it should be a view in backbone because it is a single piece of UI that needs code attaching to it?
Basically what I have is a panel with show and hide functionality, which shows some check boxes for saving settings. When the panel closes it will save the states of the check boxes.
Here is the HTML:
<div id="panel-holder">
<div id="settings">
<ul class="settingsChecks">
<li>
<label>Display Desktop Notification Popup </label>
<input type="checkbox" id="formDisplayPopup" checked="checked"/>
</li>
<li>
<label>Play Alert Sound </label>
<input type="checkbox" id="formPlaySounds" checked="checked"/>
</li>
</ul>
</div>
</div>
So the above code is attached to a view using #panel-holder.
Here is the Backbone code:
var SettingsView = Backbone.View.extend({
el: '#panel-holder',
events: {
'click #click': 'toggleContent'
},
initialize: function() {
this.toggleContent();
},
showmeState: true,
toggleContent: function(){
if (this.showmeState === false) {
this.openPanel();
} else {
this.closePanel();
}
},
closePanel: function() {
this.$el.find('#settings').slideUp('fast');//Close Panel
this.$el.find('#click').text("Open Settings");//Change Text
this.showmeState = false;
this.saveSettings();
},
openPanel: function() {
this.$el.find('#settings').slideDown('fast');//Open Panel
this.$el.find('#click').text("Close Settings");//Change Text
this.showmeState = true;
},
saveSettings: function() {
//when the panel closes get the states of the checkboxes and save them
}
});
Question 3:
Should I be using jQuery .find('') in the open and close panel areas? Is there a better way of attaching functionality to these elements.
Question 4:
Is this code and my understanding of Backbone Views ok? Am I way off course?
Answers
I would include a render method that returns itself so that you can append the view to the #panel-holder element
Yes, this is fine
Always use $.find("") when working within a view's elements. Just index the elements so you can access them as this.$settings, etc
No looks good generally
Code
Here is my recommended code in Coffeescript (sorry I'm lazy):
Also uses Handlebars library to compile templates
tmpl = ....all your html...
SettingsView = Backbone.View.extend
attributes:
id: 'settings'
template: Handlebars.compile tmpl
events:
'click #click': 'toggleContent'
initialize:
#toggleContent()
showMeState: true
toggleContent: ->
#showMeState is false then #openPanel()
else #closePanel()
closePanel:->
#$settings.slideUp('fast')
#$click.text('Open Settings')
#showMeState = false
#saveSettings()
openPanel: ->
#$settings.slideDown('fast')
#$click.text('Close Settings')
#showMeState = true
saveSettings: ->
render: ->
#$el.append #template
#Store settings and click
#$settings = #$el.find('#settings')
#$click = #$el.find('#click')
#
settings = new SettingsView
$('#panel-holder').append settings.render().el
Question 1: There is no data, models or collections behind it so I
assume no need for a render method?
For now it is not required. But if you want to add extra functionality or if the UI changes in any way later then you might be requiring one.
Question 2: I am assuming it should be a view in backbone because it
is a single piece of UI that needs code attaching to it?
Yes. it is a view.
Question 3: Should I be using jQuery .find('') in the open and close
panel areas? Is there a better way of attaching functionality to these
elements.
this.$el.find('#settings') is perfectly fine.
You can also use either $('#settings', this.$el) or this.$('#settings')
But remember that you are using ID selectors . And ID will be unique on the page. So you do not need to find it in the context of the view
$('#setting') should be good enough
Question 4: Is this code and my understanding of Backbone Views ok? Am
I way off course?
You are doing perfectly well. But the thing is in backbone you can get a task done in many ways which works. And you need to choose the best approach amongst them. You will get to know the more you work with it. So do not worry about it.
The same can be condensed into a single method that will take care of toggling
var SettingModel = Backbone.Model.extend({
defaults: {
showmeState : false
}
});
var settingModel = new SettingModel();
var SettingsView = Backbone.View.extend({
el: '#panel-holder',
events: {
'click #click': 'toggleState'
},
initialize: function () {
// Listen to the Model that triggers
this.listenTo(settingModel, 'change', this.toggleContent);
this.model.trigger('change');
},
toggleState: function() {
//When Clicked just negate the boolean so that it triggers the change event
this.model.set('showmeState', !this.model.get('showmeState'));
},
toggleContent: function() {
var check = !this.model.get('showmeState'),
txt = check === true ? "Close Settings" : "Open Settings";
$('#click').text(txt);
check === true ? $('#settings').slideUp('fast')
: $('#settings').slideDown('fast');
if(check)
saveSettings();
},
saveSettings: function () {
//when the panel closes get the states of the checkboxes and save them
}
});
If you think check for condition is an overkill, then a simple if should be sufficient.
Related
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/
We have a single Backbone view comprised of a sidebar and several sub-views. For simplicity, we've decided to have the sidebar and sub-views governed by a single render function. However, the click .edit event seems to be firing multiple times after clicking on one of the sidebar items. For example, if I start out on "general" and click .edit, then hello fires once. If I then click .profile on the sidebar and click .edit again, hello fires twice. Any ideas?
View
events: {
"click .general": "general",
"click .profile": "profile",
"click .edit": "hello",
},
general: function() {
app.router.navigate("/account/general", {trigger: true});
},
profile: function() {
app.router.navigate("/account/profile", {trigger: true});
},
render: function(section) {
$(this.el).html(getHTML("#account-template", {}));
this.$("#sidebar").html(getHTML("#account-sidebar-template", {}));
this.$("#sidebar div").removeClass("active");
switch (this.options.section) {
case "profile":
this.$("#sidebar .profile").addClass("active");
this.$("#content").html(getHTML("#account-profile-template"));
break;
default:
this.$("#sidebar .general").addClass("active");
this.$("#content").html(getHTML("#account-general-template"));
}
},
hello: function() {
console.log("Hello world.");
},
Router
account: function(section) {
if (section) {
var section = section.toLowerCase();
}
app.view = new AccountView({model: app.user, section: section});
},
Solution
My solution was to change the router to this:
account: function(section) {
if (section) {
var section = section.toLowerCase();
}
if (app.view) {
app.view.undelegateEvents();
}
app.view = new AccountView({model: app.user, section: section});
},
This works for now, but will this create a memory leak?
I had exactly the same problem when I first started using backbone. Like Peter says, the problem is that you have more than one instance of the View being created and listening for the event. To solve this, I created this solution in my last backbone project:
/* Router view functions */
showContact:function () {
require([
'views/contact'
], $.proxy(function (ContactView) {
this.setCurrentView(ContactView).render();
}, this));
},
showBlog:function () {
require([
'views/blog'
], $.proxy(function (BlogView) {
this.setCurrentView(BlogView).render();
}, this));
},
/* Utility functions */
setCurrentView:function (view) {
if (view != this._currentView) {
if (this._currentView != null && this._currentView.remove != null) {
this._currentView.remove();
}
this._currentView = new view();
}
return this._currentView;
}
As you can see, it's always removing the last view and creating a new one, which then renders. I also add a require statement in the router because I don't want to have to load all views in the router until they are actually needed. Good luck.
Sounds like you are attaching multiple view instances to the same DOM element and they are all responding to the events. Are you making a new view each time you navigate without removing the previous view?
I have a dynamic view, that renders different templates inside the same element (about 12), based on router params. Now, the container in which the view renders, is defined inside the view.render() like so "el: '#some-container'". Naturally, i have to remove the view if it exists, before creating a new or the same one, to prevent zombies and s#!t. Just as a reminder, calling view.remove(), actually removes '#some-container' from the DOM, meaning the view has no place to render in, except for the first time. Now, there are dozens of methods to prevent this from happening. Just thought i should share in case anyone needs to save a few hours of research.
I have a web application developed with Backbone.js. In the application, there are some buttons that remove the content view, but not the content model when pushed. For example, If I push the same button multiple times, the content is replaced, but the model of that content isn't removed.
How can I remove it?
I know how to remove the content with other different button, but I don't know how to remove the content if the same button (or other button not destined to delete but to add) is pushed.
The example code:
HTML:
<button class="ShowCam"></button>
<button class="camClose"></button>
<button class="anotherButton"></button>
JS:
var camContent = Backbone.View.extend({
el: "body",
events: {
"click .ShowCam": "addContentCam",
"click .anotherButton": "otherAddContentFunction"
},
initialize: function() {
_.bindAll(this);
this.model = new ContentCollection();
this.model.on("add", this.contentAdded);
this.model.on("remove", this.removeContentCam);
},
addContentCam: function(event) {
this.model.add({ modelName: "IPcam"});
contentAdded: function(content) {
if (content.view == null) {
var templ_name = 'cam';
content.view = new ContentView({
model: content,
template: $.trim($("[data-template='"+ templ_name +"'] div").html() || "Template not found!")});
$("div.camBox").empty();
this.$el.find(".content").find("div.camBox").append(content.view.render().el);
}
},
removeContentCam: function(content) {
if (content.view != null) {
content.view.remove();
}
content.clear(); //Clear the properties of the model
}
});
var ContentView = Backbone.View.extend({
tagName: "div",
template: null,
events: {
"click .camClose": "removeView"
},
initialize: function() {
_.bindAll(this);
this.template = this.options.template;
},
render: function() {
this.$el.html(Mustache.render(this.template, this.model.toJSON()));
return this;
},
removeView: function() {
this.model.collection.remove(this.model); //Remove the model of the collection
}
});
Javascript uses a garbage collection system to do its memory management. What this means is that you can delete anything simply by removing all references to it (well, technically it doesn't actually get deleted until the garbage collector gets to it, but essentially it's deleted).
So, if you want to make sure that a model gets removed, you don't need to call any special methods, you just need to do delete someView.model on every view (or other place in your code) that references that model.
You can actually see all this in practice if you look at the remove method on Backbone.View. You'll find that all it really does (besides triggering events) is call an internal method _removeReference. And what does _removeReference do? This:
if (this == model.collection) {
delete model.collection;
}
// (there's also an event-related line here)
Now, all that being said, if you're making a new view to replace an old one, and they both have the same model ... well you likely shouldn't be making a new view in the first place. The more standard Backbone way of handling such situations is to just re-call render on the view (instead of making a new one).
I just started with backbone.js and is currently making a page which has a div #listing_filter containing text input elements and a table showing some listings fetched from a RESTful backend (PHP/Codeigniter). The values in the text input will act as filters to narrow down the results retrieved by the server.
Problem: Whenever the value in any of the text boxes changes, I want the browser to GET another set of results based on the filter values. Below is my attempt, where the updateFilter function does not fire even though the text input value was changed. Any ideas what went wrong?
Another question will be whether I should put the contents of #listing_filter into a template, or just hardcode it into the main body HTML? Thanks!
JS Code
window.ListingFilterView = Backbone.View.extend({
initialize: function() {
_.bindAll(this, 'updateFilter');
},
events: {
'change input' : 'updateFilter'
},
updateFilter: function() {
console.log('hey');
}
});
// Router
var AppRouter = Backbone.Router.extend({
routes: {
'': 'listings',
},
listings: function() {
//... some other code here
this.listingFilterView = new ListingFilterView();
}
});
HTML Code
<div id="listing_filter">
Price: <input type="text" id="listing_filter_price" />
Beds: <input type="text" id="listing_filter_bedroom" />
Baths: <input type="text" id="listing_filter_bathroom" />
</div>
A Backbone view will only respond to events on the view's el or on elements that are inside its el. By default, the view's el is just an empty <div> and you're not telling the view to use anything else.
You can tell the view which el to use (http://jsfiddle.net/ambiguous/5yXcU/1/):
new ListingFilterView({ el: $('#listing_filter') })
// Or like this:
new ListingFilterView({ el: '#listing_filter' })
or you can let the view attach itself to that element using setElement (http://jsfiddle.net/ambiguous/APEfa/1/):
initialize: function() {
this.setElement($('#listing_filter'));
// or setElement('#listing_filter')
}
or set the el when you define the view (http://jsfiddle.net/ambiguous/nyTZU/):
window.ListingFilterView = Backbone.View.extend({
el: '#listing_filter',
//...
});
Once that's out of the way, you'll need to understand that an <input> won't trigger a change event until it loses focus so you'll have to wait for the user to tab-out of the input or switch to listening for keypress events and using a timer to detect when they've stopped typing.
I'm trying to link an input, a model, and a dom element.
<div data-carName="Isetta">
<input type="textfield" name="speed"/>
<br />
<br />
Speed: <br />
<div>{speed}</div>
</div>
var Isetta = {
speed:speedval
}
What do I do if I want whenever the card speed input is changed, for the speed dom element to change with it, and the javascript object/model to change as well?
I can do this easily with jQuery data-linking. How do I do it with backbone.js?
Thanks.
You may also want to consider binding keystroke events and timers to the input field so that when a user has finished typing in their input, you trigger an event on the model that then updates the view:
If you add id=name to that field, then in your view you can add something like:
events: {
"keypress #speed" : "updateViewOnEnter"
},
updateViewOnEnter: function(e) {
if (e.keyCode != 13) return;
e.preventDefault();
this.model.trigger('speed:change');
},
Check out this throttle function from Remy Sharp if you want to throttle function calls as a user is still typing in that input field:
http://remysharp.com/2010/07/21/throttling-function-calls/
events: {
"keypress #speed" : "updateViewOnDelayedKeypress"
},
updateViewOnDelayedKeypress: function(e) {
throttle(function (e) {
this.model.trigger('speed:change');
}, 250));
},
var Car = Backbone.Model.extend({ });
var CarView = Backbone.View.extend({
model: Car,
initialize: function() {
this.model.bind('change', _.bind(this.render, this));
}
render: function() { ... }
}
The Car model will generate events, and CarView responds to them. The list of events is far broader-- and you can add your own, if you like-- than those of data-link. jQuery Data-Link appears to be concerned entirely with forms, and has a limited filtering mechanism. It's interesting, but it's clearly tackling a different problem from the one Backbone and other MVC libraries are intended to cover.
You can also check out this library: https://github.com/derickbailey/backbone.modelbinding which adds a module that will handle dynamic and convention based binding for you.