I just started with Extjs and I have few basic doubts.I have created a simple view(ScreenPropertiesPage) which has 1 select box and 1 custom view inside it. onchange of the select box value, view field is updated which is done in controller. I am done with creating view and controller which has listener for onchange select box value and updates associated view field.
But now the problem is : in my application I have to create 4 instances of ScreenPropertiesPage view and when onchange event is triggered from any views the textbox of 1st view is updated always. How to combine the event to specific view? What is the best procedure to combine controller and views and to reuse it(Even link to the documents from where I can learn controller view reusability is enough)? Any help is greatly appreciated.
Code skeleton for view:
Ext.define('Configurator.view.screenproperties.ScreenPropertiesPage', {
extend: 'Ext.container.Container',
alias: 'widget.screenpropertiespage',
requires: [
'Configurator.store.screenproperties.ScreenProperties'
],
autoScroll: true,
config: {
overLayMode: false
},
initComponent: function () {
var me = this;
this.items = [{
xtype: 'container',
componentCls: 'screenComboSelectorPanel',
layout: {
type: 'hbox',
align: 'center',
pack: 'center'
},
items: [{
xtype: 'combo',
store: Ext.create(
'Configurator.store.screenproperties.ScreenProperties'
),
itemId: 'screenSelector',
margin: 3,
width: 400,
listConfig: {
maxHeight: 200
},
fieldLabel: 'Screen Name',
disabledCls: 'disabledBtn',
disabled: me.getOverLayMode(),
queryMode: 'local',
emptyText: '-SELECT-',
valueField: 'screenName',
displayField: 'screenName',
forceSelection: true,
selectOnTab: true,
autoSelect: true,
height: 25,
tpl: Ext.create('Ext.XTemplate',
'<tpl for=".">',
'<div class="x-boundlist-item comboContainer "><div class="rowExpanedrTextArea " style="">{screenName} </div>{[this.isExpandable(xkey,parent,values,xindex)]}</div>',
'</tpl>'
),
displayTpl: Ext.create('Ext.XTemplate',
'<tpl for=".">',
'{screenName}',
'</tpl>'
)
}]
}, {
xtype: 'screenpropertieseditor',
itemId: 'messagesEditor',
margin: '25',
header: true,
frame: false,
border: true,
collectionName: 'messages',
title: 'Messages'
}]
me.callParent(arguments);
}
});
When user changes the value in combobox I want to update the screenpropertieseditor type view.
Controller for view :
Ext.define('Configurator.controller.ScreenProperties', {
extend: 'Ext.app.Controller',
refs: [{
ref: 'screenPropertiesPage',
selector: 'screenpropertiespage'
}, {
ref: 'screenSelector',
selector: 'screenpropertiespage combobox[itemId=screenSelector]'
}, {
ref: 'screenPropertiesMessagesEditor',
selector: 'screenpropertieseditor[itemId=messagesEditor]'
}, {
ref: 'screenPropertiesPage',
selector: 'screenpropertiespage'
}],
init: function (application) {
var me = this;
this.control({
'screenpropertiespage combobox[itemId=screenSelector]': {
change: this.screenPropertiesPageStoreHandler
}
});
},
screenPropertiesPageStoreHandler: function (thisObj, eOpts) {
var messagesEditor = this.getScreenPropertiesMessagesEditor();
var screenSelector = this.getScreenSelector();
var screenSelected = screenSelector.getValue();
//Screen tile store first time loading handling
if (screenSelected === undefined) {
screenSelected = screenSelector.getStore().getAt(0).data.screenName;
}
var selectedRecord = screenSelector.getStore().findRecord(
'screenName',
screenSelected, 0, false, false, true);
if (selectedRecord != undefined) {
Ext.apply(messagesEditor, {
'screenName': screenSelected
});
try {
messagesEditor.bindStore(selectedRecord.messages());
} catch (e) {}
}
}
});
ScreenPropertiesPage will hava lot more extra fields along with this. I have to create multiple instances of ScreenPropertiesPage. screenPropertiesPageStoreHandler method of Configurator.controller.ScreenProperties will be triggered whenever value changes in the combobox of any ScreenPropertiesPage view. But since my ref and selector in controller are not proper it always refers to the first ScreenPropertiesPage view.
You need to know that Controller in Extjs is singleton.
But you can force Controller in your case ScreenProperties to handle multiple instances of views. This is done by firing events from particular view instance to Controller to handle more complex logic.
Before i throw an example you need to be aware that using refs with handling multiple instance of the same view is wrong because it uses this code(it is just a wrapper): Ext.ComponentQuery.query('yourComponent')[0]; So from your view instances pool it gets first.
So you need to get rid off refs in your controller since it does not work with multiple instance of the same view.
Alright, lets make this happen and implement good way to handle multiple instances of the same view/components.
In your view:
initComponent: function () {
var me = this;
this.items = [{
xtype: 'container',
componentCls: 'screenComboSelectorPanel',
layout: {
type: 'hbox',
align: 'center',
pack: 'center'
},
items: [{
xtype: 'combo',
store: Ext.create(
'Configurator.store.screenproperties.ScreenProperties'
),
itemId: 'screenSelector',
margin: 3,
width: 400,
listConfig: {
maxHeight: 200
},
fieldLabel: 'Screen Name',
disabledCls: 'disabledBtn',
disabled: me.getOverLayMode(),
queryMode: 'local',
emptyText: '-SELECT-',
valueField: 'screenName',
displayField: 'screenName',
forceSelection: true,
selectOnTab: true,
autoSelect: true,
height: 25,
tpl: Ext.create('Ext.XTemplate',
'<tpl for=".">',
'<div class="x-boundlist-item comboContainer "><div class="rowExpanedrTextArea " style="">{screenName} </div>{[this.isExpandable(xkey,parent,values,xindex)]}</div>',
'</tpl>'
),
displayTpl: Ext.create('Ext.XTemplate',
'<tpl for=".">',
'{screenName}',
'</tpl>'
),
listeners: {
change: function (cmp, newValue, oldValue) {
this.fireEvent('onCustomChange',cmp,newValue, oldValue)
},
scope: this
}
}]
}
In your Controller - ScreenProperties you need to listen on this event and handle particular instance of component in view:
init: function (application) {
var me = this;
this.listen({
// We are using Controller event domain here
controller: {
// This selector matches any originating Controller
'*': {
onCustomChange: 'onCustonChangeHandler'
}
}
});
},
onCustonChangeHandler: function(componentInstance, newValue, oldValue) {
//Your complex logic here.
//componentInstance is the instance of actual component in particular view
}
In this way you can handle multiple instances of the same view with one controller since every particular component that is created in your view is passed by event.
Related
I have a ViewModel, which is empty, as the bind data is set dynamically elsewhere, which works fine -
Ext.define('TestApp.model.modelView', {
extend: 'Ext.app.ViewModel',
alias: 'viewmodel.test',
singleton: true
});
From my main view, I call a view on a button click, where I create a new window and display it
Ext.define('TestApp.view.main.Main', {
extend: 'Ext.container.Viewport',
xtype: 'app-main',
requires: [
'TestApp.model.modelView'
],
viewModel: {
type: 'test'
},
items: [
{
...
items: [
{
xtype: 'button',
text: 'Read Out',
margin: '5 10 0 10',
handler: function () {
win = Ext.create('TestApp.view.ReadOutButtonTemplate');
TestApp.Controllers.ReadOutButtonController.windowOpened();
}
},
Here is the window layout
Ext.define('TestApp.view.ReadOutButtonTemplate',
{
extend: 'Ext.window.Window',
alias: 'roTemplate',
itemId: 'roWindow',
requires: [
'TestApp.model.modelView'
],
viewModel: {
type: 'test'
},
controller: 'roTemplate',
xtype:'buttonTemplate',
autoShow: true,
height: 600,
width: 600,
constrainHeader: true,
title: 'Window',
Each time I create a window, I want to add it to the model view, and each time I close a window, if I have multiple open, I only want to destroy the one the cross was clicked on, which I have attempted to reflect in -
listeners:{
'close': function (win) {
win.destroy();
}
}
I have a feeling that each time I open a new window, it is creating a completely new ViewModel, which then when I destroy it, it is destroying the whole ViewModel, then not allowing me to use it for other windows that are still open, and in reality I want each window to be independent but using the same window template. Any help of thoughts on how to tackle this would be appreciated as I'm struggling to find any documentation on it.
Note - There are also controllers attached to the app, but these are working as I would expect.
I have an angularjs - kendo UI grid-based solution. In the controller for the grid I have placed the following code:
$scope.customClick = function(e) {
$scope.$apply(
function() {
e.preventDefault();
alert('customClick');
});
};
$scope.gridOptions = {
dataSource: $scope.gridData,
pageable: {
refresh: true,
pageSizes: true,
buttonCount: 5
},
scrollable: true,
sortable: true,
filterable: true,
selectable: true,
editable: "inline",
columns: [
{
command :[ {text: "", template: '<input type="checkbox" id="check-all" />', click: $scope.customClick} ]
},
{field: "DocumentKey", title: "Document Key"},
{field: "Sender", title: "Sender"},
{field: "Recipient", title: "Recipient"},
{field: "ChangeDate", title: "ReceivedBy Time"},
{field: "FlowComment", title: "Comment"},
{field: "Location", title: "Location"}
]
};
});
Added checkbox is displayed fine, but I don't know how to handle the click event. $scope.customClick is not triggered after clicking on check box.
A fairly old question, the user had probably found a solution long ago, but in case google search gets someone to this question, it's good to have an answer. JavaScript combined with libraries like KendoUI and AngularJS usually allow us to solve problems by using several different approaches, but here is one of them:
Say you have a grid defined like this:
<div kendo-grid="kendo.myGrid" k-options="gridOptions"></div>
Your JavaScript code to define this grid might look like this:
$scope.gridOptions = {
dataSource: new kendo.data.DataSource({
data: dataFromSomeLocalVariableMaybe,
pageSize: 10
}),
sortable: true,
pageable: {
pageSizes: [10, 20, 50]
},
columns: [{
field: "column1",
title: "Column 1",
width: "100px"
}, {
field: "column2",
title: "Column 2",
width: "120px"
}, {
command: [{
template: "<span class='k-button' ng-click='doSomething($event)'> Do something</span>"
}, {
template: "<span class='k-button' ng-click='doSomethingElse($event)'> Do something else</span>"
}],
title: " ",
width: "100px"
}]
};
Notice the $event that is passed to ng-click call to a function. That $event contains the actual click event data.
If it would be like this, then you would need to have these two functions defined:
$scope.doSomething = function($event) {
// Get the element which was clicked
var sender = $event.currentTarget;
// Get the Kendo grid row which contains the clicked element
var row = angular.element(sender).closest("tr");
// Get the data bound item for that row
var dataItem = $scope.kendo.myGrid.dataItem(row);
console.log(dataItem);
};
$scope.doSomethingElse = function($event) {
// Do something else
};
And that's it.
Omit $scope.
It should as follows:
{ command : {text: "", click:customClick}, template: '<input type="checkbox" id="check-all"/>}
Your command template should include ng directive, in your case ng-change for the checkbox input, which would point to your target function:
{
command :[{
text: "",
template: '<input type="checkbox" id="check-all" ng-change="customClick"/>'
}]
}
I create a custom xtype widget from a promooted class. When I want to render the widget to a container I get an error Cannot read property 'dom' of null.
cont is my container
var d = Ext.widget('MultiViewComponent', {
renderTo: cont
});
I have tried renderTo:cont.getLayout().
I have also tried using cont.add and then cont.doLayout();
For 'renderTo' documentation states:
Do not use this option if the Component is to be a child item of a Container. It is the responsibility of the Container's layout manager to render and manage its child items.
When using add to add multiple times. All components are visible on the screen but only one is inside cont.items.items(last one added) So when using cont.removeAll() only one is removed.
What am I missing here or doing wrong? Please help and advise.
UPDATE: If try to remove the widgets from container using container.removeAll()
only one of the widgets is deleted. Looking into container.items.items only one was there even if multiple components were added.
var d = Ext.widget('MultiViewComponent', {
});
cont.add(d);
cont.doLayout();
//later
cont.removeAll();
MultiViewComponent
Ext.define('myApp.view.MultiViewComponent', {
extend: 'Ext.container.Container',
alias: 'widget.MultiViewComponent',
requires: [
'Ext.form.Label',
'Ext.grid.Panel',
'Ext.grid.View',
'Ext.grid.column.Date',
'Ext.grid.column.Number'
],
height: 204,
itemId: 'multiViewComponent',
initComponent: function() {
var me = this;
Ext.applyIf(me, {
layout: {
type: 'vbox',
align: 'stretch'
},
items: [
{
xtype: 'label',
itemId: 'multiViewLabel',
text: 'My Label'
},
{
xtype: 'gridpanel',
itemId: 'multiViewGrid',
width: 498,
title: 'My Grid Panel',
store: 'Document',
columns: [
{
xtype: 'datecolumn',
dataIndex: 'dateCreated',
text: 'DateCreated'
},
{
xtype: 'gridcolumn',
dataIndex: 'documentType',
text: 'DocumentType'
},
{
xtype: 'gridcolumn',
dataIndex: 'description',
text: 'Description'
},
{
xtype: 'gridcolumn',
dataIndex: 'name',
text: 'Name'
},
{
xtype: 'gridcolumn',
dataIndex: 'creator',
text: 'Creator'
},
{
xtype: 'numbercolumn',
dataIndex: 'fileId',
text: 'FileId'
},
{
xtype: 'numbercolumn',
dataIndex: 'metaFileId',
text: 'MetaFileId'
},
{
xtype: 'gridcolumn',
dataIndex: 'uid',
text: 'Uid'
}
]
}
]
});
me.callParent(arguments);
}
});
cont.items is an Ext.util.MixedCollection, not an array. The underlying array is at cont.items.items. Anyway, you don't want to touch that. Use Ext.container.Container#remove to remove your child component afterward.
Your docs quote says it all... Containers do plenty of things with their components, most notably rendering, layout, and ComponentQuery wiring. You can't just change their DOM elements or javascript member variable and expect them to work. They have to be aware of when their state change to react accordingly, that's why you should always use the documented public methods to manipulate them. To let them know something happen and give them the chance to act on it.
Update
Given your feedback, it seems apparent that you're doing something funky with your code. Can't say because you haven't posted it... But here's a complete working example of what you want to do. Check your own code against it.
Ext.define('My.Component', {
extend: 'Ext.Component'
,alias: 'widget.mycmp'
,html: "<p>Lorem ipsum dolor sit amet et cetera...</p>"
,initComponent: function() {
if (this.name) {
this.html = '<h1>' + this.name + '</h1>' + this.html;
}
this.callParent();
}
});
Render a container with two initial children:
var ct = Ext.widget('container', {
renderTo: Ext.getBody()
,items: [{
xtype: 'mycmp'
,name: 'First static'
},{
xtype: 'mycmp'
,name: 'Second static'
}]
});
Adding a child dynamically post creation:
// Notice that layout is updated automatically
var first = ct.add({
xtype: 'mycmp'
,name: 'First dynamic'
});
// Possible alternative:
var firstAlt = Ext.widget('mycmp', {
name: 'First dynamic (alternative)'
});
ct.add(firstAlt);
And so on...
var second = ct.add({
xtype: 'mycmp'
,name: 'Second dynamic'
});
Removing a given child by reference:
ct.remove(first);
Removing a component by index:
ct.remove(ct.items.getAt(1));
Removing all components:
ct.removeAll();
Update 2
Your error is the itemId. One container must not have two items with the same itemId.
I don't know your app's architecture, but i think you could try show the widget when some container's event fired (beforerender for example).
I have the following EditorGridPanel on extJS:
http://jsfiddle.net/VDFsq/1/
Ext.onReady(function () {
var myData = [[ '<SPAN STYLE=\"text-align:Left;font-family:Segoe UI;font-style:normal;font-weight:normal;font-size:12;color:#000000;\"><P STYLE=\"font-family:Arial;font-size:16;margin:0 0 0 0;\"><SPAN><SPAN>HTML </SPAN></SPAN><SPAN STYLE=\"font-weight:bold;color:#FF0000;\"><SPAN>FORMAT</SPAN></SPAN><SPAN><SPAN> TEST<BR />TEST</SPAN></SPAN></P></SPAN>', "lisa#simpsons.com", "555-111-1224"],
[ 'Bart', "bart#simpsons.com", "555-222-1234"],
[ 'Homer', "home#simpsons.com", "555-222-1244"],
[ 'Marge', "marge#simpsons.com", "555-222-1254"]];
var store = new Ext.data.SimpleStore({
fields:[ {
name: 'name'
},
{
name: 'email'
},
{
name: 'phone'
}],
data: myData
});
var grid = new Ext.grid.EditorGridPanel({
renderTo: 'grid-container',
columns:[ {
header: 'Name',
dataIndex: 'name',
width:200
}
],
store: store,
frame: true,
height: 240,
width: 500,
enableColumnMove :false,
stripeRows: true,
enableHdMenu: false,
border: true,
autoScroll:true,
clicksToEdit: true,
title: 'HTML in Grid Cell',
iconCls: 'icon-grid',
sm: new Ext.grid.RowSelectionModel({
singleSelect: true
})
});
grid.on({
celldblclick: function() {alert(1);}
});
});
the problem is, when the gridCell contains HTML data (which is my situation) when you double click on the cell with html the grid does not fire the event celldblclick.
in my application I need to display that kind of html in the grid.
how can fix this problem? anyway to bubble the event from the html to the grid?
Thanks
It seems that there is some limits to dom tree deep inside your structure. I think it is not good idea to put html into grid - if you can unify it structure - may be templates would be more useful.
Try this instead of your HTML:
"<div ondblclick=\"alert('1!')\">1<div ondblclick=\"alert('2!')\">2<div ondblclick=\"alert('3!')\">3<div ondblclick=\"alert('4!')\">4</div>3</div>2</div>1</div>"
Event inheritance works fine in this HTML, but works only 2 levels deep in your EXt example.
NOTE: if you try
grid.on('rowdblclick', function(eventGrid, rowIndex, e) {
console.log('double click');
}, this);
you will not get problem (but, obviously, you can dblclick only rows in this way)
I have a gridpanel that allows inline editing of a column. This column uses a combobox as the editor, and neither the "change" event nor the "select" event give me something usable to backtrace the edited value to get the changed row from the gridpanel.
I believe Ext floats the editor's combobox so therefore I can't do something simple like
combo.up()
To return to the grid.
Here is the grid panel from the view:
{
xtype: 'gridpanel',
title: 'Important Projects',
id: 'importantProjectsGrid',
dockedItems: [],
flex: 1,
columns: [
{ header: 'Quote Name', dataIndex: 'QuoteName', flex: 4 },
{ header: 'Quote Status', dataIndex: 'QuoteStatusID', flex: 6, editor: {
xtype: 'combobox',
editable: false,
action: 'QuoteStatus',
selectOnTab: true,
store: 'statuses',
queryMode: 'local',
displayField: 'Description',
valueField: 'Description'
} }
],
store: 'myimpprojects',
selModel: {
selType: 'cellmodel'
},
plugins: [Ext.create('Ext.grid.plugin.CellEditing', {
clicksToEdit: 1
})]
}
Here is the controller code pertaining to this:
init: function () {
this.control({
'[action=QuoteStatus]': {
change: function (combo, new_value, old_value, opts) {
// I need to go back up from this combobox
// to get the row that this value was edited in
// to grab an ID value from that row's data
// in order to make an ajax request
}
}
});
},
Thanks for any help!
You can monitor store's update event.
init: function () {
this.getMyimpprojectsStore().on('update', function(store, record) {
// do something with record
});
// ...
},
Try putting the listener on the CellEditing plugin. There are events for beforeedit, edit, and validateedit that receive an object containing references to the grid, the record, field, row and column indexes, and more. You should be able to check for the combobox in the event handler and handle your information from there.
Quick link to the doc page: Ext.grid.plugin.CellEditing
I'm convinced that the update plugin will handle the update automatically, through the api of the underlying store and post the data automatically to the server if the proxy as autoSync to true.
Example of the configured proxy:
Ext.define('MyApp.store.YourStore', {
extend: 'Ext.data.Store',
model: 'MyApp.model.YourGridModel',
autoSync: true, //Commits the changes realtime to the server
proxy: {
type: 'ajax',
batchActions : true, //Commits the changes everytime a value is changed if true o otherwise store the changes and batch update them in 1 single post
api: {
read: 'path/to/select',
create: 'path/to/create',
update: 'path/to/update',
destroy: 'path/to/delete'
},
reader: {
type: 'json',
root: 'results',
successProperty: 'success'
},
writer: {
type: 'json',
writeAllFields: true
},
listeners: {
exception: function(proxy, response, operation){
Ext.MessageBox.show({
title: 'REMOTE EXCEPTION',
msg: operation.getError(),
icon: Ext.MessageBox.ERROR,
buttons: Ext.Msg.OK
});
}
}
},
listeners: {
write: function(proxy, operation){
var response = Ext.JSON.decode(operation.response.responseText);
if(response.success == true)
{
//TODO: Proxy - Messageboxes might be a little anoying we might instead use the status bar in teh grid or something so show status of the operation
Ext.MessageBox.show({
title: this.xFileLibraryTitle,
msg: response.message,
icon: (response.success == true)? Ext.MessageBox.INFO : Ext.MessageBox.ERROR,
buttons: Ext.Msg.OK
});
}
}
}
});
I would look specially for the two configs: "autoSync" and "batchActions"
Hope this helps you further with your issue!