We are using extjs 3.2.1 JavaScript Framework. In one of the grid panel have an action menu on top. Based on the value retrieved from the database, I need to show or hide the menu. I can use the hidden property of the menu for that purpose.
The problem is the function I use to retrieve the value from database runs asynchronously and takes time to retrieve the value and by the time it returns the menu is already initialized. Here are both methods.
employeeProfile: function(profileType) {
CR.Controllers.Employee.GetProfile(function(result) {
if (CR.CRError.ParseResult(result)) {
switch (profileType) {
case "IsStandardAllowed":
return result.IsStandardAllowed === 1 ? true : false;
case "IsUploadAllowed":
return result.IsUploadAllowed === 1 ? true : false;
case "IsCopyAllowed":
return result.IsCopyAllowed === 1 ? true : false;
default:
return true;
}
}
return true;
}, this);
},
getMenuActions:
function() {
return [
// add button
new CR.EmployeePanelAction({
text: this.lang_newFromStandard,
tooltip: this.lang_tipNewFromStandard,
handler: this.onNewFromTemplate,
hidden: this.EmployeeProfile("IsStandardAllowed")
scope: this
}),
new CR.EmployeePanelAction({
text: this.lang_newFromUpload,
tooltip: this.lang_tipNewFromUpload,
handler: this.onNewFromUpload,
hidden: this.employeeProfile("IsUploadAllowed"),
scope: this
}),
new CR.EmployeePanelAction({
text: this.lang_newFromCopy,
tooltip: this.lang_tipNewFromCopy,
handler: this.onNewFromCopy,
hidden: this.employeeProfile("IsCopyAllowed"),
scope: this
})
];
},
Ext.Button.hide(), Available since: 1.1.0
Should be working for you, unless there is a breaking bug in 3.2.1.
The problem is I didn't understand JavaScript Callback and Scope. I passed the callback method to the database retrieval method along with scope and it is working fine. Here is the method that I use to retrieve successfully.
isImageActionAllowed: function(setImageActions, scope) {
MVC.Controllers.Users.GetUserProfile(function(result) {
if(MVC.Error.ParseResult(result)) {
setImageActions.call(scope, result);
}
}, this);
},
Related
I'm trying to implement the afterCellEdit function inside my gridOption.onREgisterApi function. I'm not using $scope in my program as is recommended to do in the guidelines.
In fact, my question is exactly the same as this one : question
Sadly it is not answered.
When I use null as one of the answer suggest I got an error.
Here is the code :
vm.gridOptions.onRegisterApi = function(gridApi){
vm.gridApi = gridApi;
vm.gridApi.edit.on.afterCellEdit(null,function(rowEntity, colDef, newValue, oldValue){
alert("afterCellEdit");
});
};
And here is my error :
typeError: Cannot read property '$on' of null
at Object.feature.on.(anonymous function) [as afterCellEdit]
Thanks !
Edit : for #SaurabhTiwari here is my $scope.gridData alternative
function onCustomerListComplete(data) {
vm.gridOptions.data = data;
}
function OnError(reason) {
$log.error(reason);
}
function activate() {
customerService.getCustomerList()
.then(onCustomerListComplete, OnError);
}
vm.gridOptions = {
enablePaginationControls: false,
useExternalSorting: true,
enableHorizontalScrollbar : uiGridConstants.scrollbars.NEVER,
enableVerticalScrollbar : uiGridConstants.scrollbars.WHEN_NEEDED,
columnDefs: [
// will be modified once the data model is set
{ field: 'id', name: '', cellTemplate: 'content/customerList/rowEditor/customerList.rowEditor.editButton.html', width: 34 },
{ name: 'customerNumber', },
{ name: 'customerName' },
{ name: 'city' },
{ name: 'creditLimit' },
{ name: 'postalCode' },
]
};
It might be late to provide some help here and you possibly have found a solution. But I would like to explain your condition using the below code.
function (scope, handler, _this) {
if ( scope !== null && typeof(scope.$on) === 'undefined' ){
gridUtil.logError('asked to listen on ' + featureName + '.on.' + eventName + ' but scope wasn\'t passed in the input parameters. It is legitimate to pass null, but you\'ve passed something else, so you probably forgot to provide scope rather than did it deliberately, not registering');
return;
}
var deregAngularOn = registerEventWithAngular(eventId, handler, self.grid, _this);
//track our listener so we can turn off and on
var listener = {handler: handler, dereg: deregAngularOn, eventId: eventId, scope: scope, _this:_this};
self.listeners.push(listener);
var removeListener = function(){
listener.dereg();
var index = self.listeners.indexOf(listener);
self.listeners.splice(index,1);
};
//destroy tracking when scope is destroyed
if (scope) {
scope.$on('$destroy', function() {
removeListener();
});
}
return removeListener;
}
This is the original code from the ui-grid Api. You can search for it in the library or from a proper scope in application you can just try doing:
$scope.gridApi.edit.on.afterCellEdit.toString()
So the point here is that this function explicitly requires a scope or a null. So You do need to pass a scope to this listener.
The point you mentioned in your question that you are not using $scope in your code is somewhat vague. There always is a scope associated if you have a controller. Angular is pretty much about scopes (atleast Angular 1 was).
What the referred guidelines says is that you should avoid heaping everything on your $scope. The guidelines also says you should use $scope only for listening and watching, which is exactly the case here
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!
Using ng-grid with server side sorting and paging. It works great, with one caveat: the initial rendering makes two calls to get data from my service.
I'm not sure how easy (or hard) this would be to replicate in a jsFiddle or plunker.
Here is my controller code:
function reportQueueController($scope, $location, reportDataService) {
function init() {
$scope.state = {};
}
$scope.setPagingData = function (data) {
$scope.reportQueueList = data.Data;
$scope.totalServerItems = data.TotalItems;
};
$scope.$watch('pagingOptions', function(newVal, oldVal) {
if (newVal === oldVal) return;
getPagedDataAsync();
}, true);
$scope.pagingOptions = {
pageSizes: [25, 50, 100, 'All'],
pageSize: 25,
currentPage: 1
};
$scope.$watch('gridOptions.ngGrid.config.sortInfo', function (newVal, oldVal) {
if (newVal === oldVal) return;
$scope.state.sortField = newVal.fields[0];
$scope.state.sortDirection = newVal.directions[0];
$scope.pagingOptions.currentPage = 1;
getPagedDataAsync();
}, true);
$scope.gridOptions = {
data: 'reportQueueList',
enablePaging: true,
enableRowSelection: false,
showFooter: true,
pagingOptions: $scope.pagingOptions,
totalServerItems: 'totalServerItems',
enableSorting: true,
useExternalSorting: true,
sortInfo: { fields: ['CustomerName'], directions: ['asc'] },
filterOptions: $scope.filterOptions,
columnDefs: [
{ field: 'CustomerName', displayName: 'Customer' },
{ field: 'ParentCustomerName', displayName: 'Parent' },
{ field: 'Name', displayName: 'Report Name' },
{ field: 'Emails', displayName: 'Email Recipients', cellTemplate: emailCellTemplate },
{ cellTemplate: editCellTemplate, width: '50px' }
]
};
function getPagedDataAsync() {
console.log('in get data'); //this get logged twice
reportDataService.getReportQueueList($scope.pagingOptions.pageSize, $scope.pagingOptions.currentPage, $scope.state.emailAddress, $scope.state.reportSearch, $scope.state.sortField, $scope.state.sortDirection).then(function(data) {
$scope.setPagingData(data);
});
};
init();
}
Since Angular is going to call your watch at least twice, maybe more due to dirty processing, per $digest cycle you could use debounce. This is similar to what windows event listeners sometimes do. underscore (http://underscorejs.org) and lo-dash (http://lodash.com) both offer a _.debounce() you can use right out of the box.
_.debounce() allows you to say that a function should run, at most, once every per the specified number of milliseconds- no matter how many times the function is actually called. So you might do something like:
var checkSortData = _.debounce(function(e) {
$scope.state.sortField = newVal.fields[0];
$scope.state.sortDirection = newVal.directions[0];
$scope.pagingOptions.currentPage = 1;
getPagedDataAsync();
}, 500); // Run no more than once every 500 milliseconds
As you'd imagine underscore uses $timeout to do this, so you could write your own debounce if you preferred.
Using debounce could help with performance/server load too by minimizing server calls.
But rather than paying the performance price of polling the server to see if it has updated you might also consider using something like http://socket.io. Then you wouldn't have to poll using a watch, you can just attach an event listener on the client side. Here's an article on using socket.io with Angular written by Brian Ford: http://www.html5rocks.com/en/tutorials/frameworks/angular-websockets/
Your code looks correct, check if you are using unobtrusive js file twice, like
jquery.validate.unobtrusive.js
jquery.validate.unobtrusive.min.js
Or the same file is adding twice.
I'm trying to get a custom extjs component to render either a green-check or red-x image, based on a true/false value being bound to it.
There's a couple of other controls that previous developers have written for rendering custom labels/custom buttons that I'm trying to base my control off but I'm not having much luck.
I'd like to be able to use it in a view as follows where "recordIsValid" is the name of the property in my model. (If I remove the xtype: it just renders as true/false)
{
"xtype": "booldisplayfield",
"name": "recordIsValid"
}
Here's what I have so far, but ExtJS is pretty foreign to me.
Ext.define('MyApp.view.ux.form.BoolDisplayField', {
extend: 'Ext.Component',
alias : 'widget.booldisplayfield',
renderTpl : '<img src="{value}" />',
autoEl: 'img',
config: {
value: ''
},
initComponent: function () {
var me = this;
me.callParent(arguments);
this.renderData = {
value: this.getValue()
};
},
getValue: function () {
return this.value;
},
setValue: function (v) {
if(v){
this.value = "/Images/booltrue.png";
}else{
this.value = "/Images/boolfalse.png";
}
return this;
}
});
I'd taken most of the above from a previous custom linkbutton implementation. I was assuming that setValue would be called when the model-value for recordIsValid is bound to the control. Then based on whether that was true or false, it would override setting the value property of the control with the correct image.
And then in the initComponent, it would set the renderData value by calling getValue and that this would be injected into the renderTpl string.
Any help would be greatly appreciated.
You should use the tpl option instead of the renderTpl one. The later is intended for rendering the component structure, rather that its content. This way, you'll be able to use the update method to update the component.
You also need to call initConfig in your component's constructor for the initial state to be applied.
Finally, I advice to use applyValue instead of setValue for semantical reasons, and to keep the boolean value for getValue/setValue.
Ext.define('MyApp.view.ux.form.BoolDisplayField', {
extend: 'Ext.Component',
alias : 'widget.booldisplayfield',
tpl: '<img src="{src}" />',
config: {
// I think you should keep the true value in there
// (in order for setValue/getValue to yield the expected
// result)
value: false
},
constructor: function(config) {
// will trigger applyValue
this.initConfig(config);
this.callParent(arguments);
},
// You can do this in setValue, but since you're using
// a config option (for value), it is semantically more
// appropriate to use applyValue. setValue & getValue
// will be generated anyway.
applyValue: function(v) {
if (v) {
this.update({
src: "/Images/booltrue.png"
});
}else{
this.update({
src: "/Images/boolfalse.png"
});
}
return v;
}
});
With that, you can set your value either at creation time, or later, using setValue.
// Initial value
var c = Ext.create('MyApp.view.ux.form.BoolDisplayField', {
renderTo: Ext.getBody()
,value: false
});
// ... that you can change later
c.setValue(true);
However, you won't be able to drop this component as it is in an Ext form and have it acting as a full fledged field. That is, its value won't be set, retrieved, etc. For that, you'll have to use the Ext.form.field.Field mixin. See this other question for an extended discussion on the subject.
I'm trying to build an edit column, but my routine isn't quite right for some reason. My value of "store" is not returning anything like I thought it would.
Any thoughts?
function editLinkRenderer(value, metadata, record, rowIndex, colIndex, store) {
if (store == V2020.ServiceStore)
return 'Edit';
else if (store == V2020.PriceStore)
return 'Edit';
else if (store == V2020.PromoStore)
return 'Edit';
return "Edit";
}
I'm using it in my gridpanel like so:
{ header: "Edit", width: 60, dataIndex: 'serviceID', sortable: false, renderer: editLinkRenderer },
You might consider using an ActionColumn. That way you can do this:
var items = [ ... ]; // existing items
if (store.constructEditColumn) {
items.push(store.constructEditColumn());
}
Where your constructEditColumn might look like this:
...
constructEditColumn: function() {
return {
xtype: 'actioncolumn',
items: {
text: 'Edit',
handler: function() {
// do stuff
},
scope: this
}
}
},
...
Barring that, I'd be suspicious of doing equality on the stores. Are the two params before store ints? Can you breakpoint and take a look at whether the record.store property is what you expect? Old version of Ext, perhaps, with a different signature to the renderer?
I appreciate you taking a look, but I figured out the issue.
I had two V2020.ServiceStore defined by mistake and the latter one was mucking everything up.