I have an MVC architecture, but when I try to make another TabPanel insite an existing one, I get this in the browser:
el is null
el = el.dom || Ext.getDom(el); ext-all-debug.js (line 12807)
From what I understand, it seems that the new TabPanel can't find the needed div for it to render. Anyway, here is the controller:
Ext.define('FI.controller.MainController', {
extend: 'Ext.app.Controller',
id: 'mainController',
views: ['MainTabPanel', 'UnitsTabPanel', 'SummariesTabPanel'],
mainTabPanel : {},
unitsTabPanel : {},
summariesTabPanel : {},
init: function(){
console.log("main controller is init");
console.log(this);
this.control({
'mainTabPanel':{
afterrender: this.onCreate
}
});
this.mainTabPanel = Ext.widget('mainTabPanel');
},
onCreate: function(){
console.log("main tab panel is created");
console.log(this);
this.unitsTabPanel = Ext.widget('unitsTabPanel');
this.summariesTabPanel = Ext.widget('summariesTabPanel');
}
});
This is the MainTabPanel:
Ext.define("FI.view.MainTabPanel", {
extend: 'Ext.tab.Panel',
renderTo:'container',
alias: 'widget.mainTabPanel',
enableTabScroll: true,
items:[{
title:'Units',
html: "<div id='units'></div>",
closable: false
},
{title: 'Summaries',
html: "<div id='summaries'></div>",
closable:false
}
]
});
And this is the SummariesTabPanel(the one with problems):
Ext.define("FI.view.SummariesTabPanel", {
extend: 'Ext.tab.Panel',
renderTo: 'summaries',
alias: 'widget.summariesTabPanel',
enableTabScroll: true
});
The problem is with the SummariesTabPanel. If I don't create it, the UnitsTabPanel gets rendered. For some reason, it can't find the summaries div.
Can you please tell me what is wrong?
The SummariesTabPanel renders to the "units" div according to your last snippet of code, is that correct? If not, replace renderTo: 'units' with renderTo: 'summaries'.
Edit
Since it was not the case, you may take a look ath this piece of Ext 4 Panel documentation (here: http://docs.sencha.com/ext-js/4-0/#!/api/Ext.panel.Panel-cfg-html ) that says that the html content isn't added until the component is completely rendered. So, you have to wait for the afterrender event of the tab (not the tabpanel, as you do now) before you can actually get the DOM element.
If you instantiate this Panel
{title: 'Summaries',
html: "<div id='summaries'></div>",
closable:false
}
and store the pointer to it into a separate variable, you could listen to its afterrender event and try again.
A workaround to this could be using an existing element of the page (that is, a static html fragment) instead of adding it via the Panel's html config option.
Why are you doing this that way? If you want to have a nested panels - just define them inside one another. Don't use renderTo
Ext.define("FI.view.SummariesTabPanel", {
extend: 'Ext.tab.Panel',
alias: 'widget.summariesTabPanel'
});
Ext.define("FI.view.MainTabPanel", {
extend: 'Ext.tab.Panel',
alias: 'widget.mainTabPanel',
enableTabScroll: true,
items:[{
xtype: 'summariesTabPanel'
title:'Units',
closable: false
}
]
});
Related
Ext JS - v 6.2.1
I'm interested in reusing a main component developed in ExtJs which i've written in different tabs of the tabpanel. The main component is composed of two child components and each of these components have their respective controllers and the child components interact among themselves. Since the event listeners are added in the controller domain the events fired in one instance of tab affects the other tabs as well.
Pseudo code of the scenario explained
*********** Views ***********
Ext.define('MainApp.view.main.Main', {
extend: 'Ext.tab.Panel',
xtype: 'mainapp-main',
controller: 'main',
...
items: [{
title: 'Main Component Instance 1',
closable: true,
items: [{
xtype: 'mainapp-maincomponent'
}]
}, {
title: 'Main Component Instance 2',
closable: true,
items: [{
xtype: 'mainapp-maincomponent'
}]
}]
});
Ext.define('MainApp.view.maincomponent.MainComponent', {
extend: 'Ext.panel.Panel',
xtype: 'mainapp-maincomponent',
controller: 'maincomponent',
...
items: [{
xtype: 'mainapp-component1'
},{
xtype: 'mainapp-component2'
}]
});
Ext.define('MainApp.view.component1.Component1', {
extend: 'Ext.panel.Panel',
xtype: 'mainapp-component1',
controller: 'component1',
...
items: [{
xtype: 'button',
cls: 'contactBtn',
scale: 'large',
text: 'Fire Event',
handler: 'onComponentButtonTapped'
}]
});
Ext.define('MainApp.view.component2.Component2', {
extend: 'Ext.panel.Panel',
xtype: 'mainapp-component2',
controller: 'component2',
...
items: [{
xtype: 'textfield',
value: 'Button is not clicked yet',
width: 500,
readOnly: true
}]
});
*********** Controllers ***********
Ext.define('MainApp.view.component1.Component1Controller', {
extend: 'Ext.app.ViewController',
alias: 'controller.component1',
onComponentButtonTapped: function (btn, eventArgs) {
this.fireEvent('component1ButtonTapped', btn, eventArgs);
}
});
Ext.define('MainApp.view.component2.Component2Controller', {
extend: 'Ext.app.ViewController',
alias: 'controller.component2',
listen: {
controller: {
'component1': {
component1ButtonTapped: 'onComponent1ButtonTapped'
}
}
},
onComponent1ButtonTapped: function(){
this.getView().down('textfield').setValue(Ext.String.format('Button tapped at {0}', Ext.Date.format(new Date(), 'Y-m-d H:i:s')));
}
});
Can somebody please suggest the correct way of addressing this used case.
More Details
The tab panel is loaded in viewport
Tab 1 has first instance of Main Component - M1
Tab 2 has second instance of Main Component - M2
Every instance of Main Component has two child components
Component1 - M1C1 > M1C1 View and M1C1 Controller
Component2 - M1C2 > M1C2 View and M1C2 Controller
Similarly for the second instance of Main Component
Component1 - M2C1 > M2C1 View and M2C1 Controller
Component2 - M2C2 > M2C2 View and M2C2 Controller
Requirement here is to restrict Actions done on M1C1 view should be processed by M1C2 Controller only.
Issue is that with the code above M2C2 Controller also listens to the event
Change handler: 'onComponentButtonTapped' to
{
xtype: 'button',
cls: 'contactBtn',
scale: 'large',
text: 'Fire Event',
listeners: {
click: 'onComponentButtonTapped',
scope: SCOPE-YOU-WANT
}
}
First, I believe you are overnesting in your MainApp.view.main.Main component and you don't use a layout for your overnesting either. That can lead to unnecessary consequences like extra layout runs and bloated DOM affecting performance.
Ok, onto your question! I don't think scope is what you're actually after here. I see it as how to properly architect your ViewControllers. While understanding this isn't your real code, I will say that in this example you don't need that Component1Controller controller. Get rid of it and the button's handler will get resolved up to the maincomponent controller which is where you can control both child items. I only say this fully knowing that your Component1Controller is likely doing other things so it's not going anywhere just to say that not all containers need controllers. It also serves what I would do in this case if Component1Controller is to stick around. Instead of firing an event on the Component1Controller instance and use the event domain to get to Component2Controller, I would fire a view event on Component1 and add a listener on your config object so that maincomponent controller gets the event to do it's thing.
That sounds messy and hard to follow so I created this fiddle.
I have created a view (a panel) with 3 subpanels ...
When the view loads , I want a function to run in viewController and based on its outcome , I want subpanel 1 to be visible(subpanel2 to be invisible) or subpanel2 to be visible(subpanel1 to be invisible)
How can I accomplish this ?
You are looking for card layout. It is already implemented. So you don't have to implement again. Just tell it witch panel gonna be active it will do all layout things itself. Checkout this api doc.
May be the Accordion layout can help you:
This is a layout that manages multiple Panels in an expandable accordion style such that by default only one Panel can be expanded at any given time
Here's a full example, it's quite straight forward:
Fiddle
Ext.define('FooController', {
extend: 'Ext.app.ViewController',
alias: 'controller.foo',
init: function(view) {
var child = Math.random() < 0.5 ? 'p1' : 'p2';
view.setActiveItem(this.lookupReference(child));
}
})
Ext.define('Foo', {
extend: 'Ext.container.Container',
layout: 'card',
controller: 'foo',
items: [{
title: 'P1',
reference: 'p1'
}, {
title: 'P2',
reference: 'p2'
}]
});
Ext.onReady(function() {
new Foo({
renderTo: document.body,
width: 200,
height: 200
});
});
Give itemId to all three panel and then fireEvent.
Listener of view
listeners:{
show: function(){
me.fireEvent('showHidePanel');
}
}
define showHidePanel method in Controller and in that method get panel by using down() with item id and hide/show panel by using hide()/show() method.
I defined a Ext.grid.Panel called JobList that has an Ext button with an itemId called myButton. JobList has a controller. In the controller I have the following code:
Ext.define('App.controller.JobList', {
extend: 'Ext.app.Controller',
refs: [
{ref: 'jobList', selector: '#jobList'},
{ref: 'myButton', selector: '#myButton'}
],
init: function(){
this.control({
'jobList': {
select: this.selectJob
}
});
},
selectJob: function(){
this.getMyButton().enable();
}
});
I then create two instances of jobList using Ext.create they have an id of jobList1 and jobList2. The problem is when I select a job in the list on jobList2 it will enable the myButton on jobList1 not jobList2. How do I correctly enable the myButton on each instance of jobList?
Try to avoid referencing by itemId, and use aliases instead:
// in App.view.JobList.js you should have
Ext.define('App.view.JobList', {
extend: 'Ext.grid.Panel',
alias: 'widget.job-list',
// ...
dockedItems: [{
xtype: 'button',
name: 'myButton',
text: 'My button',
}]
});
// and the in the App.controller.JobList.js:
// ...
'job-list': {
selectionchange: function(model, selected) {
var button = model.view.up('job-list').down('button[name=myButton]');
button.setDisabled(Ext.isEmpty(selected));
}
}
Check the example: https://fiddle.sencha.com/#fiddle/tq1
You're using global controller, so it catches events from all views that matching the query. Look at MVVM pattern in extjs5. Sencha did a great job, in MVVM each instance of view has their own instance of ViewController, so this situation will never happen. If you want to stick with MVC pattern, then you need to manually control this. Forget about refs, you can't use them if you have more than one instance of your view class. Get other components only by query from your current component. Something like:
Ext.define('App.controller.JobList', {
extend: 'Ext.app.Controller',
init: function() {
this.control({
'jobList': {
select: this.selectJob
}
});
},
selectJob: function(selectionModel){
//first of all you need to get a grid. We have only selectionModel in this event that linked somehow with our grid
var grid = selectionModel.view.ownerCt; //or if you find more ellegant way to get a grid from selectionModel, use it
var button = grid.down('#myButton');
button.enable();
}
});
I’m trying to add different detail view based on taped item in list.
I have home screen that is using List component and this view is displaying ['Real estate', 'Vehicles', 'Jobs']... as menu items.
Based on selected item in list, I want to display different view.
And I want to follow MVC design pattern..
Here is some code...
App.js
Ext.application({
name: 'App',
controllers: ['Main'],
views: ['Viewport', 'HomePage'],
stores: ['HomePageItems'],
models: ['HomePageItem'],
launch: function () {
Ext.Viewport.add(Ext.create('App.view.Viewport'));
}
});
Viewport.js
Ext.define("App.view.Viewport", {
extend: 'Ext.navigation.View',
requires: [ 'App.view.realestate.Realestate',
'App.view.HomePage',
'App.view.jobs.Jobs',
'App.view.other.Other',
'App.view.vehicles.Vehicles'
],
config: {
items: [
{
xtype: 'homepage'
}
]
}
});
HomePage.js ( xtype = "homepage" )
Ext.define('App.view.HomePage', {
extend: 'Ext.List',
xtype: 'homepage',
id: 'homepage',
config: {
title: 'Oglasi',
itemTpl: '<strong>{name}</strong><p style="color:gray; font-size:8pt">{description}</p>',
store: 'HomePageItems',
onItemDisclosure: true
}
});
Main.js
Ext.define('App.controller.Main', {
extend: 'Ext.app.Controller',
config: {
refs: {
main: '#homepage'
},
control: {
'homepage': {
disclose: 'HookUpDetailView'
}
}
},
HookUpDetailView: function (element, record, target, index, ev) {
// TO DO: I have 4 differente views to load programmaticaly based on selected item in List
//'App.view.realestate.Realestate'
//'App.view.jobs.Jobs'
//'App.view.other.Other'
//'App.view.vehicles.Vehicles'
}
});
I found one example, but it's not working for me (push method doesn't exist)
this.getMain().push({
xtype: 'realestatehome'
});
Thank in advance!
The method you're looking for is
http://docs.sencha.com/touch/2-0/#!/api/Ext.Container-method-add
this.getMain().add({xtype: 'realestatehome'});
But what you have doesnt make sense, realestatehome is a list, you can't add a component under it. You need to read about layoutsFrom the link above
Push should work. You could try something like this.
HookUpDetailView: function (list, record) {
if(record.data.description==='real estate'){
this.getRealEstate().push({
xtype: 'realestatehome',
title: record.data.description,
data. record.data
});
if(record.data.description==='Vehicles'){
this.getVehicles().push({
xtype: 'vehicles',
title: record.data.description,
data. record.data
});
}
}
And a particular view could look like
Ext.define('App.view.RealEstateHome', {
extend: 'Ext.Panel',
xtype: 'realestatehome',
config: {
styleHtmlContent: true,
scrollable: 'vertical',
tpl: [
'{description}, {example}, {example}, {example}'
]
}
});
And the refs to access your particular view should look something like
refs: {
realEstate: 'realestatehome',
vehicles: 'vehicleshome'
},
Hope that helps
I made a mistake in controller this.getMain()
get main is returning Ext.List and I need Ext.navigation.View that have 'push' method.
So... I added xtype and id to my viewport ("container")
and quick change in my controller solved my troubles...
refs: {
main: '#homepage',
container: '#container'
}
and instead of getting Ext.List object
this.getContainer().push({
xtype: 'realestatehome'
});
And this work like a charm :)
simple question for you today...
This works:
var carousel = Ext.create('Ext.Carousel', {
fullscreen: 'true',
//load in views view clean instantiation using
// the widget.alias's defined in each view... yea
// For some reason, putting flex on these components... oh...
// Have to call directly in by just the xtype since these are just
// references..
items: [
{
xtype: 'Main'
},
{
xtype: 'CommentList'
}
]
This does NOT work:
var tabpanel = Ext.create('Ext.TabPanel', {
fullscreen: 'true',
tabBarPosition: 'bottom',
defaults: {
styleHtmlContent: true
},
//load in views view clean instantiation using
// the widget.alias's defined in each view... yea
// For some reason, putting flex on these components... oh...
// Have to call directly in by just the xtype since these are just
// references..
items: [
{
xtype: 'Main',
title: 'The Main',
iconCls: 'user'
},
{
xtype: 'CommentList',
title: 'Comments',
iconCls: 'user'
}
]
});
As you can see, they are pretty much the same except one is a TapPanel (with the required default configs added) and the other is a carousel.
Everything else is exactly the same.... This is in the app.js of my Sencha Touch 2.0 app designed following the MVC architecture.
The result of the not-working TabPanel is that I only see the first view (Main) and no tab-bar appears in the bottom of the screen.
Any ideas what my problem might be?
I am not sure if this is an issue but in my code the line is:
Ext.create("Ext.tab.Panel", {
Not:
Ext.create('Ext.TabPanel', {
Fullscreen should be fullscreen: true instead of fullscreen: 'true'. You could also add this code to make them switch:
cardSwitchAnimation: {type: "fade", duration: 1000},
layout: "card",
Didn't test it, but it worked for me (got it from a snippet of my own code)