I know the MVC concept in ExtJs just very briefly. Could you please help me to fill up the knowledge gaps please? I only know how to create a single view this way...
Ext.define('My.controller.Contacts', {
extend: 'Ext.app.Controller',
stores: ['Contacts'],
views: ['ContactsGrid'],
refs: [{ref: 'grid', selector: '', xtype: 'contacts-grid', autoCreate: true}],
getGrid: function() {
var g = this.getGrid();
return g;
}
});
this.getGrid() seems to give you the same grid view. But what if:
I want to create multiple instances of grid views dynamically, how? and where do I store them by convention?
For each view I have created, I want to give it a config object, like how I do Ext.create(somecontrol, config); but this case in MVC they are in refs? Where do I insert this config object for every view instance I create?
I have a store Contacts, what is the relationship between all these views and the store? One each, or all sharing one store?
Thank you very much.
For all of these the answer is going to be "it depends",
I would create dynamic views in the view definition if it's something that you read at startup, otherwise if you are clicking on a button and adding a view element, you could have everything in the controller, or you could have the controller call a method on the view that actually creates the view. I'd probably go with the latter but it depends on how you want to encapsulate your view logic.
As far as 'storing' these views is concerned, the convention is to give your views an unique id so you can reference it later, similar to DOM lookups. But you can also store references to a component in a variable obviously. It really depends on what you are doing. If a controller is constructing a bunch of dynamic elements, it might make sense to just hold on to references to those elements in the controller.
Views are typically defined in their own files under the MVC approach, essentially this is an Ext.define block with a configuration in it. There are a few sample MVC applications on the Sencha site, I recommend looking them over.
This really depends on what your doing. If you have multiple Contacts views, it might make sense to have a single store be referenced by multiple views, but in general Stores represent a collection of specific data. Like Books, or Contacts, or Employees. So if a view needs to show Books and Employees, it would make sense to references those stores in the view.
I think the crux of what you're asking is where do I encapsulate the logic for dynamic views. I would recommend creating reusable view components that encapsulate your display logic and have the controller create these components and give them the data to populate themselves. This gives you a nice flexible separation of concerns.
If you're just getting started with ExtJS and their implementation of MVC I highly recommend playing around with Sencha Architect. I wouldn't build a real project with it, but it's great for throwing together quick little demo apps, and it generates an MVC structure for you. Take a look at what it gives you and take a look at the demo applications on the Sencha site.
Here's some code to explain as well. The button text config is set to 'ZZZ' in my example. The component being instantiated from view 'view-XXX' (xtype) extends 'Ext.window.Window' in my example. If a component is being instantiated within a view, the Ext.create is implicit when using lazy instantiation via the xtype config. If a component is being instantiated within the controller, I use the standard notation Ext.create. If you want to give an id, use the Ext.id() to dynamically generate an id for the component. I won't repeat #James' answer for #2 and #3.
Try this:
Ext.define('App.controller.ControllerExample', {
extend : 'Ext.app.Controller',
stores: ['XXXs', 'YYYs'],
models: ['XXX', 'YYY'],
refs : [{
ref: 'viewport',
selector: 'view-viewport'
}, {
ref: 'XXX',
selector: 'view-XXX-window'
}, {
ref: 'YYY',
selector: 'view-YYY'
}],
init : function() {
this.control({
'view-XXX-window': {
minimize: function(win, obj) {
this.getXXX().hide();
},
close: function(panel, eOpt) {
this.getXXX().hide();
}
},
'view-XXX-window button[text=ZZZ]': {
click: function() {
var XXX = this.getXXX();
if (XXX === undefined) {
var viewportWidth = this.getViewport().getWidth();
var viewportHeight = this.getViewport().getHeight();
var windowWidth = viewportWidth * 0.9;
var windowHeight = viewportHeight * 0.9;
var x = (viewportWidth / 2) - (windowWidth / 2);
var y = (viewportHeight / 2) - (windowHeight / 2);
XXX = Ext.create('App.view.XXX', {
x: x,
y: y,
width: windowWidth,
height: windowHeight
});
XXX.show();
}
else {
XXX.show();
}
}
},
});
},
});
Related
I am looking for some description of best practices for views and models/collections in Backbone. I know how to add models to collections, render views using templates and use views in parent views, however I'm looking for more context and perhaps some example links.
I've updated this question to be more specific.
Let's say you have a more grid layout with all kinds of variation, that gets pulled from the same collection. What would you do here to create this page? A simple child view repeated in a parent view won't work because the variation of the grid items is too great. Do you:
create tons of tiny views and collections and render all of these different views using the relevant collections into that one page?
create a complex template file that has a loop in it, that as you go through the loop, the loop outputs different markup?
Do people put multiple views inside a parent view, all from the same model?
Similarly, do people mix different models into the same parent view? For example movies and tv shows - these different models, can get they added to the same collection that renders that list?
Thanks!
You've asked good question. To answer it lets take a look to this from other angle:
On my exp i used to check first is there any logic on parent view, like sorting, validation, search and so on. Second - Collection with models or just model with array as property : is the model is independent and may exist without collection , for example you have navigation item, and there are no sense to make separate model for each item and navigation as collection as you will never use item itself. Another case you have user list. You may use user model in a lot of places and its better to make a separate model for user and collection to combine it.
Your case with UL may be resolved with single model and items properties with array of li, simple grid may have same approach as i don't see some special logic on wrap from your description.
But i should point out - i had close task to build mansory grid with collection parsed from server, items were models as it had different data structure, different templates and different events listener.
Taking decision i considered folowing:
item as independent tile, may be used as in grid and also independent.
item is model + template + view. different Models types helped to support different data structure, different Views types helped to support different events listeners and user interaction, different templates - diff looks.
collection as a tool to fetch initial data + load extra items + arrange mansonry view + create models according to fetched result.
UPDATE
Lets consider this pseudo code as masnonry implementation:
Models may looks like these:
var MansonryModel = Backbone.Model.extend({
/* Common methods and properties */
}),
MansonryVideoModel = MansonryModel.extend({
defaults: {
type: 'video',
videoUrl: '#',
thumbnail: 'no-cover.jpg'
}
}),
MansonryImageModel = MansonryModel.extend({
defaults: {
type: 'image',
pictureUrl: '#',
alt: 'no-pictire'
}
});
var MansonryCollection = Backbone.Collection.extend({
model: MansonryModel
});
Views could be like this:
var MansonryTileView = Marionette.ItemView.extend({
/* place to keep some common methods and properties */
}),
MansonryVideoTile = MansonryTileView.extend({
template: '#video-tile',
events: {
'click .play': 'playVideo'
},
playVideo: function(){}
}),
MansonryImageTile = MansonryTileView.extend({
template: '#image-tile',
events: {
'click .enlarge': 'enlargePicture'
},
enlargePicture: function(){}
});
var MansonryListView = Marionette.CollectionView.extend({
childView : MansonryItem
})
Hope this help
I have two main page layouts: one is the default layout that is going to be used for most pages and the other one is a bare layout without the header/footer etc. that will be used for example for the login page. There might be more in the future.
My current router:
var AppRouter = Backbone.Router.extend({
routes: {
'login': 'login',
'main': 'main'
},
login: function(){
var mainLayoutView = new MainLayoutView({
'layout': 'bare'
});
App.Notes.mainLayoutContainer.show(mainLayoutView);
},
main: function(){
var mainLayoutView = new MainLayoutView({
'layout': 'default'
});
App.Notes.mainLayoutContainer.show(mainLayoutView);
}
});
How should approach the implementation of the MainLayoutView to be able to render the layout specified in options? Or should I actually have two separate layouts to handle the two templates? They'll obviously share a lot of functionality, though, so I'd rather have only one.
One way could look like this...
MainLayoutView = Backbone.Marionette.ItemView.extend({
initialize: function(options){
this.layout = options.layout;
},
onRender: function() {
if (this.layout == 'default') { // or "fullView"
// Render all components
} else {
...
// Perhaps there's nothing here
}
// Render the center partial view
}
});
Another way could be directly in your view template. Marionette suggests to have a templating system (Handlebars, Jade, ...).
If you don't use a templating system, first you should consider using one. Second, if you thought about always rendering all the views and simply $(element).hide() what you don't want: DON'T. The advanced users will be able to make the elements visible again and perhaps abuse of your system. Plus, this is useless processing and useless data sent through the wire :-)
I think I've found the best way to handle this. I've decided to set the template dynamically in "initialize":
var MainLayoutView = Backbone.Marionette.LayoutView.extend({
initialize: function(options){
if(options.layout==='bare'){
this.template = '#bare-layout';
}
else{
this.template = '#default-layout';
}
}
});
This way I can use as many templates for a layout as I want to. Although I'm starting to think that ideally I should be using separate instances of LayoutView for that.
We are currently building a Marionette based application.
Basically, we have a Marionette Application that has multiple regions defined on it.
Each region will act as a container for different Modules to display their views. I want each Module to have full control of what is being displayed in it's container, but I want the Application to allocate these regions. For simplicity, let's say that each module just has a simple ItemView.
I'm considering 2 approaches to populating those regions with the module views.
The first approach says that when each module is initialized, it will create its view and it will call the application to display its view in the specified region, for example:
var app = new Marionette.Application();
app.addRegions({
regionA: "#regionA",
regionB: "#regionB"
});
app.module("moduleA", function(moduleA, app, ...){
moduleA.on("start", function(){
var viewA = new MyViewA();
app.regionA.show(viewA);
}
});
app.module("moduleB", function(moduleB, app, ...){
moduleB.on("start", function(){
var viewB = new MyViewB();
app.regionB.show(viewB);
}
});
The second approach says that each module should expose some function that returns its view. The Application will call that function when ready and it will stick the view in the designated region.
I'm not sure which approach is better and would be happy to hear opinions.
I would definitely go with the second approach, after having gone with the first approach in the past I am now hitting the limitations of this approach and moving to the second approach. I wrote a blog post about it here
It depends which approach you take, both are fine, we choose the second option because we use require.js to load our modules dynamically.
var dashboardPage = Backbone.Marionette.Layout.extend({
template: Handlebars.compile(tmpl),
regions: {
graphWidget : "#graphWidget",
datePickerWidget: "#datePickerWidget",
searchWidget : "#searchWidget"
},
widget: {
graphWidget: null,
datePickerWidget: null,
searchWidget: null,
},
initialize: function(options){
this.someId= options.someId;
//if have someId ID - fetch model;
if(this.someId){
//fetch model if campaignId not null
this.modelAjax = this.model.fetch();
}
onShow: function() {
var that = this;
if(this.modelAjax){
this.modelAjax.done(function(){
that.widget.graphWidget= new graphWidget(graphWidgetOptions);
that.listenTo(that.widget.graphWidget, 'graphWidget', that.getGraphWidgetData, that);
....
that.graphWidget.show(that.widget.graphWidget);
that.datePickerWidget.show(that.widget.datePickerWidget);
I have a collection of items they all share some data (like an id, title) however outside of their shared stem of attributes they're functionally unique items and have separate views and business logic.
My problem is without prior experience in Backbone style MVC, I don't know the pros / cons of each... or perhaps if there is a much more elegant solution I'm missing. Here's an example of the 3 techniques I could potentially use?
var gizmoCollection = new Backbone.Collection(); // or extend
var gizmoModel = Backbone.Model.extend({ ... });
var morgView = Backbone.View.extend({ ... });
var blarView = Backbone.View.extend({ ... });
// 1.) Create an attribute for the view in the model?
gizmoCollection.add(new gizmoModel({ title: 'Gizmo1': view: morgView }));
gizmoCollection.add(new gizmoModel({ title: 'Gizmo2': view: blarView }));
// 2.) Or create a seperate model for each type of model?
var morgModel = morgModel.extend({});
var blarModel = blarModel.extend({});
gizmoCollection.add(new morgModel({ title: 'Gizmo1' });
gizmoCollection.add(new blarModel({ title: 'Gizmo2' });
// 3. Or register 'types' of views?
gizmoView.subClassView('morg', morgView);
gizmoView.subClassView('blar', blarView);
gizmoCollection.add(new gizmoModel({ title: 'Gizmo1', type: 'morg' });
gizmoCollection.add(new gizmoModel({ title: 'Gizmo2', type: 'blar' });
My choice would be to create separate models and it views if necessary. The reason is that each model should hold business logic for it self. Now, you may find sometimes easier to do this just with subviews if there is only presentational logic which is different for each model type or model attribute value.
You should keep in mind following:
Presentational logic goes to Presenter(s) (Backbone.View)
Business logic goest to model(s) (Backbone.Model)
Navigation logic either router (aka controller) or you can make your Event Bus from Backbone.Events or jQuery.callbacks() which will do this job and probably some other things which you want separate from your presenters and models.
Final note. Always keep in mind that your app will grow, sometimes it is wiser to add few more lines of code regardless you don't need so much complexity at the moment. But if you senses tell you that at some point that code will become more complex, well you should do it right away or later you will not have enough time.
These are some of the classes in my JavaScript application:
myApp.mode.model Handles the state
myApp.mode.controller Instantiates and updates components based on the model
myApp.data.dataManager Does operations on the dataSource
myApp.data.dataSource A big singleton with structured data
myApp.chart.grid A grid component
myApp.chart.scatter A scatter gram renderer
myApp.chart.tooltip A tooltip renderer
The interaction between these components are sketched below: (Sorry for the bad illus. skills ... ;) )
What I'm looking for is a clean way to pass the necessary arguments (dependency management) to the sub-components of the Visualization controller:
Let's say the user changes an indicator in the Visualization display. The model asks the data manager to load the necessary data.
When the data is loaded, the Visualization controller learns about the model change and should update its respective components: Grid, Scatter, Tooltips etc.
The Grid needs to know things such as xMin, xMax, width, height ...
The "Scatter renderer" also needs xMin, xMax, width, height. In addition it needs access to the big data singleton and it needs to find out what parts of the data to draw.
Three questions:
How do I pass the dataSource to the Scatter renderer? Do I declare it or pass it?
Many components are interested in the available data to draw. The data manager could answer this query. Should the "dataAvailability" be passed to the Scatter renderer or should it instead have the whole data manager as a dependency?
Looking at the schematic drawing, how would you lay out the objects so that a new state (new indicators, year, selection, width, height) would easily propagate down through all the sub-objects?
Thanks :)
You might want to look at AngularJS as it has DI capabilities (to support easier testing).
also check out https://github.com/briancavalier/wire for dependency injection
What you're talking about is more a question of MVC architecture. You don't have tens of instances of objects in different scopes to require a DI.
Looking at the diagram you drew, I have a strong feeling that in place of model there should be a controller. It's controller's duty to handle user's interactions. Your controller might look like this:
var Controller = {
init: function {
this.dataManager = ...;
this.grid = ...; // grid is some sort of widget
this.scatter = ...; // scatter is some sort of widget
// Setup widgets
this.scatter.x = 100;
this.scatter.y = 100;
},
bind: function {
// Bind to your indicator controls
$('#indicator').change(_.bind(this.update, this)); // mind the scope
},
update: function () {
var indicatorValue = $('#indicator').val();
var data = this.dataManager.getData(indicatorValue);
this.grid.setData(data);
this.scatter.setData(data);
this.scatter.setRegion(300, 300);
}
}
Controller.init();
Controller.bind();
That's it. Pass ready data to Grid and Scatter, don't pass data source and data query parameters to them.