Ext.define('DigitalPaper.controller.Documents', {
extend: 'Ext.app.Controller',
views: ['Documents'],
stores: ['Documents'],
models: ['Documents'],
init: function() {
console.log('[OK] Init Controller: Documents');
}
});
What's the function to get Model or View of this controller?
I've tried
Ext.getModel('Documents');
this.getModel('Documents');
this.getModel();
this.getDocumentsModel();
Any suggests?
Solution Found:
In the Controller it is possible to use the reference to the View:
refs: [{
ref: 'viewDocuments', // will be create the method this.getViewDocuments();
selector: 'Documents'
}],
So you can get the View with this:
this.getViewDocuments();
Ext controllers are pretty weird, in that there is a single instance of a given controller, no matter how many related view instances you might have. In most MVC or MVP systems there is one controller instance per view instance.
If you plan to use multiple view instances, then you should not keep references to those views in the controller.
You might want to look into Deft's MVC extension for ExtJs that has one controller instance per view instance (plus dependency injection):
http://deftjs.org/
Anyways, controller.getView() returns a reference to the view CLASS, not an object instance. Same with getModel(). getStore() DOES return a store instance.
In your controller, you can do something like this:
this.viewInstance = this.getDocumentsView().create();
I would also recommend naming your model in the singular. It is not a Documents. It is a Document.
This should work:
Ext.define('DigitalPaper.controller.Documents', {
extend: 'Ext.app.Controller',
views: ['Documents'],
stores: ['Documents'],
models: ['Documents'],
init: function() {
console.log('[OK] Init Controller: Documents');
// get references to view and model classes which can be used to create new instances
console.log('View', this.getDocumentsView());
console.log('Model', this.getDocumentsModel());
// reference the Documents store
console.log('Store', this.getDocumentsStore());
}
});
These methods are created by a method in the Ext controller that creates the getters.
http://docs.sencha.com/ext-js/4-0/source/Controller.html#Ext-app-Controller
Here is what that method looks like:
createGetters: function(type, refs) {
type = Ext.String.capitalize(type);
Ext.Array.each(refs, function(ref) {
var fn = 'get',
parts = ref.split('.');
// Handle namespaced class names. E.g. feed.Add becomes getFeedAddView etc.
Ext.Array.each(parts, function(part) {
fn += Ext.String.capitalize(part);
});
fn += type;
if (!this[fn]) {
this[fn] = Ext.Function.pass(this['get' + type], [ref], this);
}
// Execute it right away
this[fn](ref);
},
this);
},
getModel() and getView() do not return the model / views of the controller - they return instances of these in the app (and if none exist, they will be instanced).
You can simply use this to get the view / model names:
this.views[0]
I'm not sure where you are using your gets ( ie, this.getModel('Details') ), but these should correctly return an instance of the model (the constructor is the only place you might have issues referring to these).
Related
So here is my scenario:
I have a Backbone Collection full of Models. For performance reasons, however, these are not "full" Models. My "full" Models are quite large (imagine each "full" Model has a sub-collection of equally large objects), so when I fetch the Collection from the server I return an array of "partial" Models whose properties are a subset of the "full" model (for example I return only the length of the sub-collection instead of the full sub-collection), just enough to display the Models in a list view to the user.
Now when the user selects an item from the list, I fetch the "full" Model from the server and show a details view of that Model. The problem I have is that now I have two versions of the same Model, one "partial" in the Collection and one "full", and manually having to keep them in sync isn't the right way to do things.
What I'm wondering is if there is an existing pattern (in Backbone or Marionette) for "populating" a "partial" Model into a "full" Model while keeping all of the same references, and for "depopulating" the same Model from a "full" Model into a "partial" Model when we no longer need all of the extra data (i.e. the user navigates to another item in the list).
I have full control over both the front-end and the back-end of my application, and can make changes accordingly if a pattern requires I change what the server returns.
You are representing a single domain object (albeit in two different forms), so you should use a single Model instance to cover both cases.
One fairly clean pattern:
var MyModel = Backbone.Model.extend({
// ... existing code...
inflate: function() {
return $.ajax({
// parameters to fetch the full version
}).then(function(data) {
// process the response - something like this:
this.set({
a: data.a,
b: data.b
}, { silent: true })
this.trigger('inflate')
})
},
deflate: function() {
this.unset('a', { silent: true });
this.unset('b', { silent: true });
// any other cleanup to prevent leaking the large attributes
this.trigger('deflate')
}
})
This pattern uses the custom inflate and deflate events in preference to firing change, because it's semantically more accurate.
You could, of course, DRY up the code by maintaining an array of attribute names that should be in/deflated.
Just like your collection has a URL to the "partial" models, your models should have a URL to the full versions:
var Library = Backbone.Collection.extend({
model: Book,
url: "/books"
});
var Book = Backbone.Model.extend({
url: function () {
return "/books/" + this.get("id");
}
});
When you click your item view use that same model, call a fetch(), and pass it into the detail view.
var BookView = Backbone.View.extend({
tagName: "li",
events: {
"click .details": "openBook"
},
initialize: function() {
// ...
},
openBook: function () {
this.model.fetch();
var bookDetailView = new BookDetailView({ model: this.model });
// Or create the view after successful fetch...
}
// ...
});
var BookDetailView = Backbone.View.extend({});
You won't have two versions of the same model. The model in the collection view will now have all the attributes, but it will only display what is in the template.
As far as "depopulating" it doesn't seem necessary. If the item is clicked again you could even check if the "full" model data is available and lose the extra fetch. If you really want to drop the data, then go ahead and create a method on the model to unset the attributes.
Say a user is going down a page and checking off and selecting items.
I have a Backbone model object, and each time the user selects something I want to update the object.
I have this in a separate JavaScript file that I source in my HTML:
var app = {};
var newLineup = null;
var team = document.getElementsByName('team');
app.Lineup = Backbone.Model.extend({
defaults: {
team: team,
completed: false
},
idAttribute: "ID",
initialize: function () {
console.log('Book has been intialized');
this.on("invalid", function (model, error) {
console.log("Houston, we have a problem: " + error)
});
},
constructor: function (attributes, options) {
console.log('document',document);
console.log('Book\'s constructor had been called');
Backbone.Model.apply(this, arguments);
},
validate: function (attr) {
if (attr.ID <= 0) {
return "Invalid value for ID supplied."
}
},
urlRoot: 'http://localhost:3000/api/lineups'
});
function createNewLineupInDatabase(){
newLineup = new app.Lineup({team: team, completed: false});
newLineup.save({}, {
success: function (model, respose, options) {
},
error: function (model, xhr, options) {
}
});
}
When the user first accesses the page, I will create a new lineup object by calling the above function. But how do I update that object as the user interacts with the page? Is there a better way to do this other than putting the Backbone model object at the top of my JavaScript file?
The Backbone pattern was designed to answer your question. As other respondents said, wire up a View, which takes your model as a parameter and lets you bind DOM events to the model.
That said, you don't have to use the rest of the framework. I guess you can use all the functionality Backbone provides models by handling the model yourself.
You need to worry about a couple of things.
Give you model a little encapsulation.
Set up a listener (or listeners) for your checkbox items.
Scope the model to your app
Backbone provides neat encapsulation for your model inside a View, but if you can live with it, just use your app variable which is within scope of the JavaScript file you posted.
When you're ready to instantiate your model, make it a property of app:
app.newLineup = new app.Lineup({team: team, completed: false});
It may look weird to have the instance and the constructor in the same object, but there aren't other options until you pull out the rest of Backbone.
The listener
So you have N number of checkboxes you care about. Say you give them a class, say, .options. Your listener will look like
$( ".options" ).change(function() {
if(this.checked) {
//Do stuff with your model
//You can access it from app.newLineup
} else {
}
});
Voila! Now your page is ready to talk to your model.
If there is frontend ui / any user interaction within your code it is extremely useful to create a backbone view which makes use of an events object where you can set up your event handler.
You can also link a view to a model to allow your model / your object to be updated without scope issues.
I am using backbone.js in my app. My model named MyModel is
Backbone.Model.extend({
urlRoot: 'home'
});
Need to fetch model with url "home/abc/xyz" where "abc" and "xyz" are dynamic in my view. I did the following
var model = new MyModel({id:'abc/xyz'});
model.fetch();
but its not working.
It goes to url "home/abc?xyz".
How should i solve this issue?
Here is the url function of Backbone.Model which is responsible for such kind of behavior in Backbone:
url: function() {
var base =
_.result(this, 'urlRoot') ||
_.result(this.collection, 'url') ||
urlError();
if (this.isNew()) return base;
return base.replace(/([^\/])$/, '$1/') + encodeURIComponent(this.id);
}
As you can see encodeURIComponent(this.id) will encode your id, so you can't pass and '/' -> '%2F'.
You always can rewrite this function, but I guess it's not the best idea, cause it can break another things.
I can suggest you another approach:
Just define urlRoot of your model as function and there do your job:
var yourModel = Backbone.Model.extend({
defaultUrl: 'home',
urlRoot: function () {
return defaultUrl + this.get('yourUrlAttribute');
}
});
Just pass the yourUrlAttribute as model attribute when creating it and fetch the model.
Having in mind this approach and that Backbone.Model will append encoded 'id' as the last part of URL (when you fetching model) you can get what you want.
In my controller I want to use certain variables in several places.
For example I got a form with few fields (combo / textfield) and I want to use a link to them in a various places of my controller code. How can / should I declare such variable?
Usually I use:
refs: [
{
ref: 'myCombo',
selector: 'myPanel combo[name=myCombo]'
},
{
ref: 'myTextfield',
selector: 'myPanel textfield[name=myTextfield]'
}
]
But is it ok to use getMyCombo() / getMyTextfield() every time I have to work with this fields in my controller?
The "refs" feature of the controller is really just generating getter functions for you by using Ext.ComponentQuery with the provided CSS selector. The way you're using them is one way you can make use of the system, though you can also use refs to instantiate (for example) views for the controller using their configured alias or xtype. In your example, you're saving yourself the hassle of re-writing some long-ish ComponentQuery calls.
The 'autoCreate' option, although not documented, is great for this type of thing if for example you wanted to always instantiate a new instance of a certain object every time the controller is activated, you could do so in the init() function.
The answer posted here demonstrates using refs to create new instances and further explains the functionality of autoCreate and forceCreate options.
If you want to use an object or some variable throughout your controller, just set a property on the controller, most suitably in the init method...
Ext.define('App.controller.Messaging', {
/** Include models, stores, views, etc... */
refs: [{
ref: 'messageBox', // creates magic method "getMessageBox"
xtype: 'my-messagebox', // in the class file: alias: 'widget.my-messagebox'
selector: '', // could be itemId, name, etc. Same rules as a CSS selector
autoCreate: true // automatically create when "getMessageBox()" is called
}],
/** I always initialize controllers as-needed, passing the application object */
init: function(app){
var me = this;
// Initialize whatever you need, maybe set up some controller properties
this.eventBus = app.getEventBus();
this.user = app.getActiveUser();
// I prevent listeners from being established twice like this..
if(this.inited === true)
return;
this.inited = true; // nothing past this line will be executed twice
// Observe view events
this.control();
// Listen for application events
app.on({
'getMessages': {fn: me.showMessageBox, scope: me}
});
// Specific controller events
this.on({
'someEvent': {fn: me.someFunction, scope: me}
});
},
// A function using controller properties defined in the init() method
someFunction = function(){
var me = this; // controller
// Lets show the active user
console.warn("Active User", me.user);
// And then fire an event, passing this controller
me.eventBus.fireEvent('somethingHappened', me);
},
// Invoked on the application event "getMessages"
showMessageBox: function(sessionId){
var me = this; // controller
/** ... Load the messages for the provided sessionId ... */
// Then create an instance of the message box widget as property on the controller
me.messageBox = me.getMessageBox({
/** pass config to the view here if needed */
});
}
});
I try to develop an app with MVC architecture. I've the following Controller code:
Ext.define('PM.controller.Projects', {
extend: 'Ext.app.Controller',
models: ['Project'],
stores: ['Projects'],
views: [
'projects.Tree',
'Toolbar',
],
init: function(config) {
var tree = this.getProjectsTreeView();
var rootNode = tree.getRootNode();
console.log(rootNode);
this.callParent(config);
}
});
And this view code:
Ext.define('PM.view.projects.Tree', {
extend: 'Ext.tree.Panel',
xtype: 'projectsTree',
title: 'Projects',
hideHeaders: true,
root: {
text: "Projekte"
}
});
It try to get the root node from my tree view in the controller but I get the error that getRootNode() is not a valid function in my controller. Can anybody tell me why I get this error? My target is to add new children to this root node from an ajax request.
Thanks
The methods Ext generates for each string in the views array return constructors that can be used to create the respective views. That seems bizarre, but that's how it is.
If you want to access the actual view component, you'll need to create a ref for it. Your init method should not assume that the view exists yet. It's very likely that it won't since the controller's init method is called before the application's launch method which is probably where all the views are getting added to the page.
You want to put your logic in the controller's onLaunch template method which is called after the application has been launched and your view has been added.
Ext.define('PM.controller.Projects', {
extend: 'Ext.app.Controller',
refs: [{
ref: 'projectsTreeView',
selector: 'projectsTree'
}],
init: function() {
// It's safe to add selectors for views that don't exist yet.
this.control(/*...*/)
},
onLaunch: function(config) {
var tree = this.getProjectsTreeView();
var rootNode = tree.getRootNode();
console.log(rootNode);
}
});
If this doesn't work, that means you aren't actually adding your view anywhere. One place you could add it is in the application's launch method. Something has to add the treeview.
Ext.application({
// ...
views: ['projects.Tree']
launch: function() {
Ext.create('Ext.container.Viewport', {
layout: 'fit',
items: [new this.getProjectsTreeView()]
});
}
});
So the chronology of events is this:
Application#constructor
Controller#constructor
Controller#init (can't assume the view exists)
Application#onBeforeLaunch
Application#launch (view is now added)
Controller#onLaunch (do something with the view that is now available)
Also, your view alias may need to be 'widget.projectsTree' not just 'projectsTree'.