Click event not firing in main view in Backbonejs - events

I have really weird problem. I am trying to implement "root" view which also works as some namespace structure. Same principle introduced in codeschool.com course part II. In this root view, I want to catch event "click button", but that's the problem. When I click on button nothing happened.
window.App = new (Backbone.View.extend({
el: $("#app"),
Collections: {},
Models: {},
Views: {},
Routers: {},
events: {
'click button' : function(e) {
alert("Thank god!");
}
},
render: function(){
//for test purposes
console.log($("#app").find("button"));
console.log(this.$el.find('button'));
},
start: function(){
this.render();
new App.Routers.PostsRouter();
Backbone.history.start({pushState: true});
}
}))();
$(document).ready(function() { App.start() });
The HTML look like this
<body>
<div id="app">
<div id="posts"></div>
<button>Click me</button>
</div>
</body>
And what's really weird is output from console.log in render function. Both selectors are same, even the context is same, so where is problem?
console.log($("#app").find("button")); //this returns correct button
console.log(this.$el.find('button')); //this returns 0 occurences (WTF?)
EDIT:
After little change at el: "#app", still same problem. Problem was (thanks to #nemesv) in instantiating this class before DOM is loaded. But however, it's not possible to instantiating after DOM is loaded, because then it's not possible to use that namespace structure (eg. App.Model.Post = Backbone.Model.extend() ). But this "main view with namespace structure" is introduced in codeschool.com course as some sort of good practice. Solution can be found there http://jsfiddle.net/BckAe

You have specified your el as a jquery selector but because you are inside an object literal it evaluates immediately so before the DOM has been loaded.
So the el: $("#app"), won't select anything.
You can solve this by using one of the backbone features that you can initilaize the el as a string containing a selector.
So change your el declaration to:
el: "#app"
Your click event is not triggered because you instantiate your view before the DOM is loaded so backbone cannot do the event delegation your you.
So you need separate your view declaration and creation into two steps. And only instantiate your view when the DOM is loaded:
var AppView = Backbone.View.extend({
el: "#app",
//...
});
$(document).ready(function()
{
window.App = new AppView();
App.start()
});
Demo: JSFiddle.

Related

Polymer catch all events from child element

I am currently working on making lazy-loading possible together with "more-routing" in Polymer. I already got a first working implementation of lazy loading (inspired by app-router that unfortunately does not support Polymer 1.0 completely), but I have no idea how I can pass all events from the imported child element up to my lazy-loading element.
So, in my example I want the event "hi" passed from page-login to lazy-loader and from lazy-loader upwards to the element containing lazy-loader - but without having knowledge of that specific event in lazy-loader. So I need something like "catch all events fired by child element".
That's what I have :
page-login:
ready:function() {
this.fire("hi");
},
using lazy-loader:
<lazy-loader on-hi="hi" href="../pages/page-login.html" id="login" route="login"></lazy-loader>
calling load :
document.getElementById("login").load()
Lazy-loader (reduced version):
<dom-module id="lazy-loader">
<template>
<span id="content">Loading...</span>
</template>
<script>
window.lazyLoaded = window.lazyLoaded || [];
Polymer({
is: "lazy-loader",
properties: {
href:{
type:String,
value:""
},
element:{
type:String,
value:""
}
},
load:function(finishFunc) {
if(window.lazyLoaded.indexOf(this.href)===-1) {
var that = this;
this.importHref(this.href, function(e) {
window.lazyLoaded.push(that.href);
if(!that.element) {
that.element = that.href.split('/').reverse()[0];
that.element = that.element.split(".")[0];
}
var customElement = document.createElement(that.element);
// here I need something like "customElement.on("anyEvent")"
// => that.fire("anyEvent")
Polymer.dom(that.$.content).innerHTML = "Loading done.";
Polymer.dom(that.$.content).appendChild(customElement);
Polymer.dom.flush();
that.fire("loaded");
});
}
}
});
</script>
It seems like I have to revoke my question - If I fire the event in the attached-handler, not in ready, it gets passed through automatically - at least in the current version of Polymer. Not sure if this is intended behavior, but for now the problem is solved. An answer still would be interesting, maybe for other cases (like event logging)

Encapsulation with React child components

How should one access state (just state, not the React State) of child components in React?
I've built a small React UI. In it, at one point, I have a Component displaying a list of selected options and a button to allow them to be edited. Clicking the button opens a Modal with a bunch of checkboxes in, one for each option. The Modal is it's own React component. The top level component showing the selected options and the button to edit them owns the state, the Modal renders with props instead. Once the Modal is dismissed I want to get the state of the checkboxes to update the state of the parent object. I am doing this by using refs to call a function on the child object 'getSelectedOptions' which returns some JSON for me identifying those options selected. So when the Modal is selected it calls a callback function passed in from the parent which then asks the Modal for the new set of options selected.
Here's a simplified version of my code
OptionsChooser = React.createClass({
//function passed to Modal, called when user "OK's" their new selection
optionsSelected: function() {
var optsSelected = this.refs.modal.getOptionsSelected();
//setState locally and save to server...
},
render: function() {
return (
<UneditableOptions />
<button onClick={this.showModal}>Select options</button>
<div>
<Modal
ref="modal"
options={this.state.options}
optionsSelected={this.optionsSelected}
/>
</div>
);
}
});
Modal = React.createClass({
getOptionsSelected: function() {
return $(React.findDOMNode(this.refs.optionsselector))
.find('input[type="checkbox"]:checked').map(function(i, input){
return {
normalisedName: input.value
};
}
);
},
render: function() {
return (
//Modal with list of checkboxes, dismissing calls optionsSelected function passed in
);
}
});
This keeps the implementation details of the UI of the Modal hidden from the parent, which seems to me to be a good coding practice. I have however been advised that using refs in this manner may be incorrect and I should be passing state around somehow else, or indeed having the parent component access the checkboxes itself. I'm still relatively new to React so was wondering if there is a better approach in this situation?
Yeah, you don't want to use refs like this really. Instead, one way would be to pass a callback to the Modal:
OptionsChooser = React.createClass({
onOptionSelect: function(data) {
},
render: function() {
return <Modal onClose={this.onOptionSelect} />
}
});
Modal = React.createClass({
onClose: function() {
var selectedOptions = this.state.selectedOptions;
this.props.onClose(selectedOptions);
},
render: function() {
return ();
}
});
I.e., the child calls a function that is passed in via props. Also the way you're getting the selected options looks over-fussy. Instead you could have a function that runs when the checkboxes are ticked and store the selections in the Modal state.
Another solution to this problem could be to use the Flux pattern, where your child component fires off an action with data and relays it to a store, which your top-level component would listen to. It's a bit out of scope of this question though.

backbone click event fires as many times as there is views

I searched info on this topic but found only info about getting event element.
Yes, I can get an element of clicked div, but why it's fired all 19 times? (it's the number of views total). Info of clicked event is same - of the clicked div.
Here is what divs look like: http://d.pr/i/AbJP
Here is console.log: http://d.pr/i/zncs
Here is the code of index.js
$(function () {
var Table = new Backbone.Collection;
var TrModel = Backbone.Model.extend({
defaults: {
id: '0',
name: 'defaultName'
},
initialize: function () {
this.view = new Tr({model: this, collection: Table});
this.on('destroy', function () {
this.view.remove();
})
}
});
var Tr = Backbone.View.extend({
el: $('.pop-tags').find('.container'),
template: _.template($('#td_template').html()),
events: {
'click .tag': 'clicked'
},
clicked: function (event) {
event.preventDefault();
event.stopPropagation();
console.log(event.currentTarget);
},
initialize: function () {
this.render();
},
render: function () {
this.$el.append(this.template(this.model.attributes));
return this;
}
});
for (var i = 0, size = 19; i < size; i++) {
var trModel = new TrModel({id: i, name: 'name_' + i});
Table.add(trModel);
}
});
How can I avoid all elements from firing an event and fire only one that clicked and only 1 time?
el: $('.pop-tags').find('.container'),
Don't do that. You are attaching every view instance to the same DOM node. Each backbone view needs a distinct DOM node or, as you see, delegate events become complete chaos. In your view, set tagName: 'tr', then when creating your views, create them, call .render() and then append them to the DOM with something like $('.table-where-views-go').append(trView.el);.
You also may want to brush up on the basic MVC concept because Tables and Rows are view-related notions, not model-related, so a class called TrModel is a code smell that you aren't clear on Model vs View.
I would use a slightly different approach to solve your problem.
Instead for one view for every tr I would create one view for the entire table.
When I create the view I would pass the collection containing the 19 models to the view and in view.initialize use the collection to render the rows.
I created a jsbin with a working example.

Backbone.js: Dynamically adding events not firing

I am trying around with Backbone.js and get along so far, but I have got a problem.
Lets say I got a root element and a child element.
When the document loads, I create 3 "root" instances. The root instance appends a tag.
Each root instance creates one child instance which creates a tag in the ul tag.
Now I would like the child instance to attach and onclick event to the tag. Unfortunately, it won't work.
I created a fiddle:
http://jsfiddle.net/Fluxo/sEjE5/17/
var child = Backbone.View.extend({
template: _.template('<li>Item '+count+'</li>'),
events: {
'click li': function() {
alert('listitem Click Child Element');
}
},
initialize: function() {
_.bindAll('render');
this.render();
}, render: function() {
this.$el.html(this.template())
}
});
var root = Backbone.View.extend({
template: _.template('<div><h3>List</h3><p /><ul></ul><hr />'),
events: {
'click li': function() {
alert('listitem Click - Root Element');
}
},
initialize: function() {
_.bindAll('render');
this.render();
},
render: function() {
this.$el.html(this.template());
$('body').append(this.el);
var item = new child();
this.$el.find('ul').append(item.$el.html());
}
});
The events created in the root element will fire, but not the ones in the child element.
Am I doing anything wrong?
You're doing two things wrong.
First of all, your child is an <li>, it doesn't contain an <li>:
template: _.template('<li>Item '+count+'</li>'),
events: {
'click li': ...
},
so your click li event won't do anything. Events are bound to the view's el using delegate:
delegateEvents delegateEvents([events])
Uses jQuery's delegate function to provide declarative callbacks for DOM events within a view. [...] Omitting the selector causes the event to be bound to the view's root element (this.el).
So if you want to bind a click handler directly to the view's el rather than one of its children, you want to leave out the selector:
events: {
'click': ...
}
The next problem is that you're not actually inserting the child element into the DOM, you're copying the HTML:
this.$el.find('ul').append(item.$el.html());
By appending item.$el.html() instead of item.el, you're grabbing the correct HTML as a string and inserting that HTML but you lose the events in the process; the events are bound to the DOM object, item.el, not to the HTML string. You can fix this by appending item.el:
this.$el.find('ul').append(item.el);
// Or you could say, the two approaches are the same
this.$('ul').append(item.el);
Demo: http://jsfiddle.net/ambiguous/K76JM/ (or http://jsfiddle.net/ambiguous/kFxHQ/)

backbone.js: understanding browser event handling and view removing

I'm fiddling with a view and related model that look like that:
App.Views.Addresses = App.Views.Addresses || {};
App.Views.Addresses.Address = Backbone.View.extend({
events: {
"click button#foo" : "clear"
},
initialize: function(model){
this.address = model.model;
this.address.view = this;
_.extend(this, Backbone.Events);
this.render();
},
render: function(){
... rendering stuff
},
clear: function(){
this.address.clear();
}
});
and
var Address = Backbone.Model.extend({
url: function() {
... url stuff
},
clear: function(){
this.destroy();
this.view.remove();
}
});
I'm facing two problems here. The first one:
I have a button with id="foo" in my source and would like the view to catch the 'click' event of this very button and fire the 'clear' event. Problem: This does not work.
Anyway calling 'clear' on my model by hand cleanly removes the data on the server but does not remove the view itself. Thats the second problem. Hopefully someone more experienced can enlighten me.
Thx in advance
Felix
First problem:
Your button must be inside the element rendered by the view.
backbone scope events to inner elements only
You must render your view within this.el element
backbone use that element for delegation
Second problem:
Use events to destroy your view
You should not store the view in the model. This is kind of a "no no" in MVC. Your model already emits a "remove" event when deleted. Your view should listen to it and behave accordingly.
You must remove your view element from the DOM yourself
This is not handled by backbone.
Other general comments:
Views already are extending Backbone.Events
Use this.model instead of this.address

Resources