Angular: afterCellEdit without scope - javascript

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

Related

Calling a SAPUI5 control from a view works, embedded in a XMLComposite control does not (global external library)

I am trying to build a amCharts Container control, a UI5 control with a graph built via amCharts. Generally speaking it works but I am sure you can spot a lot of hacks that could be done better.
My biggest problem at the moment is that when I use the control from within another XMLComposite control I get the error
Uncaught ReferenceError: am4core is not defined at f.onAfterRendering
(AmChartContainer.js?eval:38)
Debugging the code proofs that the jQuery.sap.includeScript is executed before, still the global element am4core is not available. When I embed the control directly into the view, it works though.
The other thing I am not happy with is the event order. Would love to instantiate the amchart inside the render method but at that point in time there is no htmlelement, which the amchart instantiation needs. Agreed?
Would appreciate any pointers!
sap.ui.define([ 'sap/ui/core/Control', "jquery.sap.global" ], function(Control, jQuery) {
return Control.extend("io.rtdi.amChartContainer", {
metadata: {
properties: {
width: {
type: "sap.ui.core.CSSSize",
defaultValue: "100%"
},
height: {
type: "sap.ui.core.CSSSize",
defaultValue: "100%"
},
plugin: {
type: "string"
}
},
aggregations : {},
},
renderer : function(oRm, oControl) {
oRm.write("<div");
oRm.write(" style=\"width: " + oControl.getWidth() +
"; height: " + oControl.getHeight() + ";\" ");
oRm.writeControlData(oControl);
oRm.write(">");
oRm.write("</div>");
},
onBeforeRendering : function() {
jQuery.sap.includeScript("https://www.amcharts.com/lib/4/core.js",
"amCharts.core", null, null);
jQuery.sap.includeScript("https://www.amcharts.com/lib/4/charts.js",
"amCharts.charts", null, null);
if (!!this.getPlugin()) {
jQuery.sap.includeScript(
"https://www.amcharts.com/lib/4/" + this.getPlugin(),
"amCharts.plugin", null, null);
}
},
onAfterRendering : function() {
// if I need to do any post render actions, it will happen here
if (sap.ui.core.Control.prototype.onAfterRendering) {
sap.ui.core.Control.prototype.onAfterRendering.apply(this, arguments);
}
this._chart = am4core.create(this.getId(), am4plugins_forceDirected.ForceDirectedTree);
},
getChart: function() {
return this._chart;
}
});
});
First of all, please use sap/ui/dom/includeScript since jQuery.sap.includeScript is considered deprecated.
You do not have any fnLoadCallback defined. Are you 100% sure, that the scripts are already loaded and am4core is defined, when onAfterRendering is called?
It seems the global keyword at the beginning was the missing piece:
/* global am4core:true */
/* global am4charts:true */
sap.ui.define([
"sap/ui/core/mvc/Controller",...

Object property becomes null after chrome.storage.sync.set

I'm building a chrome extension and have encountered a bug I cannot wrap my head around. The problem is a single object property that becomes null in chromes' storage.
I'm testing this by doing:
console.log("pre-storage", settings);
var obj = {};
obj[storage_key] = settings;
chrome.storage.sync.set(obj, function() {
chrome.storage.sync.get(storage_key, function(data) {
console.log("post-storage", data[storage_key]);
});
});
This is the output:
pre-storage, Object {
...
soundClip: Object {
options: Array[5],
selected: Object {
label: "Soft2",
value: "snd/soft2.wav"
}
}
}
post-storage, Object {
...
soundClip: Object {
options: Array[5],
selected: null
}
}
Storing JSON.parse(JSON.stringify(obj)) instead of obj directly seems to fix this. Anyone have any ideas what might cause this? Any help is appreciated!
Edit: Making a deep copy of obj does not fix it.
Edit2: I should expand on how settings.soundClip is set. I'm using Angular (1.x) and I'm using a custom select directive. The stripped down directive looks like this:
function mySelect() {
return {
restrict: "E",
templateUrl: "mySelect.html",
scope: {
options: "=",
selected: "="
},
link: function (scope) {
scope.select = function (item) {
scope.selected = item;
};
}
}
}
Directive template view (mySelect.html):
<div>
<div ng-repeat="item in options track by $index"
ng-click="select(item)">
</div>
</div>
The properties are then two-way bound like this:
<my-select selected="settings.soundClip.selected"
options="settings.soundClip.options">
</my-select >
Since calling JSON.parse(JSON.stringify(obj)) seems to fix it, my guess is that you're having a problem with encoding your settings object with a variable instead of a string. See the answer here which might help.
Is it possible that the total quota (or per item) is being hit? Consider displaying the runtime.lastError on the set callback to see if there are any error messages.
chrome.storage.sync.set(obj, function() {
console.log('Error', runtime.lastError);
chrome.storage.sync.get(storage_key, function(data) {
console.log("post-storage", data[storage_key]);
});
});
See the limits here chrome.storage.sync.set

knockout.js ReferenceError - Unable to parse bindings $data is not defined

I am writing Jasmine test that is using mocked database response from WebSQL database. In following code segment I am getting an error.
function createCalculatedField(calculatedValue, objectContext) {
var computedObservable = ko.computed({
read: function () {
return ko.unwrap(ko.bindingProvider.instance.parseBindingsString("text: " + calculatedValue, objectContext).text);
},
write: function (value) {
computedObservable.notifySubscribers(value);
},
owner: objectContext
});
error message I am getting is following:
ReferenceError: Unable to parse bindings. Bindings value: text: ko.unwrap(PagingStartIndex) + $context().length Message: $data is not defined
I have printed out function inputs, and reproduced the error into Chrome console in screenshot below.
upon inspection of knockout-3.0.0.custom.min.js parseBindingsString method on which it fails.
function (b,c,d,e){try{var f=this.bindingCache,h=b+(e&&e.valueAccessors||""),g;if(!(g=f[h])){var n,k="with($context){with($data||{}){return{"+a.expressionRewriting.preProcessBindings(b,e)+"}}}";n=new Function("$context","$element",k);g=f[h]=n}return g(c,d)}catch(p){throw p.message="Unable to parse bindings.\nBindings value: "+
b+"\nMessage: "+p.message,p;}}
I can see that $data is internal knockout.js parameter. This same code works just fine in production environment, so I am assuming I am not setting something somewhere, could you point me in direction on how to debug this issue, cause I am totally out of ideas at this point.
Internally, Knockout uses the following dynamic function to evaluate your expression
function($context, $element) {
with($context) {
with($data||{}) {
return {text: ko.unwrap(PagingStartIndex) + $context().length};
}
}
}
If you look at the object that is normally passed into the parseBindingsString by knockout it looks like...
{
$data: {...},
$index: ko.observable(),
$parent: {...},
$parentContext: ko.bindingContext,
$parents: [...],
$root: {...}
}
This object graph is normally created by invoking new ko.bindingContext(...) or, if you are within a custom binding, bindingContext.createChildContext(...)
Looking at your screenshot it looks like objectContext is an observableArray containing 2 elements and I'm also assuming you manually created the objectContext instance in your jasmine tests.
Therefore the object you are passing into the parseBindingString ( which comes into the dynamic function as $context ) does not have a $data field, that is the reason the exception is thrown.
The should have the object graph similar to the normal bindingContext object where the value of $data is your model
i.e.
{
$data: ko.observableArray: ( [
{
Fields:{...},
Insert: false,
SetFields:[]
}, {
Fields:{},
Insert: false,
SetFields: []
}] ),
$index: ko.observable(),
...
}
However, you will still get an error message in this instance as PagingStartIndex is missing from $data object, the same as if you got a mismatch between your binding expression and the model in your production system.

How to get a Custom ExtJS Component to render some html based on a bound value

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.

Set listener for store events in a controller

I have a controller with a store, a model, and some views.
I need to listen for the beforesync and write event of the store in the controller, but I don't know how to set these listeners in the controllers control-function.
My store looks like this :
Ext.define('DT.store.UsersStore', {
extend : 'Ext.data.Store',
model : 'DT.model.User',
id : 'myStore'
autoSync : true,
proxy : {
type : 'ajax',
api : {
read : '/load_entries',
update : '/update_entry'
},
reader : {
type : 'json',
root : 'user',
successProperty : 'success'
}
}
});
Now I try to listen to the events in my controller :
...
init : function () {
this.control({
'myStore' : {
beforesync : this.doSomething,
write : this.doSomethingElse
}
});
},
...
My expected result is that the functions will be executed, when the events are fired.
But at this time nothing happens when they are fired.
How can I get this to work?
Your way is possible but it's not ideal, IMO. The better way is to use controllers's store getter. In your case the code would be something like this:
init : function () {
// every controller has getters for its stores.
// For store UsersStore getter would be getUsersStoreStore()
this.getUsersStoreStore().addListener('write',this.finishedLoading, this);
this.control({
// widgets event handlers
});
},
Here is an alternative syntax to Molecular Man's answer.
Instead of writing,
init : function () {
this.getUsersStoreStore().addListener('write',this.finishedLoading, this);
this.control({
// widgets event handlers
});
},
You can write
init : function () {
this.getUsersStoreStore().on({
write: this.finishedLoading,
scope: this
});
this.control({
// widgets event handlers
});
},
I think this alternative definition reads a little bit better.
I took this from an answer Izhaki gave me.
As for Extjs 4.2.1, your initial way of accessing the store listener would actually work, if you were using the 'storeId' instead of the id and the 'listen' function instead of 'control':
Ext.define('DT.store.UsersStore', {
extend : 'Ext.data.Store',
model : 'DT.model.User',
storeId : 'myStore'
....
init : function () {
this.listen({
store: {
'#myStore' : {
beforesync : this.doSomething,
...
Ext.define('Store', {
model: 'Model',
extend: 'Ext.data.Store',
listeners: {
'beforesync': function(){
App.getController('somecontroller').onBeforeSync();
}
}
});
App - your application object
The function onBeforeSync you can implement it in the controller ... this is the only way i could assign the event to the store and still implement the logic in the controll. I hope it helps
I solved it by myself.
I added the listener manually in the render-event of my Panel
Ext.getCmp('userPanel').down('gridpanel').getStore().addListener('write',this.finishedLoading, this);
Thank you for the help #nscrob.
Hope this addition will help someone out there to avoid spending some hours on debugging the library core:
Related to this practice of accessing Ext.data.Store instance inside Controller using the controller's getter method: e.g. for the "DT.store.UsersStore" above using this.getUsersStoreStore():
pay attention that if the store is already associated in a view(e.g. was declared as the store property for a "UsersGrid" Grid.panel.Panel widget definition) then this getter method will retrieve in fact another instance of the same Store class and not the instance used by the widget!
The reason is that adding the store in the constructor configuration object like this:
stores: ['UsersStore']
will in fact add a new store instance in Ext.data.StoreManager.map hash so - supposing that 'UsersStore' is the only Store object instantiated so far - the map keys now look like:
0: "ext-empty-store"
1: "UsersStore"
2: "myStore"
Now imagine you want to read some new data using your store'proxy and display this new data in the "UsersGrid" and you want to do all these when user clicks on something, so inside controller you will have a handler method for the user event with the code:
'user-selector' : {
click: function(){
var oStoreReference = this.getUsersStoreStore();
oStoreReference.load( {params:{} });
}
}
That call to get the reference will be translated internally in this.getStore('UsersStore') and will return a reference to the controller generated - 1: "UsersStore" - and not - 2: "myStore" - as one might expected. Furthermore, the load() call will load the UsersStore instance with the new Models and this will not be reflected in your grid view(because the grid is bound and listens to the events generated by "myStore" store instance).
So better access the store by its itemId using the general getStore method: this.getStore('storeItemId')
Why not just relay the store's events? For example:
this.getUsersGrid().relayEvents(this.getUsersStoreStore(), ['write'], 'store')
And then be able to
this.control('somegrid-selector': {storeWrite: function(){...}})
Just in case somebody stumbels over this.
ExtJS 7.3
Ext.define('DT.controller.UsersStore', {
extend: 'Ext.app.Controller',
listen: {
store: {
UsersStore: {
beforesync : 'doSomething',
write : 'doSomethingElse'
}
}
},
doSomething() {
console.log(arguments)
},
doSomethingElse() {
console.log(arguments)
}
});

Categories

Resources