I'm working on a small application where I want to add a new row to a single-column EditorGridPanel, and each time a new row is added I want it to be in edit mode so the user can write the wanted text into it immediately.
Relevant code from a complete jsfiddle:
store = new Ext.data.ArrayStore({
autoDestroy: true,
storeId: 'myStore',
idIndex: 0,
fields: [
{ name: 'group', type: 'string' }
],
data: [],
listeners: {
add: function(t, records, index) {
// This call causes problems
grid.startEditing(index, 0);
}
}
});
When the add event in the store is triggered, which should be after the record has been added, it seems it still hasn't been added to the actual grid, only the store. This causes an error in in the grid component.
Is there any other event, possibly in the grid component, that can be used instead?
This is just a matter of timing. You are trying to add the grid when you add the record to the store and it takes a little time for the two things to be in sync. You should try putting your code on the rowinserted event handler of the grid.getView() .
For your code to work the way you have it right now, simply change it to:
store = new Ext.data.ArrayStore({
autoDestroy: true,
storeId: 'myStore',
idIndex: 0,
fields: [
{ name: 'group', type: 'string' }
],
data: [],
listeners: {
add: function(t, records, index) {
Ext.defer(function() {
grid.startEditing(index, 0);
},10);
}
}
});
Doing this will give enough time for the info between the store and the grid to be in sync.
Related
Ok I'm pretty sure I know exactly what I need to do here but I'm not sure how to do it. Basically I have a grid that I want to make a key column bind to an array of key/values, which I've done before with kendo (not using Angular) and I know that when I'm creating my key/value array asynchronously then that needs to complete before I can get them show-up with kendo, which I have done using promises before.
So here I have the same issue only angular is also involved. I need to fetch and format an array of data into the format in which a kendo grid column can digest it, so no problem here is my controller code:
var realm = kendo.data.Model.define({
id: 'realmID',
fields: {
realmID: { editable: false, nullable: true }
realmType: { type: 'string', validation: { required: true } }
}
})
var ds1 = kendoHelpers.dataSourceFactory('realms', realm, 'realmID')
var realmType = kendo.data.Model.define({
id: 'realmTypeID',
fields: {
realmTypeID: { editable: false, nullable: true },
name: { type: 'string', validation: { required: true } }
}
})
var ds2 = kendoHelpers.dataSourceFactory('realms/types', realmType, 'realmTypeID')
$scope.mainGridOptions = {
dataSource: ds1,
editable: true,
navigatable: true,
autoBind:false,
toolbar: [
{ name: "create" },
{ name: 'save' },
{ name: 'cancel' }
],
columns: [
{ field: 'realmID', title: 'ID' }
{ field: 'realmTypeID', title: 'Realm Type', editor: realmTypesDDL, values: $scope.realmTypeValues },
{ command: "destroy" }
]
}
$scope.secondGridOptions = {
dataSource: ds2,
editable: true,
navigatable: true,
toolbar: [
{ name: "create" },
{ name: 'save' },
{ name: 'cancel' }
],
columns: [
{ field: 'realmTypeID', title: 'ID' },
{ field: 'name', title: 'Name' }
{ command: "destroy" }
]
}
ds2.fetch(function () {
$scope.realmTypeValues = [{ text: 'Test', value: "24bc2e62-f761-4e70-804c-bc36fdeced3d" }];
//this.data().map(function (v, i) {
// $scope.realmTypeValues.push({ text: v.name, value: v.realmTypeID})
//});
//$scope.mainGridOptions.ds1.read()
});
function realmTypesDDL(container, options) {
$('<input />')
.appendTo(container)
.kendoDropDownList({
dataSource: ds2,
dataTextField: 'name',
dataValueField: 'realmTypeID'
});
}
I made this dataSourceFatory helper method above to return me a basic CRUD kendo dataSource that uses transport and also injects an authorization header which is working fine so don't get hung up on that, ultimately I'm going to be using this data in another grid as well as for reference values for the main grid, but I've hard coded some values that I can use to test with in the ds2.fetch callback.
My HTML is pretty plain:
<div>
<h2>Realms</h2>
<kendo-grid options="mainGridOptions"></kendo-grid>
<h2>Realm Types</h2>
<kendo-grid options="secondGridOptions"></kendo-grid>
</div>
This all works fine and well except I am only seeing the GUID of the realmTypeID in the grid, I click it and the editor is populated correctly so that's good but I want the text value to be displayed instead of the GUID. I'm sure the issue is that the array of values is empty whenever angular is binding to the grid options. My questions are:
How do I either delay this bind operation or manually rebind it after the fetch call?
Is there a better way to handle a situation like this? I try not to expend finite resources for no reason (IE making server calls when unnecessary)
Note: When I move the creation of the text/value array to happen before the grid options, I get the desired behavior I am after
EDIT A work around is to not use the directive to create the grid and instead defer the grid creation until the callback of whatever data your column is dependent on, I was hoping for a more elegant solution but this is better than nothing. So your HTML becomes something like
<h2>Realms</h2>
<div id="realms"></div>
<h2>Realm Types</h2>
<kendo-grid options="secondGridOptions"></kendo-grid>
Then you can create the grid in the fetch callback for example:
ds2.fetch(function () {this.data().map(function (v, i) {
$scope.realmTypeValues.push({ text: v.name, value: v.realmTypeID})
});
$('#realms').kendoGrid($scope.mainGridOptions);
$scope.mainGridOptions.dataSource.fetch()
});
But this doesn't feel very angularish so I'm really hoping for a better solution!
Ok...well I think I hacked this enough and without another suggestion I'm going to go forward with this approach. I'm just going to move the binding logic to the requestEnd event of the second grid so that the values array can be populated right before the binding even. I'm also reworking the values array in this method. It is a bit weird though, I think there is some kendo black magic going on with this array because I can't just set it to a new empty array without it breaking completely...which is why I'm poping everything out prior to repopulating the array. That way when something is deleted or edited in the second grid, the DDL in the first grid is updated in the callback.
function requestEnd(e) {
for (var i = $scope.realmTypeValues.length; i >= 0; i--) $scope.realmTypeValues.pop();
var data;
if (e.type == "read")
data = e.response;
else
data = e.sender.data();
data.map(function (v, i) { $scope.realmTypeValues.push({ text: v.name, value: v.realmTypeID }); });
if ($('#realms').data('kendoGrid') == undefined) {
$('#realms').kendoGrid($scope.mainGridOptions);
}
else
$('#realms').data('kendoGrid').columns[4].values = $scope.realmTypeValues;
}
ds2.bind('requestEnd', requestEnd);
So I'm going to accept my own answer unless anyone has a better approach!
EDIT: ANSWER FOUND
I found the answer in this post. there is a private store config field called remoteSort that is set to true by default, so client-side sorters won't get used unless remoteSort is explicitly set to false.
I'm trying to sort a grid of Features by the number of User Stories in them. I've tried a couple different things. The first was a sorter function in the data Store Configuration:
Ext.create('Rally.data.wsapi.Store',{
model: 'PortfolioItem/Feature',
autoLoad:true,
start: 0,
pageSize: 20,
fetch: ['Name', 'ObjectID', 'Project', 'Release', 'UserStories','State','_ref'],
context://context stuff
filters:[
//filter stuff
],
sorters:[
{
property:'UserStories',
sorterFn: function(o1, o2){
return o1.get('UserStories').Count - o2.get('UserStories').Count;
}
}
],
listeners: //listener stuff
But this always returned nothing. (when the sorter was not included, i did get back all the Correct Features, but I could not sort the the number of user stories per Feature).
I also tried adding a sorter to the column in the grid, as seen in this post:
xtype: 'rallygrid',
width:'50%',
height:400,
scroll:'vertical',
columnCfgs: [
{
text:'Stories',
dataIndex:'UserStories',
xtype:'numbercolumn',
sortable:true,
doSort: function(direction){
var ds = this.up('grid').getStore();
var field = this.getSortParam();
console.log(ds, field, direction);
ds.sort({
property: field,
direction:direction,
sorterFn: function(us1, us2){
return (direction=='ASC'? 1 : -1) * (us1.get(field).Count - us2.get(field).Count);
}
});
},
width:'20%',
renderer:function(us){
return us.Count;
}
}
]
But I was having the same issues that the person in the other thread was having, where nothing was getting sorted.
I have a simple Sencha App that has a main view. The view extends the Ext.navigation.View so that I can "push" to a new view when the user selects an item in the list. This happens by setting up a listener and then calling the push function on the MainView object.
However, I'm having problems getting the data across to that view. I tried using the answer from this StackOverflow question, but it didn't work.
In that answer it suggests that you use the record parameter of the itemTap() function, but this returns as an empty object.
Why does record return as an empty object?
Perhaps I'm going about this the wrong way?
In my case, I have a list of "brands", each with a title, image and description. I'd like to use that in the panel that slides in.
The launch function of my app which creates the view and ads to the viewport
launch: function() {
// Destroy the #appLoadingIndicator element
Ext.fly('appLoadingIndicator').destroy();
// Create instance of the main view so we can use it's functions
SenchaTest.MainView = Ext.create('SenchaTest.view.Main');
// Initialize the main view
Ext.Viewport.add(SenchaTest.MainView);
},
Here is my view
Ext.define('SenchaTest.view.Main', {
extend: 'Ext.navigation.View',
xtype: 'main',
requires: [
'Ext.TitleBar',
'Ext.Video',
'Ext.carousel.Carousel',
'Ext.Img'
],
config: {
fullscreen: true,
items: [
{
title: 'Test',
layout: {
type: 'vbox',
align: 'stretch'
},
items: [{
xtype: 'highlightscarousel',
flex: 0.35
}, {
xtype: 'list',
displayField: 'title',
flex: 0.65,
store: Ext.create('SenchaTest.store.Brands'),
itemTpl: '<img src="{image}" class="listThumb"><h1 class="listTitle">{name}</h1><span class="clearFloat"></span>',
listeners: {
itemtap: function(nestedList, list, index, element, post, record) {
}
}
}]
}
]
}
});
Based on the Sencha Touch docs, the signature of the itemtap listener is:
(this, index, target, record, e, eOpts)
you're using:
(nestedList, list, index, element, post, record)
so that might be why the record is an empty object. If that's not the case, could you post a JSFiddle or some kind of working example of the problem?
This is my code for combo box inside grid:
{
header: 'FSCS',
dataIndex: 'acntOvrrideTypeCd',
flex: 1,
renderer: function(val, metaData, record, rowIndex, colIndex) {
var id = Ext.id();
var store = new Ext.data.Store({
fields: ['code', 'description'],
data: [{
"code": "",
"description": ""
}, {
"code": "E",
"description": "E"
}, {
"code": "D",
"description": "D"
}, {
"code": "S",
"description": "S"
}]
});
Ext.Function.defer(
(function() {
var cb = Ext.create('Ext.form.ComboBox', {
id: 'acntOvrrideTypeCd-' + rowIndex,
queryMode: 'local',
renderTo: id,
store: store,
forceSelection: true,
triggerAction: 'all',
lazyRender: true,
size: 5,
valueField: 'code',
displayField: 'description',
value: val
//listeners:{
// scope: this,
// 'select': Ext.getCmp('amlFscsForm').controller.amlShow(rowIndex)
//}
});
cb.on(afterrender, function() {
console.log("------- box---" + rowIndex);
Ext.getCmp('amlFscsForm').controller.amlShow(rowIndex);
});
}), 0.25);
console.log("i----------" + id);
return (Ext.String.format('<div id="{0}"></div>', id));
}
}
'afterrender' event is not fired. I need to enable or disable component after its rendered.
Can anyone help?
It's just a typo, afterrender should be in quotes otherwise you will just add the function for undefined event.
cb.on('afterrender',function(){
console.log("------- box---" + rowIndex);
Ext.getCmp('amlFscsForm').controller.amlShow(rowIndex);
});
There are a few problems with your code.
It looks like you're trying to create a combobox in the renderer function of a grid (your code at the top didn't get included in the code block). You're better off using the Ext.grid.plugin.CellEditing plugin instead, which will create a field on demand instead of when the column renders. Plus, every time your grid view refreshes you'll be creating another store and combobox for every row in the grid. Not good for performance, not good for the user experience either.
When calling defer, the duration is in milliseconds, not seconds. Also, you don't need to wrap the function in parenthesis. Just give it the function itself. Like this:
Ext.defer(function(){
// do stuff
}, 25);
Setting lazyRender to true only works if your component is the child of some container that doesn't render all its components immediately (like a tabpanel).
It may be easier to just set the disabled config in the combobox when you create it instead of when you render it, unless you don't have the information available at creation time.
Like nscrob said, when using the on method you need to specify the event as a string. If you use the listeners config (which you have commented out), you can just do:
listeners: {
afterrender: function(){
console.log("------- box---" + rowIndex);
Ext.getCmp('amlFscsForm').controller.amlShow(rowIndex);
},
select: function(){
Ext.getCmp('amlFscsForm').controller.amlShow(rowIndex);
}
}
It's important to note that the scope of these listener functions defaults to the component itself (your combobox) so scope: this is unnecessary. Unless you want the scope to be whatever object is creating this combobox, that is.
The first point is the most important. Look into using the CellEditing (or RowEditing) plugin and I guarantee things will go a lot more smoothly.
I'm trying to learn how to use the EXTJS grids for some simple CRUD operations over a table in a admin app.
I have a simple grid that allows someone to edit users, the store is defined as:
var userDataStore = new Ext.data.Store({
id: 'userDataStore',
autoSave: false,
batch: true,
proxy: new Ext.data.HttpProxy({
api: {
read: '/Admin/Users/All',
create: '/Admin/Users/Save',
update: '/Admin/Users/Save'
}
}),
reader: new Ext.data.JsonReader(
{
root: 'Data',
idProperty: 'ID',
totalProperty: 'total',
successProperty: 'success',
messageProperty: 'message'
}, [
{ name: 'ID', type: 'string', allowBlanks: false },
{ name: 'NT_ID', type: 'string', allowBlank: false },
{ name: 'EMail', type: 'string', allowBlank: false },
{ name: 'Name', type: 'string', allowBlank: false },
{ name: 'Enabled', type: 'bool', allowBlank: false },
{ name: 'CurrentRoleCode', type: 'string', allowBlank: false}]
),
writer: new Ext.data.JsonWriter(
{
encode: false,
writeAllFields: true,
listful: true
})
});
This is bound to a grid, and I am able to load and save users without issue. The save button looks like this:
var saveButton = new Ext.Button({
text: 'Save',
disabled: true,
handler: function() {
userDataStore.save();
pageState.ClearDirty();
saveButton.disable();
}
});
However, when creating a new user, the JSON POST for the user is posted to the same REST service end point as "Update", with the only difference being that no ID value is posted (as one is only set in the store when loading from the server).
This works, and I am able to create users.
The save REST service emits back the created row with the new database ID, and I was under the assumption that EXTJS would automatically bind the new generated database ID to the row. This allows the user to further edit that row, and cause an update instead of a insert.
Instead, the row continues to have a blank user ID, so an additional save creates another new user.
So either:
EXTJS is supposed to resolve generated row ID's automatically and I am just doing something wrong.
I am supposed to manually reload the grid after each save with an additional REST call.
I've been looking at EXTJS documentation and forums, but I am unclear on the proper approach.
Can someone clarify?
EDIT: I tried returning Success = True in JSON to match the SuccessProperty, however this still didn't seem to work.
EDIT #2: So far the only thing I've found that works is doing "userDataStore.reload()" after saving, however because I was returning the contents of the store back after saving, I was hoping that EXTJS would understand that and update the row values.
I've got an idea that may help you. Let't suppose that user added a new
record in grid, in that moment add a new property newRecOrderNo to the record to
identify the record after response. When user will post data to server after
inserting you must get a new ID and associate it to newRecOrderNo
(like Map<Integer,Integer>). Then return json object like that :
{
success : true,
newIdes : {
1 : 23,
2 : 34
}
}
Then when you get response do set proper IDs to records:
userDataStore.each(function(rec){
if(rec.data.newRecOrderNo){
rec.data.ID = response.newIdes[rec.data.newRecOrderNo];
delete rec.data.newRedOrderNo;
}
})
})
Yes, it sets id (and also other fields, if server returns modified values of them), if create ajax backend returns record with set id, at least in extjs 4.1. You should return inserted record, with id set, under 'root' key as json dictionary, in this example root is 'Data', i.e.:
{
"Data": {
"ID": 8932,
"NT_ID": 28738273,
...
"CurrentRoleCode": "aaa",
},
"success": true
}
You need reload store with new params in savebtn handler
like
store.reload();
of course you can add more params to load action