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 */
});
}
});
Related
I have three different areas in my application that can modify hours. They all use the same grid, store, and model (but different instances) and call the same backend controller. I am trying to implement a way to add a parameter to the AJAX calls, so the backend knows which area of the application that the call came from through the use of a parameter to AJAX calls.
I have attempted the following:
(1) Overriding request function in Ext.data.Connection
Ext.data.Connection.override({
request: function(options){
var me = this;
if(!options.params)
options.params = {};
options.params.location = 'location 1';
return me.callOverridden(arguments);
}});
Result: I couldn't figure out a way to find the module that made the call.
(2) Adding the following to the controllers init of the controllers
Ext.Ajax.on('beforerequest', function(conn, options) {
if(!options.params)
options.params = {};
options.params.location = "location 1";
});
Result: Every call was sending the same location even if it was a different area
Considering that you are using the same store but different instances, the easiest way is to use the store methods of synchronization only. You would have to define the proxy on the store, not the model, then you can easily add a special extraParam on every instance:
Ext.define('MyStore', {
extend: 'Ext.data.Store',
proxy: {
type: 'ajax'
url: 'test.json'
}
});
var instance1 = Ext.create('MyStore');
var instance2 = Ext.create('MyStore');
var instance3 = Ext.create('MyStore');
instance1.getProxy().setExtraParam('source', 'instance1');
instance2.getProxy().setExtraParam('source', 'instance2');
instance3.getProxy().setExtraParam('source', 'instance3');
instance1.load();
instance2.load();
instance3.load();
and to sync:
instance1.getAt(0).set('text', 'testinstance1');
instance2.getAt(0).set('text', 'testinstance2');
instance3.getAt(0).set('text', 'testinstance3');
instance1.sync();
instance2.sync();
instance3.sync();
See in action here: https://fiddle.sencha.com/#view/editor&fiddle/2fl4
Open the network tab to see the parameter:
I have a marionette view that have a method to create a new model from a bootbox. Now i need to be able to edit the model from the bootbox, how can i I pass the current model data to the box?
This is some of my current code:
Module.Views.Chaptersx = Marionette.CompositeView.extend({
template: Module.Templates['documents/create/course/chapter/index'],
childView: Module.Views.ChapterItemx,
childViewContainer: "#chaptersCollection",
events: {
'click .chapters-create': 'create',
//'click #uploadFilesChapters': 'startUpload'
},
create: function (evt) {
console.log('create');
evt.preventDefault();
var me = this;
var box = bootbox.dialog({
show: false,
title: "Nueva Seccion",
message: Module.Templates['documents/create/course/chapter/chapterModal'],
buttons: {
success: {
label: "Guardar",
className: "btn-success",
callback: function () {
var chapterNo = $('#cn').val();
var chapterName = $('#chapterName').val();
var chapter = new Module.Models.Chapter({
chapterNo: chapterNo,
chapterName: chapterName,
});
me.collection.add(chapter);
}
}
}
});
box.on("show.bs.modal", function () {
console.log('numbers');
var number = (me.collection.size() + 1);
$('#cn').val(number);
});
box.modal('show');
},
TL;DR - use model's custom events or an event bus to pass the data.
You can reference this.model in the view, which is somewhat of a compromise (you're tying the view and the model).
You could pass the data via the event object's data property, but for that you're gonna have to extend some methods and get into backbone's nitty gritty.
Use a data- attribute on the element:
<div class="chapters-create" data-cats></div>
create: function (evt) {
var cats = $(evt.currentTarget).data('cats');
// ...
}
… which is considered bad habit by the way - you're still tying data to the DOM (or model to view, MVC speaking).
Well, I don't like either of the above, as they tend to have high coupling - I'd do it with custom events on a shared model resides at a higher level.
I don't know where the data comes from, but bottom line - shoot it in a custom event, or, better yet, use an event bus, like the one offered by marionette.js.
You need to create another view, call it EditView or something, render it, and provide the view.el as a message option to bootbox. However, the whole thing feels like a hack to me, and I think that it's better to implement a modalRegion and manage the modals yourself.
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.
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).
I want to change the proxy of a store before (!) it is loaded. The specific problem in this case is that I do not find the right moment, when to load.
In detail:
I have created a MVC-model by creating a view, a controller, a model, and a store as defined by EXTJS4 architecture. The view is a grid panel. It defines the store within its own define statement:
Ext.define('P.view.MyView' ,{
extend: 'Ext.grid.Panel',
alias : 'widget.MyView',
...
store: 'MyStore',
...
}
When I load the store with "autoload:true" everything works fine, but of course the proxy is then static as defined in the code. When I do not use "autoload" and try to set "extraParams" and the load the store in the "initComponent" of my view like:
initComponent: function() {
...
this.store.load();
....
I get an error: Object "MyStore" has no method 'load'.
How should I do it?
I would add that another common way to load data at the last moment is with the afterrender listener on the grid itself like this:
listeners: {
afterrender: function(grid) {
grid.store.getProxy().url = 'request/my.json'; //modify your URL
grid.store.load();
}
}
As you can see you can also influence your store and proxy settings. This help with reuse of the stores for similar grids.
When you call this.store.load() in initComponent the store is not initialized yet. The property this.store is string at the moment of calling. You have to call for loading after your widget's parent initComponent (where the store initialization is happening) was called:
initComponent: function() {
...
this.callParent(arguments);
this.store.load();
....