Adding a model to a Marionette CollectionView's collection doesn't trigger onItemAdd callback - marionette

So, I'm not sure I quite understand how this callback is supposed to be triggered. If you take a barebones model,collection and views:
PatchModel = Backbone.Model.extend({});
PatchCollection = Backbone.Collection.extend({model: PatchModel});
PatchView = Backbone.Marionette.ItemView.extend({template:'#patchview'});
PatchCollectionView = Backbone.Marionette.CollectionView.extend({
itemView:PatchView
,onItemAdded: function(itemView){
console.log("item was added");
}
});
And instantiate them like this:
Patch0 = new PatchModel({});
Patch1 = new PatchModel({});
Patches = new PatchCollection();
PatchesView = new PatchCollectionView({collection:Patches,el:"dom_id"});
Patches.add(Patch0);
PatchesView.render();
Patches.add(Patch1);
The PatchesView onItemAdded callback never triggers. Hmm...

It looks like the docs are out of date. You should use onAfterItemAdded or onBeforeItemAdded
This was changed in v1.0.0-rc2
*BREAKING: * Changed the item:added event to before:item:added and after:item:added
Link
Here is a fiddle with your example reworked.
http://jsfiddle.net/puleos/axJg4/
var PatchCollectionView = Backbone.Marionette.CollectionView.extend({
itemView: PatchView,
initialize: function() {
_.bindAll(this);
},
onAfterItemAdded: function(itemView){
console.log("item was added");
},
onRender: function(){
console.log("render");
}
});

Related

Custom Validation causing computed values to fire

For a quick view of my problem I have made a working jsFiddle here:
In KnockoutJS I have made a custom extender validator to test if the input format is in the HHMM format. If it is it returns the new value, if it doesn't it will set it back to the old value this is currently working.
ko.extenders.acValidTimeHHMM = function (target, options) {
var result = ko.computed({
read: target,
write: function (newValue) {
var re = /^([0-9]|0[0-9]|1[0-9]|2[0-3])[0-5][0-9]$/;
if (!re.test(newValue)) {
target.notifySubscribers(target());
//Time not in correct format return old time
return;
}
target(newValue);
}
}).extend({ notify: 'always' });
result(target());
return result;
};
The problem I am having is that I update my database when the value changes using a computed. However this is also firing when I reset the value back to its original using my validator. (Method based on Ryan Rahlf dirty flag technique here )
self.update = ko.computed(function () {
self.timeOne();
self.timeTwo();
alert("Fired");
});
The problem is obviously the line target.notifySubscribers(target()); in my validator. However without this line I can't reset the value to its old value and I can't find another way to do this.
So this only fires when a value actually changes rather then the validator resetting it. The jsFiddle demonstrates my problem exactly and can be used to make a working version (hopefully) I know its currently firing on page load too.
The problem I am having is that I update my database when the value changes using a computed.
I don't know all your logic, but I don't think this is a good idea to update the db each time your knockout view model has updated. May be you should look at knockout validation plugin. Using this plugin you can build the same custom validation rule and update the db only on form submit event.
About your problem...
The simplest solution I'm found is to send a success callback function to the validation extension like an option.
Something like this.
JS:
var ViewModel = function() {
var update = function () {
alert("value was successfully changed");
};
var cancel = function () {
alert("validation failed. previous value was returned");
};
var timeOne = ko.observable("1100").
extend({
acValidTimeHHMM: {
success: update,
fail: cancel
}
});
var timeTwo = ko.observable("1248").
extend({ acValidTimeHHMM: { success: update } });
return {
timeOne: timeOne,
timeTwo: timeTwo
};
};
ko.extenders.acValidTimeHHMM = function(target, option) {
var baseOptions = {
success: null,
fail: null
};
$.extend(baseOptions, option);
var result = ko.computed({
read: target,
write: function (newValue) {
var oldValue = target();
if(newValue == oldValue) return;
var re = /^([0-9]|0[0-9]|1[0-9]|2[0-3])[0-5][0-9]$/;
if (!re.test(newValue)) {
target.notifySubscribers(oldValue);
if(typeof(baseOptions.fail) == "function")
baseOptions.fail();
return;
}
target(newValue);
if(typeof(baseOptions.success) == "function")
baseOptions.success()
}
}).extend({ notify: 'always' });
result(target());
return result;
};
ko.applyBindings(new ViewModel());
HTML:
<p>Time One<input data-bind='value: timeOne' /></p>
<p>Time Two<input data-bind='value: timeTwo' /></p>

Backbone.Marionette: Pass data down through a CompositeView to its itemView?

I'm wondering if/how a CompositeView can pass data down into its defined itemView. Example (reduced) code:
var TableView = Backbone.Marionette.CompositeView.extend({
template: '#table-template',
itemView: TableRowView,
itemViewContainer: 'tbody',
});
var TableRowView = Backbone.Marionette.ItemView.extend({
tagName: 'tr',
template: '#table-row-template',
serializeData: function () {
var data = {
model: this.model,
// FIXME This should really only be called once. Pass into TableView, and down into TableRowView?
// That way, getDisplayColumns can be moved to the collection as well, where it makes more sense for it to belong.
columns: this.model.getDisplayColumns()
};
return data;
}
});
I'm using the two to render a html table. #table-row-template has some render logic for supporting different types of "columns". This allows me to use the same views for different types of Collections/Models (as long as they follow the API). So far, it's working pretty well!
However, as you can see above, each "row" makes a call to get the same "columns" data every time, when really I just wanted to pass that on down once, and use for all.
Recommendations?
Thanks!
You can use itemViewOptions either as an object or a function
var TableView = Backbone.Marionette.CompositeView.extend({
template: '#table-template',
itemView: TableRowView,
itemViewContainer: 'tbody',
itemViewOptions: {
columns: SOMEOBJECTORVALUE
}
});
OR
var TableView = Backbone.Marionette.CompositeView.extend({
template: '#table-template',
itemView: TableRowView,
itemViewContainer: 'tbody',
itemViewOptions: function(model,index){
return{
columns: SOMEOBJECTORVALUE
}
}
});
and then receive the options with:
var TableRowView = Backbone.Marionette.ItemView.extend({
tagName: 'tr',
template: '#table-row-template',
initialize: function(options){
this.columns = options.columns;
}
});
(* Note that itemView, itemViewContainer and itemViewOptions are changed in version 2 to childView , childViewContainer and childViewOptions).

How to use marionette collectionView. I am using https://github.com/marionettejs/backbone.marionette as boilerplate

I want to know how to use marionette collectionView. I am using https://github.com/marionettejs/backbone.marionette as boilerplate.
Currently I am unable to see anything rendered by collection view.
Backbone Collection
define(["jquery","backbone","models/Store"],
function($, Backbone, Store) {
// Creates a new Backbone Collection class object
var StoreCollection = Backbone.Collection.extend({
// Tells the Backbone Collection that all of it's models will be of type Model (listed up top as a dependency)
model: Store
});
storeCollectionObj = new StoreCollection();
storeCollectionObj.add({"title":"wherwr werwe"});
return storeCollectionObj;
});
Model
define(["jquery", "backbone"],
function($, Backbone) {
// Creates a new Backbone Model class object
var Store = Backbone.Model.extend({
// Model Constructor
initialize: function() {
},
// Default values for all of the Model attributes
defaults: {
},
// Get's called automatically by Backbone when the set and/or save methods are called (Add your own logic)
validate: function(attrs) {
}
});
// Returns the Model class
return Store;
}
);
itemView
define( ['underscore', 'jquery', 'handlebars', 'text!templates/merchant/store.html'],
function(_, $, Handlebars, template) {
//ItemView provides some default rendering logic
return Backbone.Marionette.ItemView.extend( {
//Template HTML string
template: Handlebars.compile(template),
id: "store",
attributes: function () {
return {class :"storeView"}
},
// View Event Handlers
events: {
}
});
});
CollectionView
define( ['underscore', 'jquery', 'handlebars' , 'views/merchant/StoreView','text!templates/merchant/storeCollection.html' , 'collections/StoreCollection'],
function(_, $, Handlebars, SroreView, template, StoreCollection) {
SroreViewObj = new SroreView();
//storeCollectionObj = new StoreCollection();
//alert(StoreCollection);
//StoreCollection.add({"title":"sdfsadfasd"});
//ItemView provides some default rendering logic
StoreCollectionView = Backbone.Marionette.CollectionView.extend({
id : "StoreListing",
template: Handlebars.compile(template),
itemView: SroreViewObj,
collection : StoreCollection,
render: function(){
}
});
return StoreCollectionView;
});
I found my mistake. I was passing itemView Object in my collection view.
line itemView: SroreViewObj should be itemView: SroreView
fixed just by replacing "SroreViewObj" with "SroreView" (itemView definition)
SroreViewObj = new SroreView();
//storeCollectionObj = new StoreCollection();
//alert(StoreCollection);
//StoreCollection.add({"title":"sdfsadfasd"});
//ItemView provides some default rendering logic
StoreCollectionView = Backbone.Marionette.CollectionView.extend({
id : "StoreListing",
template: Handlebars.compile(template),
itemView: SroreViewObj, // This should not be an object. So fixed just by replacing "SroreViewObj" with "SroreView" (itemView definition)**
collection : StoreCollection,
render: function(){
}
});

Backbone click event fires events for all collection rather than model

Can't figure out what's wrong. When I click on a model title, it fetches all models in collection at once rather than fetch one model. If I move this event from logView to logsView it works properly but doesn't have access to model, well I can find this model using index or ant other model's ID but don't think this is a nice way.
var Log = Backbone.Model.extend({});
window.LogsList = Backbone.Collection.extend({
model:Log,
url:function (tag) {
this.url = '/logs/' + tag;
return this;
}
});
window.colList = new LogsList();
window.logView = Backbone.View.extend({
el:$('.accordion'),
template:_.template($('#log').html()),
initialize:function () {
this.model.bind('add', this.render, this);
},
events:{
"click .accordion-toggle" :"getLogBody"
},
render:function () {
return this.template(this.model.toJSON());
},
getLogBody:function () {
this.model.fetch();
}
});
window.LogsView = Backbone.View.extend({
el:$("#content"),
initialize:function (options) {
colList.bind('reset', this.addAll, this);
colList.url(options.data).fetch();
},
addOne:function (model) {
var view = new logView({model:model});
$("#accordion").append(view.render());
},
addAll:function () {
colList.each(this.addOne);
}
});
window.listView = new LogsView({data:"Visa_Cl"});
The problem is caused by your el in the LogView: el:$('.accordion')
Backbone's view events are scope to the view's el. In this case, you've specified the view's el as ALL HTML elements with a class of "accordion". Therefore, when you click on any of your HTML elements with this class, the code runs for all of them, which is why you are seeing this behavior.
This article will show you a few options for doing what you want, correctly:
Backbone.js: Getting The Model For A Clicked Element
I would also recommend reading this one, to better understand the use of el in Backbone, and a few of the tricks and traps of it:
Backbone.js: Object Literals, Views Events, jQuery, and el

jQuery monitoring form field created by AJAX query

Preface: I am sure this is incredibly simple, but I have searched this site & the jQuery site and can't figure out the right search term to get an answer - please excuse my ignorance!
I am adding additional form fields using jQuery's ajax function and need to then apply additional ajax functions to those fields but can't seem to get jQuery to monitor these on the fly form fields.
How can I get jQuery to use these new fields?
$(document).ready(function() {
$('#formField').hide();
$('.lnk').click(function() {
var t = this.id;
$('#formField').show(400);
$('#form').load('loader.php?val=' + t);
});
//This works fine if the field is already present
var name = $('#name');
var email = $('#email');
$('#uid').keyup(function () {
var t = this;
if (this.value != this.lastValue) {
if (this.timer) clearTimeout(this.timer);
this.timer = setTimeout(function () {
$.ajax({
url: 'loader.php',
data: 'action=getUser&uid=' + t.value,
type: 'get',
success: function (j) {
va = j.split("|");
displayname = va[1];
mail = va[2];
name.val(displayname);
email.val(mail);
}
});
}, 200);
this.lastValue = this.value;
}
});
});
So if the is present in the basic html page the function works, but if it arrives by the $.load function it doesn't - presumably because $(document).ready has already started.
I did try:
$(document).ready(function() {
$('#formField').hide();
$('.lnk').click(function() {
var t = this.id;
$('#formField').show(400);
$('#form').load('loader.php?val=' + t);
prepUid();
});
});
function prepUid(){
var name = $('#name');
var email = $('#email');
$('#uid').keyup(function () {
snip...........
But it didn't seem to work...
I think you are close. You need to add your keyup handler once the .load call is complete. Try changing this...
$('#form').load('loader.php?val=' + t);
prepUid();
To this...
$('#form').load('loader.php?val=' + t, null, prepUid);
What you are looking for is the jquery live function.
Attach a handler to the event for all elements which match the current selector, now or in the future
You can do something like this:
$('.clickme').live('click', function() {// Live handler called.});
and then add something using the DOM
$('body').append('<div class="clickme">Another target</div>');
When you click the div added above it will trigger the click handler as you expect with statically loaded dom nodes.
You can read more here: http://api.jquery.com/live/

Resources