I have little experience with ExtJS3 and now starting with version 4.
In my controller, I have this:
init: function ()
{
this.control({
"userlist":
{
selectionchange: function (view, selected, opts)
{
//get to grid??
}
}
});
}
How can I access the grid that this event happened on, without using id's?
I want to enable/disable buttons on the grid toolbar (tbar) if there are items selected, but I don't want to give anything id's (not the bar, not the individual buttons)
EDIT: the solution was to use the refs property in the controller:
refs:
[
{
ref: "list",
selector: "userlist"
}
],
selectionchange: this.activateTbButtons
activateTbButtons: function (selected, opts)
{
if (selected.selected.length == 1)
{
var tb = this.getList().query("toolbar");
}
}
Just found out that you can use the attribute view, and views under Ext.selection.Model.
This can be useful in cases when you let's say open multiple instances of your objects.
So, to access the grid in your example:
selectionchange: function (view, selected, opts) {
//get to grid??
var grid = view.view.ownerCt;
}
Having the same problem and found the previous answers missing some points. In short, I recommend:
selectionchange: function (selModel, selected, eOpts) {
var grid = selModel.view.ownerCt;
}
This was already proposed by Adezj although it referred to the selectionchange event that has the view as the first argument, and is not applicable to ExtJS 4.0.7+. (Don't think that selectionchange ever had the view as an argument?)
Note that this might not be officially supported by ExtJS since the view property of the selection model is not mentioned in the API docs at all.
Another approach is to use Ext.ComponentQuery.query(...) or defining refs in the controller, as proposed by Arun V, which is basically just a handy wrapper for Ext.ComponentQuery.query(). This works fine if you only have individual instances of the grid class but you need to take care in case you have multiple instances of the same grid class. Simply doing Ext.ComponentQuery.query('xtype-of-your-grid') will return all instances of your grid and you will have lots of fun finding out in which one the user has selected something.
So, in general, I would highly recommend to always work your way up from the component or object that fired the event to be sure you are in the right branch of the component hierarchy unless you are sure you will never have more than one instance of that class you write a controller for.
EDIT
I took a look at the docs for the selectionChange event:
selectionchange( Ext.selection.Model this, Ext.data.Model[] selected, Object eOpts )
The view is not being passed in to the selectionchange handler. An easy way to handle this is to either use Ext.getCmp() or use refs as seen in the docs for Ext.app.Controller:
http://docs.sencha.com/ext-js/4-0/#!/api/Ext.app.Controller
//get grid
var grid = selectionModel.view.ownerCt.ownerCt;
Related
First off - I am a MarionetteJS noob.
I am having trouble making an ItemView display a loading message or throbber while it is being fetched. This is especially problematic when this ItemView is being displayed from a deep link in Backbone's history (i.e. the ItemView is the first page of the app being displayed since the user linked directly to it). I want to indicate that the page is loading (fetching), preferably with a simple view, and then show the real templated view with the fetched model.
I have seen other answers on SO like Marionette.async, (which has been deprecated) and changing the template during ItemView.initalize().
Anybody (Derrick?) got any suggestions or best practices here?
UPDATE:
I am getting the model from the collection using collection.get(id), not using model.fetch() directly.
After thinking about this, the real question is where should this be implemented:
I could change my controller to see if the model exists in the collection (and if the collection is loaded) and decide which view to show accordingly. this seems like a lot of boilerplate everywhere since this could happen with any ItemView and any controller.
I could change my ItemView initialize to test for existence of the model (and a loaded collection), but same comment here: every ItemView could have this problem.
UPDATE 2:
This is what I ended up with, in case anybody else want this solution:
app.ModelLayout = Backbone.Marionette.Layout.extend({
constructor: function(){
var args = Array.prototype.slice.apply(arguments);
Backbone.Marionette.Layout.prototype.constructor.apply(this, args);
// we want to know when the collection is loaded or changed significantly
this.listenTo(this.collection, "reset sync", this.resetHandler);
},
resetHandler: function () {
// whenever the collection is reset/sync'ed, we try to render the selected model
if (this.collection.length) {
// when there is no model, check to see if the collection is loaded and then try to get the
// specified id to render into this view
this.model = this.collection.get(this.id);
}
this.render();
},
getTemplate: function(){
// getTemplate will be called during render() processing.
// return a template based on state of collection, and state of model
if (this.model){
// normal case: we have a valid model, return the normal template
return this.template;
} else if (this.collection && this.collection.isSyncing) {
// collection is still syncing, tell the user that it is Loading
return this.loadingView;
} else {
// we're not syncing and we don't have a model, therefore, not found
return this.emptyView;
}
}
});
And here is how to use it:
// display a single model on a page
app.Access.Layout.CardLayout = app.ModelLayout.extend({
regions: {
detailsRegion:"#detailsRegion",
eventsRegion:"#eventsRegion"
},
template:"CardLayout", // this is the normal template with a loaded model
loadingView:"LoadingView", // this is a template to show while loading the collection
emptyView:"PageNotFoundView", // this is a template to show when the model is not found
onRender : function() {
this.detailsRegion.show( blah );
this.eventsRegion.show( blah );
}
});
thanks!
For the ItemView
I think you can add a spinner in your initialize function, I really like spin.js http://fgnass.github.io/spin.js/ because its pretty easy and simple to use, and you can hide the spinner in the onRender function of the Itemview
For The CollectionView
in the CollectionView you could handle it like this....
Take a look at the solution that Derick posted..
https://github.com/marionettejs/backbone.marionette/wiki/Displaying-A-%22loading-...%22-Message-For-A-Collection-Or-Composite-View
I'd suggest using jQuery deferreds:
Start fetching your data, and store the return value (which is a jQuery promise)
Instanciate your content view
Show your loading view
When the promise is done, show the view containing the content
I've talked about implementing this technique on my blog:
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/
The issue with the solution linked by Rayweb_on, is that your loading view will be displayed any time your collection is empty (i.e. not just when it's being fetched). Besides, the ItemView doesn't have an emptyView attribute, so it won't be applicable to your case anyway.
Update:
Based on your updated question, you should still be able to apply the concept by dynamically specifying which template to use: https://github.com/marionettejs/backbone.marionette/blob/master/docs/marionette.view.md#change-which-template-is-rendered-for-a-view
Then, when/if the data has been fetched successfully, trigger a rerender in the view.
I want bind the data to label control at controller level. i have a main Tab-Panel view within view have two more view like example1 view and specification view. in specification tab view have a label is id: lblSpecification, for this label i am going to bind data at controller level as shown below. But it is not working.
controller code is here:
config: {
refs: {
specificationPage: "specification",
specificationLabel: "#lblSpecification"
},
control: {
specificationPage: {
initialize: "SpecificationInitialize"
}
},
SpecificatiTabInitialize: function () {
this.getSpecificationLabel().setHtml("Welcome");
}
}
I have created another similar project, where I am not using tab panel, I have followed similar steps as code mentioned above, its working fine, please can I know its the problem due to tabpanel or is their any alternate way to achieve this?
Try to setHtml when label is initialized but not a panel.
I've been learning a lot about backbone, backbone-relational and building web apps from reading through stackexchange - so first a thank you to the community.
Now, I am stuck at trying to understand this current issue involving nested models and sub views in what seems to me to be is a pretty common usecase.
I am trying to extend this tutorial to learn about backbone-relational, views/subviews and event handling by keepin track of "CheckIns" for each wine.
I have extended the server side to return appropriate JSON and backbone-relational model for checkIns like so:
window.CheckInModel = Backbone.RelationalModel.extend({
defaults:{
"_id":null,
"Did":"true",
"dateOf":"",
}
});
window.CheckInCollection = Backbone.Collection.extend({
model : CheckInModel
});
And the Wine Model like so:
relations: [{
type: Backbone.HasMany,
key:'CheckIn',
relatedModel: 'CheckInModel',
collectionType: CheckInCollection,
}]
I've created a CheckInListView and CheckInItemView (same as WineListView and WineListItemView) and use the WineView Render function to render the CheckIns like so:
render:function (eventName) {
console.log("wine View Render");
$(this.el).html(this.template(this.model.toJSON()));
this.myCheckInListView = new CheckInListView({model: this.model.attributes.CheckIn});
this.$el.append(this.myCheckInListView.render().el);
return this;
},
I've also created a new function within wineview that creates a checkin and associated with the given event:
logcheckin: function (event){
var todate = new Date();
newCheckIn = new CheckInModel ({'Did':"true", 'dateOf': todate.toISOString()});
console.log ("Logcheckin - About to push newCheckIn onto Model.");
this.model.attributes.CheckIn.push (newCheckIn);
console.log ("Just pushed newCheckIn onto Model.");
this.saveWine();
}
Ok - if you haven't TL/DRed yet - This all seems to work fine from a UI perspective -ie. everything renders correctly and saves to the Db.
But I notice in the console that when I push a new CheckIn (between the console.logs above) the CheckInListView's Add binding gets called multiple times for wach button press - Which makes me think something is wrong with how I'm doing views or that I am not understanding something fundamental about event propagation.
Why is this happening ? Is it expected behavior ? Am I approaching what I am trying to do correctly ?
Thanks for reading if not your help.
==
Here are the relevant parts of the CheckinListView and CheckInList Item views that are bound to the add (and other) events.
window.CheckInListView = Backbone.View.extend({
initialize:function () {
this.model.bind("reset", this.render, this);
this.model.bind("change", this.render, this);
var self = this;
this.model.bind("add", function (CheckIn) {
console.log ("Adding to CheckInListView - a CheckIn List Item", CheckIn);
self.$el.append(new CheckInListItemView({model:CheckIn}).render().el);
});
},
close:function () {
$(this.el).unbind();
$(this.el).remove();
}
});
window.CheckInListItemView = Backbone.View.extend({
initialize:function () {
this.model.bind("change", this.render, this);
this.model.bind("destroy", this.close, this);
},
});
==============================================
The comment about event binding and closing views were the right hints for debugging this.
1) I was not closing and unbinding the nested views properly which left some ghost event consumers even though there was nothing in the DOM
2) You only need to bind events if we want to do something only in the subview.
For Example - If I have checkbox in a subview I can bind a subview change event in the main view and handle the event there since the mainview has model there anyway. I don't know if this is the "right" way but it works for what I need to do. (mm.. spaghetti code tastes so good)
3) Struggling with this helped me think through the UX some more and helped me simplify UI.
4) I was trying to "save" calls to the server by nesting all the data into on JSON call. And if I were to re-do this - I would not nest the data at all but handle it on the back end by associating the wine ID with checkIn ID and then having a separate collection that gets populated with the collection once a task is selected - I thought this would not be a the preferred way but it seems to be the way that a lot of people.
Still welcome any thoughts on the "right" way questions above or if anyone can point to a tutorial that goes beyond the "simple backbone app"
I'm not sure about everything that's happening, but, I've run into the problem of events firing multiple times before. If you're rendering multiple models using the same view, there's a chance that they're all being bound to the same event.
Perhaps this answer might apply:
Cleaning views with backbone.js?
If not, you should respond to Edward M Smith's comment and show how your events are being bound.
I have a button in a Sencha Touch 2 project.
The button gets destroyed with the view after being pressed and is rebuild after another button gets pressed.
But the button does not get the listener again.
the listener is build in the controller of the view.
Ext.application({
name: 'App',
controllers: ['Main','Home'],
views: ['Main','Home'],
launch: function () {Ext.Viewport.add({xtype:'mainview'});}
});
the controller
Ext.define('App.controller.Home', {extend: 'Ext.app.Controller',
config: {
refs: {homeView: '#homeview',backBtn: '#btn_test1'},
control: {
backBtn: {
tap: function(backBtn){
console.log('[Controller][Home] btn monatsrate - - tap');
Ext.Viewport.add({xtype: 'mainview'});
Ext.Viewport.setActiveItem(1);
}
},
homeView: {
deactivate: function (homeView){
console.log('[Controller][Home] autodestroy homeview');
//homeView.destroy();
Ext.Viewport.remove(homeView);
}
}
}
},
});
And the view
Ext.define("App.view.Main", {
extend:"Ext.Container",
xtype:"mainview",
config:{
id:'mainview',
items:[
{
xtype:'button',
id:'btn_test2',
text: 'test2'
}
]
},
});
Any idea how to allow the button to get the listener back?
This is because the "ref" in your controller is using the id of the button to create the ref. Instead, use a different selector for your button. For example you could give your button a "name" property and give it a value of "testbutton". Then your ref would be like
refs: {homeView: '#homeview',backBtn: 'button[name=testbutton]'},
I struggled with this same problem for buttons and list items that were created/destroyed many times throughout the application's flow. Since then I've read a few times that, in general, the Sencha Touch team recommends not using the id as the selector unless you have a specific reason to. The "name" method above works very well for me. You could use lots of other css-style selectors as well (you'd have to read up on that separately).
As mentioned in a previous comment, I would accept some answers to increase the probability of getting an answer to your questions in the future. I'm just answering this one because I beat my head against the wall on this issue for 4 hours.
Sencha's examples recommend using action config on buttons, like 'cancel', 'goHome', 'createPost', etc.. which kinda makes sense.
All refs are then in the form of: myContainer button[action=myAction]
I believe your issue is exactly the id parameter. If you ever add any id you should make sure it is unique, thus adding an id to a config of your custom view will result in no way to create more then one instance of it! I may not be a 100% right(might be inside a container but i believe it will cause issues anyway) but why would you want an id that much? Besides, you can simply reference your view by xtype:
refs: {homeView: 'homeview',backBtn: 'btn_test1'},
regards,
There is an Editor grid panel.
I am trying to add a listener to the cell click event. I have tried to do it in different ways:
gridPanel.addListener('cellclick',myfunction);
inside the grid:
listeners: {
cellclick: myfunction
}
I have tried to do it in the selectionModel object.
None of them worked.
Where can be the problem?
If you can't add the listener as a config parameter, it's probably because you're having problems with the callback function not being in scope, similar to this:
> var obj = { greetMe: this.sayHi, sayHi: function(){ console.log('hi'); }};
undefined
> obj.greetMe();
TypeError: Property 'greetMe' of object #<Object> is not a function
> obj.greetMe
undefined
In the first line, this was still referring to the global (read: window) object at the time the object was defined. So this.sayHi === window.sayHi === undefined. The same thing happens when you define Ext config objects. Try defining the callback inline:
> var obj = { greetMe: function(){ console.log('hi'); }};
undefined
> obj.greetMe()
hi
Other options:
Define the function in a wider scope, outside of your class
Attach the listener within the constructor or within initComponent, by which time your class methods should be instantiated
Attach the listener sometime later
You have to use cellmousedown of grid panel or cell selectionchange of selection model.
addListener worked. I had some syntax error.
Still adding the listener as a parameter doesn't work
For row selection I put it on the selection model and for double clicks on the grid directly. This works well but I must warn you if you don't prevent the default event you will get horrible memory consumption.
Ext.onReady(function(){
requestGrid.getSelectionModel().on('rowselect', handleRowSelect);
requestGrid.on('rowdblclick', handleDoubleClick);
}
It would help if I read :) My solution is for row selection not cell selection but it might very well work.