How to use modelEvents in Marionette.js - javascript

I have 2 textfields with id's source,destination. If any field value changes that corresponding model attribute will be change. I did this one using Backbone.Model and events object in Marionette.CompositeView. It's working fine.
Once any model Attribute change corresponding function will call. For this I written the following code. It's not working the problem was even one attribute changes both functions are evaluating.
model Code:
var mapModel = Backbone.Model.extend({
defaults: {
startPlace: "",
endPlace: ""
}
});
Marionette.CompositeView code:
var mapView = Marionette.CompositeView.extend({
events: {
"blur #source": "sAttributeSetting",
"blur #destination": "dAttributeSetting"
},
dAttributeSetting: function() {
this.model.set({"endPlace": document.getElementById(this.ui.destinationPlace).value});
},
sAttributeSetting: function() {
this.model.set({"startPlace": document.getElementById(this.ui.sourcePlace).value});
},
modelEvents: {
"change startPlace": "startMarkerDisplay",
"change endPlace": "endingMarkerDisplay"
},
startMarkerDisplay: function() {
alert("start");
},
endingMarkerDisplay: function() {
alert("end");
}
});
html code:
<input type="text" id="source">
<input type="text" id="destination">
creating instance for both model and view
mapModelObj = new mapModel();
var mapViewObj = new mapView({el:$('#mapDiv'), model:mapModelObj});
problems:
Initially If I enter any value in first field(source) getting 2 alert boxes("start", "end").
Initially If you enter any value in second field(destination) getting 4 alert boxes("start", "end", "start", "end")
I tried alot but I didn't understand where I am getting the problem
Can anyone help me.
Thanks

modelEvents should be connected by :. Say, event of changing startPlace should be
'change:startPlace'
If you use space you'll end with two events, not one event specific to this attribute.
Your code 'change startPlace' represents two events, one is 'change', the other is 'startPlace'. So you'll see "start","end","start","end"

My observations are the following for your solution (however I propose a second solution at the bottom):
The binding of entity event has colon syntax. It should be a hash of { "event:name": "eventHandler" } configuration. Multiple handlers can be separated by a space. A function can be supplied instead of a string handler name.
You can use advantage of the el property of the backbone view.
Instead of using document.getElementById(this.ui.sourcePlace), you can use this.$('#source'). This latest searches only in the context of el rather than searching the whole dom. This way the evaluation will be way faster... That way you should use this expression: this.$('.destination').val()
Please check my jsfiddle about your issue: http://jsfiddle.net/orbanbotond/VEcK6/
The code is the following:
var mapModel = Backbone.Model.extend({
defaults: {
startPlace: "",
endPlace: ""
}
});
var mapView = Marionette.CompositeView.extend({
events: {
"blur .source": "sAttributeSetting",
"blur .destination": "dAttributeSetting"
},
dAttributeSetting: function(){
console.log('end blured');
console.log('input value:' + this.$('.destination').val());
this.model.set({
"endPlace": this.$('.destination').val()
});
console.log('endplace set to: ' + this.model.get('endPlace'));
},
sAttributeSetting: function() {
console.log('start blured');
console.log('input value:' + this.$('.source').val());
this.model.set({
"startPlace": this.$('.source').val()
});
console.log('startPlace set to: ' + this.model.get('startPlace'));
},
modelEvents: {
"change:startPlace": "startMarkerDisplay",
"change:endPlace": "endingMarkerDisplay"
},
startMarkerDisplay: function () {
alert("start");
},
endingMarkerDisplay: function () {
alert("end");
}
});
$(document).ready(function(){
var mapModelObj = new mapModel();
var mapViewObj = new mapView({
el: $('#mapDiv'),
model: mapModelObj
});
});
My proposed second solution:
Use the stickit library which does all you are doing. You only need to define the mapping between the dom selector and the observed model attribute.
Here is the jsfiddle for it: http://jsfiddle.net/orbanbotond/fm64P/
Here is the code:
var mapModel = Backbone.Model.extend({
defaults: {
startPlace: "initialStartPlace",
endPlace: "initialEndplace"
},
});
var mapView = Marionette.CompositeView.extend({
template: "#mapDiv",
events: {
"blur .source": "sAttributeSetting",
"blur .destination": "dAttributeSetting"
},
bindings: {
'.source': {
observe: 'startPlace'
},
'.destination': {
observe: 'endPlace'
}
},
onRender: function() {
this.stickit();
console.debug("Sticked to it already");
},
});
$(document).ready(function(){
var mapModelObj = new mapModel();
var mapViewObj = new mapView({
el: $('#mapDiv'),
model: mapModelObj
});
mapViewObj.render();
mapModelObj.bind('change:startPlace', function(obj){alert("New value: " + obj.get('startPlace'));});
mapModelObj.bind('change:endPlace', function(){alert("New value: " + obj.get('endPlace'));});
});
For every code sample I used this template (I used class selectors instead of id selectors):
<div id="mapDiv">
<input type="text" class="source">
<input type="text" class="destination">
</div>

Related

Correct way to trigger backbone model change event

I am trying to create simple Backbone example but i don't understand what is the problem with my code. Why is there 2 testAttr attributes(on directly on object and one in attributes object) and why isn't change event triggering on any of the changes? Also i don't understand what is the correct way to set attributes on model?
Heres my code:
<div id="note"></div>
<script>
var NoteModel = Backbone.Model.extend({
defaults: function() {
return {
testAttr: "Default testAttr"
}
}
});
var NoteView = Backbone.View.extend({
initialize : function() {
this.listenTo(this.model, "change", this.changed());
},
el: "#note",
changed: function () {
debugger;
console.log("change triggered");
this.render();
},
render : function() {
this.$el.html("<h1>" + this.model.get("testAttr") + "</h1>");
return this;
}
});
var note = new NoteModel();
var noteView = new NoteView({model: note});
noteView.render();
note.set("testAttr", "blah1");
note.testAttr = "blah2";
</script>
Change this line:
this.listenTo(this.model, "change", this.changed());
to this:
this.listenTo(this.model, "change", this.changed);
For the second part of your question, using .set() goes through a set of dirty-checking to see if you changed, deleted or didn't change anything, then triggers the appropriate event. It isn't setting the object.property value, it's setting the object.attributes.property value (tracked by backbone.js). If you directly change the object property, there's nothing to initiate that event for you.
...unless of course you use the AMAZING AND TALENTED Object.observe()!!!1! - ITS ALIVE (in Chrome >36)

backbone collection url manipulation with input field text

I'm new to Backbone. I have a collection whose url function depends on the textfield text. How do i get that text from my textfield. No i don't want to use JQuery selectors as accessing outside selectors from your views aint a good practice. My HTML stucture is like:
<div id='outer'>
<input type='text' id='xyz'>
<div id='image123'></div>
<div id='div1'>
<ul>
</ul>
</div>
<div id='div2'></div>
</div>
So i got 2 views, 1 collections & 1 model.
How do i get the input text in the collection without using JQuery selectors from my 'outer' view.
[Post updated with View code]
var outerView = Backbone.View.extend
({
el: '#outer',
initialize: function()
{
},
events:
{
'keyup #xyz' : 'keyfunc'
},
keyfunc: function()
{
// inputtext is a global variable & i don't want it that way
inputtext = $('#xyz').val();
},
render: function()
{
},
});
I couldn't figure what are you doing , but only if you want to send the value to the collection with out making it global and changing the url ,try doing it this way
var outerView = Backbone.View.extend
({
el: '#outer',
initialize: function()
{
},
events:
{
'keyup #xyz' : 'keyfunc'
},
keyfunc: function()
{
// not global now
var inputtext = $('#xyz').val();
var myclooction = new MainCollection({
text : inputtext
});
},
render: function()
{
},
});
in the collection
var MainCollection = Backbone.extend.collection({
url : function(){
return "someurl/"+this.text;
},
// receive the value here
initialize:function(options){
this.text = options.text;
}
});
Backbone passes information about the element that triggered the event, and there you can find that value like so:
keyfunc: function(e)
{
inputtext = $(e.currentTarget).val();
this.model.trigger('textChanged', {id: this.myID, data: inputtext});
}
and your model can listen that event like this in its initialize-function.
this.listenTo(this, 'textChanged', this.textChangedHandler);
And the model can then decide what to do with that event. For example to send it to that another view by another event.
textChangeHandler: function (e) {
this.trigger('someTextChanged', e);
}
And your views or collections can listen that event in their initialize-function:
this.listenTo(this.model, 'someTextChanged', this.textChangedHandler);
And the handler would be something like:
textChangeHandler: function (e) {
if (e.id !== this.myID) {
//do stuff
}
}

Hammer Js Not working with Backbone.js

I have problem in implementing touch events with backbone.js and hammer.js
I have tried implementing the touch in the conventional way i.e defining the touch events in "events" section.But it has not worked for me.
Please find my code below
define(['Backbone','underscore','Handlebars','Hammer'],function(Backbone,_,Handlebars,Hammer) {
//getting the type of device in a variable using "userAgent"
window.MOBILE = navigator.userAgent.match(/mobile/i);
var HeadderView = Backbone.View.extend(
{
el: 'body',
touchPrevents : false,
initialize: function()
{
this.el$ = $(this.el);
},
events: function() {//returning different functions based on the device
return MOBILE ?
{
'tap #headcontent': 'handleTap',
} :
{
'click #headcontent':'clickbackbone',
}
},
//declaring the corresponding functions
handleTap: function(){
alert("tap event");
},
clickbackbone:function(){
alert('backbone click');
},
render: function ()
{
//rendering the template and appending it to the page
var that = this;
require(['text!gaming/gameHeadder/headder.html'],function(HeaderTemplate){
var template = Handlebars.compile(HeaderTemplate);
var context = {title: "Tick Tack Toe", imageURL: "images/logo.jpg"}
var htmlTemplate = template(context);
that.el$.html( htmlTemplate);
});
},
});
return new HeadderView();
}
);
Can some one help me out and correct my code
This is not how Hammer works. Backbone knows nothing about HammerJS.
If you really want to use Backbone-style event delegation with Hammer, you might want to check out the backbone.hammer project.

Backbone model method not incrementing

I'm trying to grok Backbone a little more, and from someone who has only used Backbone views in the past, I'm now trying my hand with Models and Collections.
Right now, when I post a comment, I try to increment the comment count.
Model:
Comment = Backbone.Model.extend({
defaults: {
text: null,
count: 0
},
updateCount : function() {
console.log(this.set('count', this.get('count') + 1));
console.log(this.get('count'));
}
});
Collection:
CommentsCollection = Backbone.Collection.extend({
model: Comment,
initialize: function (models, options) {
this.on("add", options.view.appendComment);
this.on('add', options.view.resetComment);
}
});
View:
CommentsView = Backbone.View.extend({
el: $("body"),
initialize: function () {
_.bindAll(this,
'addComment',
'appendComment',
'resetComment'
);
this.comments = new CommentsCollection(null, {
model: Comment,
view: this
});
},
events: {
"click #post-comment": "addComment"
},
addComment: function (evt) {
var $target = $(evt.currentTarget);
var $container = $target.closest('#comment-wrapper');
var text = $container.find('textarea').val();
var comment = new Comment({
text: text
});
//Add a new comment model to our comment collection
this.comments.add(comment);
return this;
},
appendComment: function (model) {
$('#comments').prepend('<div> ' + model.get('text') + '</div>');
model.updateCount();
return this;
},
resetComment: function () {
$('textarea').val('');
}
});
Why is it always returning 1 (add a comment and click Post then view the console to see)?
Demo: http://jsfiddle.net/ZkBWZ/
This is happening because you're storing the count on the Comment model. Each time you hit the submit button, you create a new Comment which has the count set to the default, 0. The method updateCount then updates the count on that brand new model, so you're always seeing 1.
If you're just looking to determine how many comments have been made, I'd suggest you just look at the size of the CommentsCollection. In appendComment, you can do it this way:
appendComment: function (model) {
$('#comments').prepend('<div> ' + model.get('text') + '</div>');
// Get the number of comments
console.log(model.collection.models.length);
return this;
},

Marionette's CompositeView event not firing

I'm having trouble using Marionette's CompositeView. I render my model in my CompositeView using a template and want to add a click event to it. Somehow I can't get the events to work using the events: { "click": "function" } handler on the CompositeView... What am I doing wrong?
var FactsMenuItem = Backbone.Marionette.ItemView.extend({
template: tmpl['factsmenuitem'],
initialize: function() {
console.log('factsmenuitem');
},
onRender: function() {
console.log('factsmenuitem');
}
});
var FactsMenuView = Backbone.Marionette.CompositeView.extend({
template: tmpl['factsmenu'],
itemView: FactsMenuItem,
itemViewContainer: ".subs",
events: {
'click': 'blaat'
},
blaat: function() {
console.log('this is not working');
},
initialize: function() {
this.model.get('pages').on('sync', function () {
this.collection = this.model.get('pages');
this.render();
}, this);
},
onRender: function() {
console.log('render factsmenu');
}
});
var FactsLayout = Backbone.Marionette.Layout.extend({
template: tmpl['facts'],
regions: {
pages: ".pages",
filter: ".filter",
data: ".data"
},
initialize: function(options) {
this.currentPage = {};
this.factsMenu = new FactsMenu();
this.factsView = new FactsMenuView({model: this.factsMenu});
},
onRender: function() {
this.pages.show(this.factsView);
}
});
Edit:
I removed some code that made the question unclear...
The problem lies that the events of the non-collectionview of the compositeview (the modelView??) are not fired. I think this has something to do with the way the FactsLayoutView instantiates the compositeview...
The problem was caused by the way the region was rendered. In my FactsLayout is used this code:
initialize: function(options) {
this.currentPage = {};
this.factsMenu = new FactsMenu();
this.factsView = new FactsMenuView({model: this.factsMenu});
},
onRender: function() {
this.pages.show(this.factsView);
}
Apparently you can't show a view on a onRender function... I had to change the way the FactsLayout is initialized:
var layout = new FactsLayout({
slug: slug
});
layout.render();
var factsMenu = new FactsMenu({ slug: slug });
var factsView = new FactsMenuView({model: factsMenu});
layout.pages.show(factsView);
Maybe I did not understand your question well but if you need to listen an event fired from an item view within your composite view you should do like the following.
Within the item view test method.
this.trigger("test");
Within the composite view initialize method.
this.on("itemview:test", function() { });
Note that when an event is fired from an item of a CollectionView (a CompositeView is a CollectionView), it is prepended by itemview prefix.
Hope it helps.
Edit: Reading you question another time, I think this is not the correct answer but, about your question, I guess the click in the composite view is captured by the item view. Could you explain better your goal?

Categories