I would like to have a button with the name 'fire', but even though I specified the value, the name doesn't show up, just an empty button. Please help.
var Tweets = Backbone.View.extend({
tagName: 'button',
id: 'tweets',
className: 'btn btn-primary',
value: 'fire',
initialize: function() {
},
render: function() {
return this;
}
Use the "attributes" property of the view, which allows you to specify a hash of name+value pairs. They will get included as attributes on the generated DOM element (that is, the "button" in your case).
Add this to your Tweets declaration:
...
attributes: { "name": "fire" },
...
That would cause the generated button element to look like this:
<button name="fire" id="tweets" class="btn btn-primary" value="fire"></button>
Relevant documentation is here: http://backbonejs.org/#View-attributes
Note, however, that while you asked how to specify the "name", that might not be what you were really trying to do. If your goal is to get a button element with the text "fire" displayed on the button itself, you need to set the content of the button tag, rather than some attribute of the tag.
Relevant documentation for the button element is here: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button
To do that, you'll need to update your render method to manually set the generated "button" element's content. Something like this should work for the render method:
render: function() {
this.$el.text("fire");
return this;
}
That should cause the generated button element to look like this:
<button id="tweets" class="btn btn-primary" value="fire">fire</button>
Related
I've created an AngularJS navigation menu and I'm trying to highlight the active menu item based on ng-repeat.
So far I have the menu working with ng-repeat, but I don't know what to put in the ng-click function for the active class to get applied.
Here's what I have:
Here is my view:
//View (in jade):
ul(ng-controller='MenuCtrl')
li(ng-repeat='navLink in navLinks', ng-click='select(navLink)', ng-class='{active: navLink.linkhref == path}')
a(href='{{navLink.linkhref}}') {{navLink.linktext}}
And here is my controller:
//Controller:
...
controller('MenuCtrl', function ($scope,$location) {
$scope.navLinks = [{
linkhref: '/',
linktext: 'View Dashboard',
}, {
linkhref: '/rpm',
linktext: 'View RPMs',
}, {
linkhref: '/status',
linktext: 'View Status',
}, {
linkhref: '/database',
linktext: 'View Database',
}, {
linkhref: '/config',
linktext: 'View Configurations',
}];
$scope.path = $location.path();
$scope.select = function(navLink) {
//idk what to put here to make the "active" class toggle between nav links
//I could try this:
//remove "active" class from nav links
//then add "active" class to this link
};
})
...
Here's a fiddle with my code:
http://jsfiddle.net/bATZ5/1/
So far here is the behavior:
when I go to http://localhost/status, the "View status" link has the "active" class on it. great!
when I click another link, the "View status" link stays "active", and the new link does not get the "active" class added.
basically nothing happens when i click, but refreshing the page works (because of the "path == path" thing)
Resources that were helpful:
ng-class to highlight active menu item based on ng-repeat. AngularJS
How to add and remove active class on click - angular way
http://jsfiddle.net/bATZ5/
On your list elements, the ngClick directive is comparing the scope's path value with the navLink element's linkhref value. So if you want the item to gain the class, simply set the values:
$scope.select = function(navLink) {
$scope.path = navLink.linkhref;
}
While this solves your problem, I believe there's a larger issue with this approach to a menu. Clicking the li element outside of the link will cause the item to appear active. Also, navigating with the keyboard (or in any way that doesn't trigger ngClick) will not update the menu.
Instead of binding the click, it might be worth exploring a method that watches the route:
function updateActiveLink(){
$scope.path = $location.path();
}
$scope.$on('$routeChangeSuccess', updateActiveLink);
Or another option that I don't like as much:
$scope.$watch(function(){
return $location.path();
}, updateActiveLink);
I am seeing the click event is triggered multiple times for each row on the itemview
return Marionette.ItemView.extend( {
template: ItemViewTemplate,
tagName: 'tr',
className: 'ItemRow',
templateHelpers: viewHelpers,
events: {
'click .editRowItem': 'editRowItem'
The editRowItem() function is triggered multiple times. What is the correct way to trigger the click just on that particular row?
Thanks!
Usually it shouldn't trigger multiple times. It can happen however, for example when:
Nested elements with the same class in a view. Event bubbling will cause multiple events to be fired.
Parent views listening to click events on a class which is present in some or all children views.
An example (stripped non-relevant parts):
<script type="text/template" id="movie-list-item">
<button type="button" class="btn btn-success some-button">
<span class="some-button">Click here</span>
</button>
</script>
// Itemview
var movieItemView = Marionette.ItemView.extend({
template: "#movie-list-item",
ui: {
viewButton: '.some-button'
},
events: {
'click #ui.viewButton': 'clickHandler'
},
clickHandler: function(ev) {
// Log the click
console.log('There was a click in an itemView!');
// Uncomment the following to prevent multiple events:
//ev.stopPropagation();
}
});
// Composite view
var movieCompView = Marionette.CompositeView.extend({
template: "#movie-list",
itemView: movieItemView,
ui: {
viewButton: '.some-button'
},
events: {
'click #ui.viewButton': 'clickHandler'
},
clickHandler: function(ev) {
// Log the click
console.log('There was a click in a collectionView!');
// Uncomment the following to prevent multiple events:
//ev.stopPropagation();
}
});
Demo here: http://jsfiddle.net/Cardiff/7d3fC/2/
Note the following, if we don't use ev.stopPropagation() in this case to prevent the event from bubbling, the console will log 4 entries; being two for the itemView and two for the collectionView. To prevent this behaviour (and you shouldn't use a click event in the collectionView in this case) and thus receive one instead of two events we use ev.stopPropagation().
Also keep in mind that using the ui attribute of a view to describe the components is considered good practice and can make your life a little easier.
Just try following if you want to apply click event to each row item in the template:
events: {
'click' : 'editRowItem'
}
I have a template in underscore having a button inside it. I want a click event to be mentioned in the event hash in the backbone view.
template code is:
<script type="text/template" id="ledgerListing">
<button class="btn btn-danger pull-right" id="addLedgerButton">Add Ledger</button>
</script>
View Code is:
app.ledgerView=Backbone.View.extend({
el:"#container",
template:_.template($("#ledgerListing").html()),
events: {},
initialize: function(){
},
render: function()
{
this.$el.html(template())
}
});
Now how to specify the click event in the event hash for the button with id addLedgerButton
Your can add events as the following, (format should be event type and a space and the element
events: {
'click #addLedgerButton': 'myclick'
},
and define a function named myclick:
myclick: function () {
alert(1);
}
Here is jsfiddle. http://jsfiddle.net/w2jm7/
I have a model and a view. The view displays attributes of a model and allows the user to manipulate these attributes. The problem is that when an attribute is modified it re-renders the whole view which causes a lot of problems for me.
Example blur event on a text input saves the new input to an attribute and thus fires render. Which means that if the user clicked from that text input straight to a button on the same view that event will never fire as the first event that fires will be blur causing the whole view to re-render and thus losing the button click event.
I have two ideas:
Have a single view where every attribute is in a separate template. Then I bind to a particular attribute change event and in render I update only the html of the changed attribute. This seems like a hack, as there is a lot of work to force the view to update only the changed attribute. It will add a lot of unnecessary complexity to an already complex view.
Create a master view which consists of views, where each of them represents a model's attribute. This will create a lot of views, with nearly no functionality.
I seem to prefer the 2. option. What do you think? What are the best practices? Is there any better way to handle this?
I think you can do this quite easily.
Take a step back and think about where you are binding your events. It seems that you are binding them directly on top of each individual element instead of using a parent delegate.
Here's an example
Backbone.View.extend({
el: $("div.parent"),
events: function() {
this.$el.on("click", "input[type=button]", function(){});
// jquery cross browser on this
this.$el.on("blur", "input[type=text]", function(){});
},
initialize: function() {
this.model.bind("change", this.render, this);
},
render: function() {
this.$el.html('<input type="text" /><input type="button" />');
}
});
Here's what el and it's structure looks like
<div class="parent">
<input type="text" />
<input type="button" />
</div>
So this.$el points to div.parent. I can constantly rerender the contents of this.$el, and as long as the html structure dosen't change, I don't have to worry about events getting unbound. The other solution is that if I really cannot do delegation, I would just call the events method whenever I render again.
Like you said yourself, both of your options seem very complex. But sometimes additionaly complexity is a necessary evil. However, if the updated fields are something relatively simple (like binding a value to an element or an input field), I would simply update the DOM elements without creating additional View/Template abstractions on top of them.
Say you have a model:
var person = new Person({ firstName: 'John', lastName: 'Lennon', instrument:'Guitar' });
And a view which renders the following template:
<div>First Name: <span class="firstName">{{firstName}}</span></div>
<div>Last Name: <span class="lastName">{{lastName}}</span></div>
<div>Instrument: <input class="instrument" value="{{instrument}}"></input></div>
You could declare in the view which property change should update which element, and bind the model change event to a function which updates them:
var PersonView = Backbone.View.extend({
//convention: propertyName+"Changed"
//specify handler as map of selector->method or a function.
firstNameChanged: { '.firstName': 'text' },
lastNameChanged: { '.lastName': 'text' },
instrumentChanged: { '.instrument': 'val' },
otherFieldChanged: function(val) { //do something else },
initialize: function (opts) {
this.model.on('change', this.update, this);
},
//called when change event is fired
update: function(state) {
_.each(state.changed, function(val, key) {
var handler = this[key + "Changed"];
//handler specified for property?
if(handler) {
//if its a function execute it
if(_.isFunction(handler)) {
handler(val);
//if its an object assume it's a selector->method map
} else if(_.isObject(handler)) {
_.each(handler, function(prop, selector) {
this.$(selector)[prop](val);
}, this);
}
}
}, this);
}
A solution like this doesn't scale to very complex views, because you have to add classed elements to the DOM and maintain them in the View code. But for simpler cases this might work quite well.
In addition it's always good to try to compose views of multiple, smaller views, if they naturally divide into sections. That way you can avoid the need to update single fields separately.
I have a few different problems going on, I hope though this example is easy to follow. The code uses an HTML template with elements hidden by default (using CSS). The Backbone View uses data in a Model to display appropriate values OR hide the UI element if no value is present in the Mode.
Given a template where everything is hidden by default (using CSS), for example:
<script type="text/template" id="Person-Template">
<span class="fname" title="FirstName"></span>
<span class="lname" title="LastName"></span>
<span class="age" title="Age"></span>
</script>
To hide each UI element the CSS is:
span.fname,
span.lname,
span.age {
display:none;
}
My Backbone.js Model would therefore be:
PersonModel = Backbone.Model.extend({
defaults: {
fname: undefined,
lname: undefined,
age: undefined
}
});
The View (simplified) would be:
PersonView = Backbone.View.extend({
tagName: 'div',
initialize: function() {
this.model.on("fname", this.updateFName, this);
this.model.on("lname", this.updateLName, this);
this.model.on("age", this.updateAge, this);
},
updateFName: function() {
// Pseudo code
Get 'new' value from Model
Obtain reference to DOM element span.fname
Update span.fname
if (model value is empty) {
Hide UI element.
}
},
updateLName: function() {
// Same as above
},
updateAge: function() {
// Same as above
},
render: function() {
// Get model values to display
var values = {
FirstName : this.model.get('fname'),
LastName : this.model.get('lname'),
Age: this.model.get('age'),
};
// Load HTML template
var template = $('#Person-Template').html();
// Populate template with values
var t = _.template(template, values);
// Show / hide UI elements
this.updateFname();
this.updateLName();
this.updateAge();
}
}
Finally, the question: It seems hacky calling each updateXYZ() method from render() just to determine whether the UI element should be set to hidden or visible. I have a lot of attributes in my model and the code just seems a little absurd really.
I have been told on SO that the View should not be responsible for determining what should or should be displays. My questions is, well then what is responsible? The user may perform some (valid) aciton which clears the First Name, in which case I don't want my View displaying 'First name:' followed by no value.
First of all, you don't need to build your values by hand, just use toJSON:
var values = this.model.toJSON();
Then, you have to add your filled in template to your view's el:
this.$el.html(_.template(template, values));
and your template should probably include something to display in your template:
<script type="text/template" id="Person-Template">
<span class="fname" title="FirstName">F: <%= fname %></span>
<span class="lname" title="LastName">L: <%= lname %></span>
<span class="age" title="Age">A: <%= age %></span>
</script>
You don't separate functions for each of the three parts, you could just loop through them in your render:
_(values).each(function(v, k) {
var m = _(v).isUndefined() ? 'hide' : 'show';
this.$('.' + k)[m]();
}, this);
Now back to your events. There is no such thing as an "fname" event unless you've added a custom one. But there's no need for that, the model will trigger "change" and "change:fname" events when the fname is changed; you only need to care about "change" though:
initialize: function() {
_.bindAll(this, 'render');
this.model.on("change", this.render);
},
I've also bound render to your view instance using _.bindAll so that you don't have to worry about the third argument to this.model.on.
Now you have something that works: http://jsfiddle.net/ambiguous/46puP/
You can also push the "should this be displayed" logic into the template:
<script type="text/template" id="Person-Template">
<% if(fname) { %><span class="fname" title="FirstName">F: <%= fname %></span><% } %>
<% if(lname) { %><span class="lname" title="LastName">L: <%= lname %></span><% } %>
<% if(age) { %><span class="age" title="Age">A: <%= age %></span><% } %>
</script>
and simplify your render:
render: function() {
var values = this.model.toJSON();
var template = $('#Person-Template').html();
this.$el.html(_.template(template, values));
return this;
}
Demo: http://jsfiddle.net/ambiguous/W9cnJ/
This approach would probably be the most common and there's nothing wrong with it. I think you're misunderstanding what the previous answer was trying to tell you. The template chooses what pieces of information to display through <%= ... %> already so there's no good reason that it shouldn't see if fname, for example, is set before trying to display it. Depending on the nature of your data, you might want to use if(!_(fname).isUndefined()) and such in your template but a simple truthiness check is probably fine; the age might be an issue in some cases though so you might want to be a bit stricter with that.