How to get attributes from the clicked element in backbone event? - backbone.js

Here is my basic backbone view for changing routes. I would like to get the href attribute of the clicked link. How to do that? Here is a code bellow:
var Menu = Backbone.View.extend({
el: '.nav',
events: {
'click a' : 'changeRoute'
},
changeRoute: function(e) {
e.preventDefault();
//var href = $(this).attr("href");
router.navigate(href, true);
}
});
I am a newbie in backbone, so please have mercy :)

you can use: var element = $(e.currentTarget);
then any attributes can be called like this: element.attr('id')
so in your code above:
changeRoute: function(e) {
e.preventDefault();
var href = $(e.currentTarget).attr("href");
router.navigate(href, true);
}

Related

Backbone JS Event from child view not firing

I'd gone through most of the solution for this issue but still couldn't solve it. Please help me guys. T.T
My child event is not firing no matter how I configure it please guide me through it.
My parent is 'dayInfo' and I'm calling 'schedule' from the parent.
But the html button is not triggering the event.
///// schedule.html /////
<a class="ic-plus-circle add"></a>
///// schedule.js /////
define(function(require) {
var Backbone = require('backbone'),
User = require('models/User'),
css = require('text!views/dayInfo/schedule.css'),
tpl = require('text!views/dayInfo/schedule.html');
return Backbone.View.extend({
initialize: function(options) {
this.template = _.template(tpl);
var self = this;
return this;
},
render: function() {
var self = this;
self.$el.html(self.template());
return this;
},
events: {
'tap .add': function(e) {
console.log('success');
var self = this;
var $this = $(e.currentTarget);
console.log('tapped');
}
}
});
});
////// dayInfo.html /////
<div class="tab-content"></div>
////// dayInfo.js /////
define(function(require) {
var Backbone = require('backbone'),
ScheduleView = require('views/dayInfo/schedule'),
css = require('text!views/dayInfo/dayInfo.css'),
tpl = require('text!views/dayInfo/dayInfo.html');
return Backbone.View.extend({
id : 'dayInfo',
initialize: function(options) {
this.template = _.template(tpl);
var self = this;
return this;
},
render: function() {
var self = this;
self.$el.html(self.template());
self.$tabContent = self.$el.find('.tab-content');
self.scheduleView = new ScheduleView({date:self.date,doctor:self.doctor,appointments:self.appointments,events:self.events}).render();
self.$tabContent.html(self.scheduleView.$el);
}
});
});
I was expecting the console to throw 'tapped' whenever I click the button
There is no such thing as tap event, which is why it's not firing. See MDN - Touch events for list of available touch events.
You'll need to get a touch event plugin what works with jQuery to have custom events such as tap

BackboneJS - How to add a Progressbar while fetching collection

I have an Backbone App where I fetch different collections by clicking a Letter from a list. So, I want to add a Progressbar or some kind of rotating image but I dont know how to do this.
My View looks like this
function (App, Backbone) {
var Artists = App.module();
var ArtistView = Backbone.View.extend({
tagName : 'li',
template: 'artistItem',
serialize: function() {
var data = this.model.toJSON();
data.letter = this.model.collection.letter;
return data;
},
});
Artists.View = Backbone.View.extend({
tagName : 'ul',
className : 'artistList',
initialize: function() {
this.listenTo(this.collection, 'all', this.render);
this.listenTo(App, 'navigateLetter', this.updateState);
},
beforeRender: function() {
var self = this;
this.collection.each(function(item) {
self.insertView(new ArtistView({model: item}))
})
},
updateState: function(letter) {
this.collection.letter = letter;
this.stopListening(this.collection);
this.collection.fetch();
this.listenTo(this.collection, 'all', this.render);
}
});
Artists.ArtistsCollection = Backbone.Collection.extend({
url: function() {
return '/projects/mdk/index.php/api/artists/' + this.letter;
}
});
return Artists;
});
So does anyone have an idea how to do this? I could imagine I should do something in initialize or beforeRender?
Thanks in advance
You can use a spinner for the loading effect. For that you need
spin.js
Add entry of that spin.js into main file.
To use that spinner.
var yourSpinner = new Spinner();
var target = document.getElementById('spinHere');
yourSpinner.spin(target);
e.g in your case take updateState:function(){} :
updateState: function(letter) {
this.collection.letter = letter;
this.stopListening(this.collection);
var yourSpinner = new Spinner();
var target = document.getElementById('spinHere');
yourSpinner.spin(target);
this.collection.fetch();
yourSpinner.stop();
this.listenTo(this.collection, 'all', this.render);
}
Take a look at this: https://github.com/davidsulc/marionette-gentle-introduction/blob/master/assets/js/apps/contacts/list/list_controller.js#L4
It's from my Marionette book app, where the idea is to immediately display a loading view, and when the collection is fetched, render the actual view (and closing the loading view, which is Handled by Marionette). It would give something like (pseudocode):
var loadingView = new ContactManager.Common.Views.Loading();
ContactManager.mainRegion.show(loadingView);
var fetchingContacts = myCollection.fetch();
$.when(fetchingContacts).done(function(contacts){
ContactManager.mainRegion.show(new MyCollView({ collection: contacts }));
});
The code uses a deferred to determine when the collection has been fetched (and therefore the new view should be displayed). You can learn more about using deferreds here:
http://davidsulc.com/blog/2013/04/01/using-jquery-promises-to-render-backbone-views-after-fetching-data/
http://davidsulc.com/blog/2013/04/02/rendering-a-view-after-multiple-async-functions-return-using-promises/

BackboneJS - Subviews navigation

I have a pretty simple mainmenu with 4 anchors and the relevant views to them. Anyhow, on one of those views I want to add a little submenu with 3 tabs, which after clicking them they show a different view. I figured out how to do it with pushState:false but what I want is a clean URL. Right now, my URL would look like http://localhost/myproject/#secondpage/subview1 or http://localhost/myproject/#secondpage/subview2 etc etc. So does anyone know how I do achieve http://localhost/secondpage no matter which subview/tab is triggered?
Im using RequireJS and HandlebarsJS (for HTML-templating)
So right now my code (snippets) look like this:
Router.js
routes: {
'': 'index',
'firstpage' : 'firstpage',
'secondpage' : 'secondpage',
'secondpage/sub1' : 'sub1',
'secondpage/sub2' : 'sub2',
'secondpage/sub3' : 'sub3',
'thirdpage' : 'thirdpage'
},
Backbone.history.start({
pushState: false
});
My HTML with the anchors:
<ul>
<li>
<a class="sub1" href="#secondpage/sub1">Bands</a>
</li>
<li>
<a class="sub2" href="#secondpage/sub2">Koncert</a>
</li>
<li>
<a class="sub3" href="#secondpage/sub3">Locations</a>
</li>
</ul>
and my View looks like
define(['backbone','handlebars', 'text!templates/SubMenu.html'],
function(Backbone,Handlebars, Template) {
'use strict';
var SubMenuView = Backbone.View.extend({
template: Handlebars.compile(Template),
initialize: function () {
_.bindAll(this);
},
render: function() {
$(this.el).html(this.template());
return this;
}
});
return SubMenuView;
}
);
Another thing is: should I move the actions to the View by setting events? I kind of tried that but it didnt work since the Views are defined in the router...
What I tried is to set pushState:true, then I removed the secondpage/sub1 thingies in my router, then in my View I wrote:
events: {
'click a.sub1': 'sub1',
},
sub1: function(event) {
event.preventDefault();
var sub1Router = new Backbone.Router();
var route = '/secondpage/';
sub1Router.navigate(route, {trigger: true});
},
but that didnt work, that gave me URL not found so...
Any help is welcome! Thanks in advance...
[UPDATE]
OK, so by request, here is my (new) router:
var Router = Backbone.Router.extend({
routes: {
'': 'index',
'firstpage' : 'firstpage',
'secondpage' : 'secondpage',
'thirdpage' : 'thirdpage'
},
initialize: function () {
var self = this;
//Views
this.mainMenuView = new MainMenuView({el:'#mainMenu'}).render();
this.subMenuView = new SubMenuView();
Backbone.history.start({
pushState: true
});
},
index: function () {
var self = this;
},
firstpage: function() {
this.firstpageView = new FirstpageView({el:'#topContent'}).render();
},
secondpage: function() {
this.secondpageView = new SecondpageView({el:'#topContent'}).render();
this.subMenuView = new SubMenuView({el:'#subMenu'}).render();
},
thirdpage: function() {
var thirdpageView = new ThirdpageView({ el:'#topContent', collection:this.categoryCollection}).render();
},
sub1: function() {
this.sub1View = new Sub1View({el:'#subContent_2'}).render();
},
sub2: function() {
this.sub2View = new Sub2View({el:'#subContent_2'}).render();
},
sub3: function() {
this.sub3View = new Sub3View({el:'#subContent_2'}).render();
}
});
return Router;
}
And my (new) View looks like:
var SubMenuView = Backbone.View.extend({
template: Handlebars.compile(Template),
events: {
'click .sub1': 'sub1',
'click .sub2': 'sub2',
'click .sub3': 'sub3',
},
sub1: function(event) {
var sub1Router = new Backbone.Router();
var route = '/secondpage';
sub1Router.navigate(route, {trigger: true});
},
sub2: function(event) {
event.preventDefault();
var sub2Router = new Backbone.Router();
var route = '/secondpage';
sub2Router.navigate(route, {trigger: true});
},
sub3: function(event) {
event.preventDefault();
var sub3Router = new Backbone.Router();
var route = '/secondpage';
sub3Router.navigate(route, {trigger: true});
},
initialize: function () {
_.bindAll(this);
},
render: function() {
$(this.el).html(this.template());
return this;
}
});
return SubMenuView;
And my (new) HTML template:
<ul>
<li>
<a class="sub1" href="/secondpage/">Sub1</a>
</li>
<li>
<a class="sub2" href="/secondpage/">Sub2</a>
</li>
<li>
<a class="sub3" href="/secondpage/">Sub3</a>
</li>
</ul>
Hope this can contribute to more input/suggestions... This is really driving me nuts which make me consider using .show() and .hide() jquery method even if i dont really want...
What you're describing is how backbone routing works, either you use '/secondpage/sub1' and use server routes, or use '#secondpage/sub1' and hit backbone routing. Either way the address bar is going to update with your URL.
One alternative option is to use a events inside the view, handling a click event and updating the view's template accordingly.
However, if you're intent on using routes then maybe have a look at clickify.js. I haven't used it myself yet, although I have it bookmarked for potential future use... sounds like it might do what you want.
try to use this in your router :
Backbone.history.navigate('secondpage');
after all the work is done (the models are fetched the views are rendered)

Controller listenTo breaks when in callback method

I have the problem that the event "form:selectedForm" is calling the method "showForm" but when sending this to my view I am getting the following error: TypeError: e[t] is not a function.
This is stated in line 128 in the backbone.js script but I have no clue what he is doing there. It looks like that he is looking for a "to" or "on" event on the collection.
What I am doing wrong here?
MyController = Backbone.Marionette.Controller.extend({
initialize: function(options) {
this.options = options;
this.urls = options.urls;
this.mainRegion = options.mainRegion;
this.view = new MyLayout();
this.mainRegion.show(this.view);
this.view.render();
this.showSelectorView(this.view.formHeader);
},
showSelectorView : function(view) {
var forms = new MyForms();
forms = this.urls.loadForms;
var selectorView = new FormSelectorView({
collection: forms
});
forms.fetch();
this.listenTo(selectorView, "form:selectedForm", this.showForm);
view.show(selectorView);
},
showForm : function(models) {
console.log("showForm");
var form = new FormContentView({
collection: models
});
this.view.form.show(form);
}
});
MyLayout = Backbone.Marionette.Layout.extend({
template: Backbone.Marionette.TemplateCache.get('#content'),
regions: {
formHeader: "#selector",
form: "#formContent",
formContent: "#content",
formFooter: "#save",
formTemplates: "#templates"
}
});
FormSelectorView = Backbone.Marionette.ItemView.extend({
template: Backbone.Marionette.TemplateCache.get('form-selector-template'),
events : {
"click option" : "selectForm"
},
initialize : function() {
this.listenTo(this.collection, "sync", this.render, this);
},
selectForm : function(e) {
e.preventDefault();
var id = $(e.currentTarget).attr("name");
var item = this.collection.get(id);
this.trigger("form:selectedForm", item.attributes.fields);
}
});
I think the error is in your showSelector view function, you are overwriting your forms collection in the second line,
i think your intention in that line was to assing the url of the forms collection so my guess is that this will fix it:
showSelectorView : function(view) {
var forms = new MyForms();
forms.url = this.urls.loadForms; /// Im assuming you were trying to pass the url here
var selectorView = new FormSelectorView({
collection: forms
});
forms.fetch();
this.listenTo(selectorView, "form:selectedForm", this.showForm);
view.show(selectorView);
},

How to bind elements in backbone?

I have a small backbone class:
view = Backbone.View.extend({
events: {
"click textarea" : "doSomething"
},
doSomething : function() {
var textarea = $(this.el).find('textarea')
// I would like to just call, this.textarea, or this.elements.textarea
}
});
Ideally I would like to be able to access my textarea through a variable instead of having to search the element every time. Anyone have any idea on how to accomplish this?
maybe i am under thinking it but how bout giving the textarea a class or id and target specifically when needed,
or create a sub view that generates the textarea
var View = Backbone.View.extend({
el: '#main',
initialize: function(){
this.render();
},
render: function() {
var subview = new SubView();
this.$('form').append(subview.el);
this.$('form').show();
},
});
var SubView = Backbone.View.extend({
tagName: 'textarea',
id: 'whateverId',
events: {
"click" : "doSomething"
},
initialize: function(){
this.render();
},
doSomething : function(event) {
var textarea = $(event.target);
// or var textarea = $(this.el);
},
render: function(){
return $(this.el);
}
});
The other answers get you the reference that you need, but if you really need a handle to the textarea, then you can do something like this:
view = Backbone.View.extend({
initialize: function() {
this.myTextareaElement = $(this.el).find('textarea')
}
events: {
"click textarea" : "doSomething"
},
doSomething : function() {
// use this.myTextareaElement ...
}
});
pass the event as the argument and then use the event target
view = Backbone.View.extend({
events: {
"click textarea" : "doSomething"
},
doSomething : function(event) {
var textarea = $(event.target);
}
});

Resources