I have these divs called beats, and I want to register when they are clicked. However, I can't seem to get them to register a click at all, either by clicking them, or calling a JQuery click event on a specific div in the console. Either way, nothing is getting registered.
The measureView.js creates this beatView, which creates a beat inside the parent measure.
beatView.js :
//filename: views/beats/beatView.js
/* This is the view for a single beat, which is contained in a measure view. */
define([ 'jquery', 'underscore', 'backbone', 'backbone/models/beat', 'text!backbone/templates/measures/audioMeasures.html', 'text!backbone/templates/beats/linearBarBeats.html', 'text!backbone/templates/beats/linearBarSVGBeats.html', 'text!backbone/templates/beats/circularPieBeats.html', 'app/dispatch', 'app/log'
], function($, _, Backbone, BeatModel, audioMeasuresTemplate, linearBarBeatsTemplate, linearBarSVGBeatsTemplate, circularPieBeatsTemplate, dispatch, log){
return Backbone.View.extend({
//registering backbone's click event to our toggle() function.
events : {
'click' : 'toggle'
},
//The constructor takes options because these views are created by measuresView objects.
initialize: function(options){
if (options) {
console.log('options :');
console.warn(options);
this.model = options.model;
// this.parentEl should be measure.cid
this.measureBeatHolder = options.parentElHolder;
} else {
console.log('never really getting here');
this.model = new BeatModel;
}
this.render();
},
//We use css classes to control the color of the beat. A beat is essentially an empty div.
render: function(toggle){
var state = this.getSelectionBooleanCSS();
if (toggle) {
$('#beat'+toggle).removeClass(state);
$('#beat'+toggle).addClass(this.switchSelectionBooleanValue());
} else {
var compiledTemplate = _.template(this.representations[this.currentBeatRepresentation], {beat: this.model, beatAngle: this.beatAngle, state: state});
$(this.measureBeatHolder).append( compiledTemplate );
return this;
}
},
getSelectionBooleanCSS: function(){
if (this.model.get("selected")) {
return "ON";
} else {
return "OFF";
}
},
switchSelectionBooleanValue: function(){
if (this.model.get('selected') == true) {
this.model.set('selected', "false");
} else {
this.model.set('selected', "true");
}
return this.model.get('selected');
},
/*
This is called when a beat is clicked.
It does a number of things:
1. toggles the model's selected field.
2. re-renders the beat.
3. prints a console message.
4. tells log to send a log of the click event.
5. triggers a beatClicked event.
*/
toggle: function(){
console.log('getting to toggle function');
var selectedBool = this.model.get("selected");
this.model.set("selected", !selectedBool);
var newBool = this.model.get("selected");
this.render(this.model.cid);
dispatch.trigger('beatClicked.event');
}
});
});
For referencing:
beatModel:
//filename: models/beat.js
/*
This is the beat model.
It only knows about whether or not it
is selected.
*/
define([
'underscore',
'backbone'
], function(_, Backbone) {
var beatModel = Backbone.Model.extend({
defaults: {
selected: false,
state: 'OFF'
},
initialize: function(){
},
getStyleClass: function() {
if (this.selected) {
return 'ON';
}
else {
return 'OFF';
}
}
});
return beatModel;
});
measureModel:
//filename: models/measure.js
/*
This is the measure model.
A component has a collection of these models.
these models have a collection of beats.
*/
define([
'underscore',
'backbone',
'backbone/collections/beats'
], function(_, Backbone, beatsCollection) {
var measureModel = Backbone.Model.extend({
defaults: {
label: '0/4',
beats: beatsCollection,
numberOfBeats: 0,
divisions: 8
},
initialize: function(){
}
});
return measureModel;
});
measureView.js:
// Filename: views/measures/measuresView.js
/*
This is the MeasuresView.
This is contained in a ComponentsView.
*/
define([
'jquery',
'underscore',
'backbone',
'backbone/collections/measures',
'backbone/collections/beats',
'backbone/models/measure',
'backbone/views/beats/beatView',
'text!backbone/templates/measures/audioMeasures.html',
'text!backbone/templates/measures/linearBarMeasures.html',
'text!backbone/templates/measures/linearBarSVGMeasures.html',
'text!backbone/templates/measures/circularPieMeasures.html',
'app/dispatch',
'app/state',
'app/log'
], function($, _, Backbone, MeasureModel, BeatsCollection, MeasuresCollection, beatView, audioMeasuresTemplate, linearBarMeasuresTemplate, linearBarSVGMeasuresTemplate, circularPieMeasuresTemplate, dispatch, state, log){
return Backbone.View.extend({
// el: $('.component'),
// The different representations
representations: {
"audio": audioMeasuresTemplate,
"linear-bar": linearBarMeasuresTemplate,
"linear-bar-svg": linearBarSVGMeasuresTemplate,
"circular-pie": circularPieMeasuresTemplate
},
currentMeasureRepresentation: 'linear-bar',
//registering click events to add and remove measures.
events : {
'click .addMeasure' : 'add',
'click .delete' : 'remove'
},
initialize: function(options){
//if we're being created by a componentView, we are
//passed in options. Otherwise we create a single
//measure and add it to our collection.
if (options) {
this.measuresCollection = options.collection;
this.parent = options.parent;
this.el = options.el;
}
// else {
// this.measure = new BeatsCollection;
// for (var i = 0; i < 4; i++) {
// this.measure.add();
// }
// this.measuresCollection = new MeasuresCollection;
// this.measuresCollection.add({beats: this.measure});
// }
if (options["template-key"]) {
this.currentBeatRepresentation = options["template-key"];
}
//registering a callback for signatureChange events.
dispatch.on('signatureChange.event', this.reconfigure, this);
//Dispatch listeners
dispatch.on('measureRepresentation.event', this.changeMeasureRepresentation, this);
this.render();
//Determines the intial beat width based on the global signature. Has to be below this.render()
this.calcBeatWidth(this.parent.get('signature'));
},
changeMeasureRepresentation: function(representation) {
this.currentMeasureRepresentation = representation;
this.render();
},
render: function(){
$(this.el).html('<div class="addMeasure">+</div>');
var measureCount = 1;
//we create a BeatsView for each measure.
_.each(this.measuresCollection.models, function(measure) {
// when representation button changes, the current representation template will get updated
var compiledTemplate = _.template( this.representations[this.currentMeasureRepresentation], {measure: measure, beatHolder:"beatHolder"+measure.cid, measureCount:measureCount, measureAngle: 360.0 } );
$(this.el).find('.addMeasure').before( compiledTemplate );
console.log('measure beats: ');
console.warn(measure.get('beats').models);
_.each(measure.get('beats').models, function(beat) {
// console.warn("#beat"+beat.cid.toString());
new beatView({model:beat, parentElHolder:'#beatHolder'+measure.cid, parentCID:measure.cid, singleBeat:"#beat"+beat.cid});
}, this);
measureCount ++;
}, this);
return this;
},
/*
This is called when the user clicks on the plus to add a new measure.
It creates a new measure and adds it to the component.
It generates a string representing the id of the measure and the ids of
its beats and logs the creation.
Lastly, it triggers a stopRequest, because we can't continue playing until
all the durations get recalculated to reflect this new measure.
*/
add: function(){
console.log('add measure');
var newMeasure = new BeatsCollection;
for (var i = 0; i < this.parent.get('signature'); i++) {
newMeasure.add();
}
this.measuresCollection.add({beats: newMeasure});
//Logging
name = 'measure' + _.last(this.measuresCollection.models).cid + '.';
_.each(newMeasure.models, function(beats) {
name = name + 'beat'+ beats.cid + '.';
}, this);
log.sendLog([[3, "Added a measure: "+name]]);
//Render
this.render();
//Dispatch
dispatch.trigger('stopRequest.event', 'off');
},
/*
This is called when the user clicks on the minus to remove a measure.
*/
remove: function(ev){
if ($('#measure'+this.measuresCollection.models[0].cid).parent()) {
//removing the last measure isn't allowed.
if(this.measuresCollection.models.length == 1) {
console.log('Can\'t remove the last measure!');
return;
}
console.log('remove measure');
//we remove the measure and get its model.
var model = this.measuresCollection.get($(ev.target).parents('.measure').attr('id').replace('measure',''));
this.measuresCollection.remove(model);
//send a log event showing the removal.
log.sendLog([[3, "Removed a measure: measure"+model.cid]]);
//re-render the view.
this.render();
//trigger a stop request to stop playback.
dispatch.trigger('stopRequest.event', 'off');
dispatch.trigger('signatureChange.event', this.parent.get('signature'));
}
},
// This is triggered by signatureChange events.
reconfigure: function(signature) {
console.log('MeasureView.reconfigure(signature) : signature=' +signature);
/* if the containing component is selected, this
triggers a request event to stop the sound.
Then this destroys the beat collection and creates
a new collection with the number of beats specified
by the signature parameter.
*/
if ($(this.parent).hasClass('selected')) {
dispatch.trigger('stopRequest.event', 'off');
this.measure.reset();
for (var i = 0; i < signature; i++) {
this.measure.add();
}
//re-render the view.
this.render();
//recalculate the widths for each beat.
this.calcBeatWidth(signature);
dispatch.trigger('signatureChange.event', this.parent.get('signature'));
}
},
//This determines the width of each beat based on the
//number of beats per measure or 'signature'.
calcBeatWidth: function(signature) {
if ($(this.el).hasClass('selected')) {
var px = 100/$('.measure').css('width').replace(/[^-\d\.]/g, '');
var beatWidth = (100 - ((signature*1+1)*px))/signature;
$(this.el).children('.beat').css({
'width' : beatWidth+'%'
});
}
}
});
});
Backbone takes your events object and delegates all of those event types and selectors to the view's el. Any HTML that you want events to register on needs to be inserted inside the view's el, and the el needs to be inserted onto the page.
Typically I set up my views like this:
var myView = Backbone.View.extend({
id: 'myView',
events: {
'click li' : 'myEventCallback'
},
initialize: function() {
$('body').append(this.el); //el on the page now
this.render(); //fills up el with useful markup
},
render: function() {
//fills el with useful markup from a template
this.el.html( JST['myTemplate' ]() );
},
myEventCallback: function() {
//code for handling click events on the li's inside the el of this view
}
});
Thanks to #AlexMcp and #PaulHoenecke for their contributions.
I ended up passing the context to itself via the JQuery proxy function in the render
// add click handler to this beat
$("#beat"+this.model.cid).click($.proxy(this.toggle, this));
Complete beatView.js file:
//filename: views/beats/beatView.js
/*
This is the view for a single beat, which
is contained in a measure view.
*/
define([
'jquery',
'underscore',
'backbone',
'backbone/models/beat',
'text!backbone/templates/measures/audioMeasures.html',
'text!backbone/templates/beats/linearBarBeats.html',
'text!backbone/templates/beats/linearBarSVGBeats.html',
'text!backbone/templates/beats/circularPieBeats.html',
'app/dispatch',
'app/log'
], function($, _, Backbone, BeatModel, audioMeasuresTemplate, linearBarBeatsTemplate, linearBarSVGBeatsTemplate, circularPieBeatsTemplate, dispatch, log){
return Backbone.View.extend({
/* TODO still issues with this
el: '.beat',
registering backbone's click event to our toggle() function.
events : {
'click' : 'toggle'
},
*/
// The different representations
representations: {
"audio": audioMeasuresTemplate,
"linear-bar": linearBarBeatsTemplate,
"linear-bar-svg": linearBarSVGBeatsTemplate,
"circular-pie": circularPieBeatsTemplate
},
currentBeatRepresentation: 'linear-bar',
beatAngle: 90,
//The constructor takes options because these views are created
//by measuresView objects.
initialize: function(options){
if (options) {
// TODO: need to take in an option about currentBeatRep
// TODO: maybe need to respond to a representation changed event (change this.currentBeatRepresentation and rerender)
console.log('options :');
console.warn(options);
this.model = options.model;
// this is the html element into which this class should render its template
this.measureBeatHolder = options.parentElHolder;
this.el = options.singleBeat;
this.parent = options.parent;
} else {
console.error('should not be in here!');
this.model = new BeatModel;
}
this.render();
},
//We use css classes to control the color of the beat.
//A beat is essentially an empty div.
render: function(toggle){
// the current state of the beat (is it ON or OFF?)
var state = this.getSelectionBooleanCSS();
// if render is being called from the toggle function, we may want to do something different
if (toggle) {
$('#beat'+toggle).toggleClass("ON");
$('#beat'+toggle).toggleClass("OFF");
} else {
// this is reached during the initial rendering of the page
// compile the template for this beat (respect the current representation)
var compiledTemplate = _.template(this.representations[this.currentBeatRepresentation], {beat: this.model, beatAngle: this.beatAngle, state: state});
// append the compiled template to the measureBeatHolder
$(this.measureBeatHolder).append( compiledTemplate );
// add click handler to this beat
$("#beat"+this.model.cid).click($.proxy(this.toggle, this));
// $(this.parentEl).append(compiledTemplate);
return this;
}
},
getSelectionBooleanCSS: function(){
if (this.model.get("selected")) {
return "ON";
} else {
return "OFF";
}
},
/*
This is called when a beat is clicked.
It does a number of things:
1. toggles the model's selected field.
2. re-renders the beat.
3. prints a console message.
4. tells log to send a log of the click event.
5. triggers a beatClicked event.
*/
toggle: function(){
//switch the selected boolean value on the model
this.model.set('selected', !this.model.get('selected'));
//re-render it, passing the clicked beat to render()
this.render(this.model.cid);
// log.sendLog([[1, "beat" + this.model.cid + " toggled: "+!bool]]);
dispatch.trigger('beatClicked.event');
}
});
});
Actually... All you need to do is define the events in your view's initialize function.
Related
i want to build simple screen, where list of items in one side and selected item details -on another. when user click on one of the items - its details displayed in 'details' section.
also the selected item in the 'list' section must be decorated with 'active' class.
here is my router code:
var AppRouter = Backbone.Router.extend({
routes:{
"":"list",
"users/:id":"userDetails"
},
list:function () {
this.usersList = new UsersCollection(usersList);/* new UsersCollection()*/
var self = this;
//this.userList.fetch({
// success: function () {
this.UsersListView = new UsersListView({ model: this.usersList });
$('#sidebar').html(this.UsersListView.render().el);
// }
//})//end of fetch
},
userDetails:function (id) {
if(this.usersList){
//unselect prevously selected
if(this.user )this.user.set({'selected':false});
this.user = this.usersList.get(id);
//select current
this.user.set({'selected':true});
//empty refill the items section
this.UsersListView = new UsersListView({ model: this.usersList });
$('#sidebar').empty().html(this.UsersListView.render().el);
if (this.UserDetailsView) this.UserDetailsView.close();
this.UserDetailsView = new UserDetailsView({model:this.user});
$('#content').html(this.UserDetailsView.render().el);
}
else{
}
}
});
So far i managed to set the 'active' item class by emptying and refill the items section html.
Is there any way to observe (like in knockoutjs) the 'selected' property, so once it changes -the change will be visible in html?
code of view:
window.UserListItemView = Backbone.View.extend({
tagName:"li",
template:_.template($('#tpl-user-list-item').html()),
render:function (eventName) {
if(this.model.get('selected')){$(this.el).addClass('active');}
$(this.el).html(this.template(this.model.toJSON()));
return this;
}
});
Thanks forwards
This is what you're looking for (especially the Events#listenTo method).
So; in your view:
initialize: function() {
// listen to your model
this.listenTo(this.model, 'change:selected', this.updateClass);
},
updateClass: function() {
// something like...
this.$el.toggleClass('active');
}
I'm using Backbone to manage the state of an HTML form. The Model's role is to handle validation. The View's role is to wrap the HTML form and respond to the change or error events emitted by the model.
Backbone seems to only emit change events when the given field is actually valid. This is causing some really unexpected behavior that makes me thing that I'm doing this wrong.
Here is a summary of what I'm doing:
1. Initial load serializes the form and injects it into the model
2. When an error event is emitted, I generate error nodes next to the invalid field.
3. When a change event is emitted, I remove the error notes next to the (now valid) field.
When a page is rendered with an initially valid form, and a user invalidates a field, the message is displayed as expected; however, the model never updates the field internally. Thus when the user corrects the error, a change event is never emitted.
Example: Initially valid
When a page is rendered with an initially invalid form, things appear to be working fine... but this is only because the model's initial attributes are empty. Correcting the field makes the messages disappear, but if you change it again to an invalid state, the message never disappears.
Example: Initially invalid
What am I doing wrong? Perhaps there's another approach I should be using instead?
My Model
var Foo = Backbone.Model.extend({
validate: function(attr) {
var errors = {};
if (_.isEmpty(attr)) return;
if (attr.foo && attr.foo != 123) {
errors.foo = ['foo is not equal to 123'];
}
if (attr.bar && attr.bar != 456) {
errors.bar = ['bar is not equal to 456'];
}
return _.isEmpty(errors) ? undefined : errors;
}
});
My View
FooForm = Backbone.View.extend({
events: {
'change :input': 'onFieldChange'
},
initialize: function(options) {
this.model.on('error', this.renderErrors, this);
this.model.on('change', this.updateFields, this);
// Debugging only
this.model.on('all', function() {
console.info('[Foo all]', arguments, this.toJSON())
});
this.model.set(this.serialize());
},
onFieldChange: function(event) {
var field = event.target,
name = field.name,
value = field.value;
this.model.set(name, value);
},
renderErrors: function(model, errors) {
_.each(errors, function(messages, fieldName) {
var el = $('#' + fieldName),
alert = $('<div/>').addClass('error');
el.parent().find('.error').remove();
_.each(messages, function(message) {
alert.clone().text(message).insertAfter(el);
});
});
},
updateFields: function(model, options) {
if (!options || !options.changes) return;
_.each(_.keys(options.changes), function(fieldName) {
var el = $('#' + fieldName);
el.parent().find('.error').remove();
});
},
serialize: function() {
var raw = this.$el.find(':input').serializeArray(),
data = {},
view = this;
$.each(raw, function() {
// Get the model's field name from the form field's name
var name = this.name;
if (data[name] !== undefined) {
if (!data[name].push) {
data[name] = [data[name]];
}
data[name].push(this.value || '');
}
else {
data[name] = this.value || '';
}
});
return data;
}
});
You can't validate individual field using native Backbone validation.
In my app I use this validation plugin: https://github.com/thedersen/backbone.validation
Then in your model you add validation rules per each field (it's optional, so you don't need to add this to all models):
var NewReview = Backbone.Model.extend({
initialize: function() {
/* ... */
},
validation: {
summary: {
required: true,
minLength: 10
},
pros: {
required: true,
minLength: 10
},
cons: {
required: true,
minLength: 10
},
overall: function(value) {
var text = $(value).text().replace(/\s{2,}/g, ' ');
if (text.length == 0) text = value;
if (text.length < 20) return "Overall review is too short";
},
rating: {
range: [0.5, 5]
},
product_id: {
required: true
}
}
});
Than in views or elsewhere you can validate either entire model or individual fields:
if (this.model.validate()) { ... }
or
if (this.model.isValid("summary")) { ... }
I am creating a contact Manager using backbone.js,this is my code
$(document).ready(function() {
var Contact=Backbone.Model.extend({
defaults: {
fname : '',
lname : '',
phoneno : ''
}
});
var ContactList=Backbone.Collection.extend({
model : Contact,
localStorage: new Store("ContactList-backbone")
});
var ContactView=Backbone.View.extend({
el : $('div#contactmanager'),
events: {
'click #additems' : 'add'
},
initialize: function() {
this.render();
this.collection = new ContactList();
},
add : function() {
s1=$('#fname').val();
s2=$('#lname').val();
s3=$('#phoneno').val();
if(s1 =="" || s2=="" || s3=="")
{
alert("Enter values in Textfield");
}
else
{
$('#tlist').append("<tr><td>"+s1+"</td><td>"+s2+"</td><td>"+s3+"</td> </tr>");
cont=new Contact({fname:s1,lname:s2,phoneno:s3});
this.collection.add(cont);
cont.save();
}
},
render : function() {
$(this.el).append("<label><b>First Name</b></label><input id= 'fname' type='text' placeholder='Write ur first name'></input>");
$(this.el).append("<br><label><b>Last Name</b></label><input id= 'lname' type='text' placeholder='Write ur last name'></input>");
$(this.el).append("<br><label><b>Phone Number</b></label><input id= 'phoneno' type='text' placeholder='Write ur phone number'></input>");
$(this.el).append("<br><button id='additems'>ADD</button>");
var showdata=localStorage.getItem('ContactList-backbone',this.model);
console.log(showdata,"showdata");
}
return this;
},
});
var contactManager=new ContactView();
});
This is how I used localstorage
function S4() {
return (((1+Math.random())*0x10000)|0).toString(16).substring(1);
};
function guid() {
return (S4());
};
var Store = function(name)
{
this.name = name;
var store = localStorage.getItem(this.name);
this.data = (store && JSON.parse(store)) || {};
};
_.extend(Store.prototype,
{
save: function() {
localStorage.setItem(this.name, JSON.stringify(this.data));
},
create: function(model) {
if (!model.id) model.id = model.attributes.id = guid();
this.data[model.id] = model;
this.save();
return model;
},
Backbone.sync = function(method, model, options) {
var resp;
var store = model.localStorage || model.collection.localStorage;
switch (method) {
case "create": resp = store.create(model); break;
//I am using only create
}
if (resp) {
options.success(resp);
}
else {
options.error("Record not found");
}
};
The data is getting stored in local storage.
But I can't figure out how to show this data in my table when the page is reloded.
For eg: Iwant to show first name,lname and phone no in table ;
I am new to backbone so plz do help me
Basically you will want to bind the add event in your collection which gets will get called for each item that is being added to the collection and then in the function your binding it to add the code to add the rows to your table. Also you will want to remove the code that is in your current add method that adds the row since it will now be generated when the item gets added to your collection.
Using your code as a base something along the lines of
var ContactView=Backbone.View.extend({
el : $('div#contactmanager'),
events: {
'click #additems' : 'add'
},
initialize: function() {
this.render();
this.collection = new ContactList();
this.collection.bind('add', this.addContact, this);
},
addContact: function(contact) {
//this will get called when reading from local storage as well as when you just add a
//model to the collection
$('#table').append($('<tr><td>' + contect.get('name') + </td></tr>'));
}
Another point being that you have already have underscore.js on your page (since its a requirement for backbone.js) you may want to consider moving your code to generate html to a underscore.js template.
http://documentcloud.github.com/underscore/#template
since you're only using create, you're never going to hit read. Replace your switch statement with by adding a read method
switch (method)
{
case "read":
resp = model.id != undefined ? store.find(model) : store.findAll();
break;
case "create":
resp = store.create(model);
break;
}
I've seen a few different ways to get the next or previous model from a collection, but was wondering if anyone could offer some advice on the way I decided to implement it. My collection is ordered, but the id that i'm sorting on is not guaranteed to be sequential. It's only guaranteed to be unique. Assume that smaller ids are "older" entries to the collection and larger ids are "newer".
MyCollection = Backbone.Collection.extend({
model: MyModel,
initialize:function (){
this.getElement = this._getElement(0);
},
comparator: function(model) {
return model.get("id");
},
_getElement: function (index){
var self = this;
return function (what){
if (what === "next"){
if (index+1 >= self.length) return null;
return self.at(++index);
}
if (what === "prev"){
if (index-1 < 0 ) return null;
return self.at(--index);
}
// what doesn't equal anything useful
return null;
};
}
});
When using getElement, I do things like getElement("next") and getElement("prev") to ask for the next or previous model in my collection. What is returned from getElement is the actual model, not the index. I know about collection.indexOf, but I wanted a way to loop through a collection without first having a model to start from. Is this implementation harder than it needs to be?
I would do something like this. Keep in mind that there isn't any error handling currently so if you are currently at the first model in the collection and try to get the previous you will probably get an error.
MyCollection = Backbone.Collection.extend({
model: MyModel,
initialize:function (){
this.bindAll(this);
this.setElement(this.at(0));
},
comparator: function(model) {
return model.get("id");
},
getElement: function() {
return this.currentElement;
},
setElement: function(model) {
this.currentElement = model;
},
next: function (){
this.setElement(this.at(this.indexOf(this.getElement()) + 1));
return this;
},
prev: function() {
this.setElement(this.at(this.indexOf(this.getElement()) - 1));
return this;
}
});
To progress to the next model collection.next(). To progress to the next model and return it var m = collection.next().getElement();
To explain a little better how next/prev works.
// The current model
this.getElement();
// Index of the current model in the collection
this.indexOf(this.getElement())
// Get the model either one before or one after where the current model is in the collection
this.at(this.indexOf(this.getElement()) + 1)
// Set the new model as the current model
this.setElement(this.at(this.indexOf(this.getElement()) + 1));
I've done this slightly differently in that I'm adding the methods to the model rather than to the collection. That way, I can grab any model, and get the next one in the sequence.
next: function () {
if (this.collection) {
return this.collection.at(this.collection.indexOf(this) + 1);
}
},
prev: function () {
if (this.collection) {
return this.collection.at(this.collection.indexOf(this) - 1);
}
},
Bumping this old thread with a somewhat more generic solution:
Stuff to add to Collection.prototype
current: null,
initialize: function(){
this.setCurrent(0);
// whatever else you want to do here...
},
setCurrent: function(index){
// ensure the requested index exists
if ( index > -1 && index < this.size() )
this.current = this.at(index);
else
// handle error...
},
// unnecessary, but if you want sugar...
prev: function() {
this.setCurrent(this.at(this.current) -1);
},
next: function() {
this.setCurrent(this.at(this.current) +1);
}
you can then use the sugar methods to get the prev/next model like so...
collection.prev();
collection.next();
My Backbone SelectableCollection class:
Backbone.Collection.extend({
selectNext: function () {
if(this.cursor < this.length - 1) {
this.cursor++;
this.selected = this.at(this.cursor);
this.trigger('selected', this.selected);
}
},
selectPrevious: function () {
if(this.cursor > 0) {
this.cursor--;
this.selected = this.at(this.cursor);
this.trigger('selected', this.selected);
}
},
selectById: function (id) {
this.selected = this.get(id);
this.cursor = this.indexOf(this.selected);
this.trigger('selected', this.selected);
},
unselect: function () {
this.cursor = null;
this.selected = null;
this.trigger('selected', null);
}
});
I am developing a checkbox grid list with pagination using the EXTJS grid. I need to remember the selected record when the page navigation is performed.
Details:
1) Go to page:1 and selected rows 1,2 and 3.
2) Now navigate to page:2
3) Come back to page:1
4) The rows 1,2 and 3 which are already selected should be shown as selected
Is there is any api in grid which handles this kind of function?
Thanks in advance.
Thanks for your responses. I have achieved my design by implementind a plugin for grid. The plugin looks as,
Ext.namespace('Ext.ux.plugins');
Ext.ux.plugins.CheckBoxMemory = Ext.extend(Object,
{
constructor: function(config)
{
if (!config)
config = {};
this.prefix = 'id_';
this.items = {};
this.idProperty = config.idProperty || 'id';
},
init: function(grid)
{
this.view = grid.getView()
this.store = grid.getStore();
this.sm = grid.getSelectionModel();
this.sm.on('rowselect', this.onSelect, this);
this.sm.on('rowdeselect', this.onDeselect, this);
this.store.on('clear', this.onClear, this);
this.view.on('refresh', this.restoreState, this);
},
onSelect: function(sm, idx, rec)
{
this.items[this.getId(rec)] = true;
},
onDeselect: function(sm, idx, rec)
{
delete this.items[this.getId(rec)];
},
restoreState: function()
{
var i = 0;
var sel = [];
this.store.each(function(rec)
{
var id = this.getId(rec);
if (this.items[id] === true)
sel.push(i);
++i;
}, this);
if (sel.length > 0)
this.sm.selectRows(sel);
},
onClear: function()
{
var sel = [];
this.items = {};
},
getId: function(rec)
{
return rec.get(this.idProperty);
}
});
This plugin was called from gird as,
Ext.grid.Gridpanel({
store: 'someStore',
plugins: [new Ext.ux.plugins.CheckBoxMemory({idProperty: "recordID"})]
});
Hope this helps some one.
I don't think there is. You;d need to store IDs of selected records in some separate store/array and use it to re-apply selections when page is changed.
You could put a MixedCollection Object at the global scope to keep track of these records. This will allow you to store global settings of different object types.