See fiddle here: https://fiddle.sencha.com/#fiddle/2iig&view/editor
The docs (https://docs.sencha.com/extjs/6.6.0/classic/Ext.ux.TreePicker.html#event-change) list 'change' in the events section but when I set the value or reset the field this event never fires. The 'select' event fires as expected but that only fires when the user selects a field.
EDIT:
Based on Snehal's suggestion below, I was able to accomplish this using the following override. Not sure if there is a simpler way to do it but this was the best I could manage:
Ext.define('MyApp.overrides.TreePicker', {
override: 'Ext.ux.TreePicker',
setValue: function (value) {
var me = this,
record;
me.value = value;
if (me.store.loading) {
// Called while the Store is loading. Ensure it is processed by the onLoad method.
return me;
}
// try to find a record in the store that matches the value
record = value ? me.store.getNodeById(value) : me.store.getRoot();
if (value === undefined) {
record = me.store.getRoot();
me.value = record.getId();
} else {
record = me.store.getNodeById(value);
}
// zeke - this is the only line I added to the original source
// without this the 'change' event is not fired
me.callSuper([value]);
// set the raw value to the record's display field if a record was found
me.setRawValue(record ? record.get(me.displayField) : '');
return me;
}
});
Because setValue function does not call this.callParent(). You can do something like this in setValue function.
setValue: function(value) {
var me = this,
record;
if (me.store.loading) {
// Called while the Store is loading. Ensure it is processed by the onLoad method.
return me;
}
// try to find a record in the store that matches the value
record = value ? me.store.getById(value) : me.store.getRoot();
me.callParent([record.get('valueField')]);
return me;
},
Related
You know how they say you don't need state management until you know you need it. Well turns out my project needs it. So I need some help wit best practice as I am adding ngxs to an existing angular project.
I have an action called getServiceDetail and my statemodel has a list of objects called DriverListsStopInfoViewModel. each of these objects have a unique ID. The html template of the consuming component uses a selector for the property currentStopDetail, which is a state property that gets set in my action.
GOAL:
in my action I want to check the list of objects in my store to see if an object with the same id exists and return that object, and if it does not exist, call and api to get it.
EXAMPLE:
The following code works, but I would like to hear if this is the right way to do it. do I even need to return the object from the action function if its found, or can I just use patch state to assign it to the currentStopDetail
export interface SignServiceStateModel {
searchResults: ServiceSearchModel[];
driverStopsDetails: DriverListsStopInfoViewModel[];
driverStopsList: DriverListsStopsViewModel[];
driverStopsMarkers: DriverStopsMarkerViewModel[];
currentStopDetail: DriverListsStopInfoViewModel;
}
const SIGNSERVICE_STATE_TOKEN = new StateToken<SignServiceStateModel>(
'signservice'
);
#State<SignServiceStateModel>({
name: SIGNSERVICE_STATE_TOKEN,
defaults: {
searchResults: [],
driverStopsDetails: [],
driverStopsList: [],
driverStopsMarkers: [],
currentStopDetail: null
},
})
#Injectable()
export class SignServiceState {
constructor(private driverListsService: DriverListsService) {}
#Action(DriverList.GetServiceDetail)
getServiceDetail(
ctx: StateContext<SignServiceStateModel>,
action: DriverList.GetServiceDetail
) {
if (action.serviceId === undefined || action.serviceId <= 0) {
return;
}
// check if record already in list and return
const currentState = ctx.getState();
const existingStopDetail = currentState.driverStopsDetails.find(s => s.SignServiceId === action.serviceId);
if (existingStopDetail !== undefined) {
const currentStopDetail = existingStopDetail;
ctx.patchState({ currentStopDetail });
return currentStopDetail;
}
// else get new record, add it to list and return
return this.driverListsService.getDriverListsInfo(action.serviceId).pipe(
tap((currentStopDetail) => {
ctx.patchState({ currentStopDetail });
ctx.setState(
patch({
driverStopsDetails: append([currentStopDetail])
})
);
})
);
}
#Selector()
static currentStopDetail(state: SignServiceStateModel) {
return state.currentStopDetail;
}
}
I only included the relevant code from my state class
QUESTION:
is this the best way to check the store for an item and call api if it does not exist?
Thanks in advance
Short answer is yes, what you have done here is a typical way of handling this scenario (in my experience). There's a couple of improvements you could make:
do I even need to return the object from the action function if its found, or can I just use patch state to assign it to the currentStopDetail
No, you don't return anything from these action handlers, other than possibly an Observable that NGXS will handle (so in your case if there is no matching item found, you return the Observable that fetchs it from the API and patches the state).
Also when you do make the API call, you should only need a single update to the state:
return this.driverListsService.getDriverListsInfo(action.serviceId).pipe(
tap((result) => {
ctx.setState(
patch({
currentStopDetails: result
driverStopsDetails: append([result]),
})
);
})
);
Element binding snippet
var oModel = oView.getModel();
var oPromiseMetadataLoaded = oModel.metadataLoaded();
oPromiseMetadataLoaded.then(function() {
var sObjectPath = oModel.createKey("Project", {
ProjectID: sProjectId
});
oView.bindElement("/" + sObjectPath);
// <HERE>
});
Now I want to execute a function (marked with '// ' where it should go) which uses data from the bound Object. When the data is not there yet (the model is obviously an OData model), I need to attach to the dataReceived event, but when when the data is already there, this event won't fire.
What is the most (UI5) idiomatic way to execute code in both cases? Is there a Promise like oModel.metadataLoaded()? Do I need to consider something, e.g. to probably not read data from an object previously bound to the view?
Maybe you can attach to the change-Event?
oView.bindElement({
path: "/" + sObjectPath,
events : {
change: this._onBindingChange.bind(this),
dataRequested: function (oEvent) {
oView.setBusy(true);
},
dataReceived: function (oEvent) {
oView.setBusy(false);
}
}
});
_onBindingChange : function (oEvent) {
if (this.getView().getBindingContext()) {
//HERE
}
else { //Invalid Binding Context };
}
I am not able to display records on my report.
Report Source: Group Approval(sysapproval_group) table
Condition:Sys Id - is one of - javascript: new GetMyGroupApprovals().getSysIds();
Script Include : MyGroupApproval
Note : Active is checked, Accesible is all application score & Client callable unchecked
var GetMyGroupApprovals = Class.create();
GetMyGroupApprovals.prototype = {
initialize: function() {
},
getSysIds : function getMyGroupMembers(){
var ga = new GlideRecord('sysapproval_group');
ga.addQuery('parent.sys_class_name', '=', 'change_request');
ga.query();
gs.log("TotalRecords1 Before:: " + ga.getRowCount());
var sysIdArray = [];
while(ga.next()){
sysIdArray.push(ga.sys_id);
}
return sysIdArray;
},
type: 'GetMyGroupApprovals'
};
Kindly note that I have to achieve with script approach. I am not able to get records on my report.
This line is probably causing unexpected behavior:
sysIdArray.push(ga.sys_id);
ga.sys_id returns a GlideElement object, which changes for each of the iterations in the GlideRecord, so the contents of sysIdArray will just be an instance of the same object for each row in the result set, but the value will just be the last row in the set.
You need to make sure you push a string to the array by using one of the following methods:
sysIdArray.push(ga.sys_id+''); // implicitly call toString
sysIdArray.push(ga.getValue('sys_id')); // return string value
Quick suggestion, you can use the following to get sys_ids as well:
sysIdArray.push(ga.getUniqueValue());
This is my current object which i need to update:
[
{ id: q1,
answers:[
{ id: a1,
answered: false
},
...
]
},
...
]
I can't figure out how to update this object and set for example answered = true.
Is there any better way saving this kind of object? I tried to use the update addon from React but can't get it to work properly.
You can update the answers list this way, in your reducer:
function update(state, action) {
// assuming you are passing an id of the item to be updated via action.itemId
let obj = state.whatever_list.filter(item => item.id === action.itemId)[0]
//assuming you are passing an id of the answer to be updated via action.answerId
//also, assuming action.payload contains {answered: true}
let answers = obj.answers.map(answer => answer.id === action.answerId ?
Object.assign({}, answer, action.payload) : answer)
obj = Object.assign({}, obj, {answers: answers})
return {
whatever_list: state.whatever_list.map(item => item.id == action.itemId? Object.assign({}, item, obj) : item)
}
}
Here is what your action might look like:
function updateAnswer(itemId, answerId, payload) {
return {
type: UPDATE_ANSWER,
itemId: itemId,
answerId: answerId,
payload: payload
}
}
In your react component class, assuming there is an event handler for monitoring whether if a question is answered:
export default class Whatever extends React.Component {
...
// assuming your props contains itemId and answerId
handleAnswered = (e) => {
this.props.dispatch(updateAnswer(this.props.itemId, this.props.answerId, {answered: true}))
}
...
}
So basically what happens is this:
Your event handler calls the action and pass the updated data to it
When your action is called, it returns the updated data along with a type parameter
When your reducer sees the type parameter, the corresponding handler will be triggered (the first piece of the code above)
The reducer will pull out the existing data from the list, replace the old data with the new one, and then return a list containing the new data
You can create a sub-reducer for the answers key. Look at this example:
https://github.com/rackt/redux/blob/master/examples/async/reducers/index.js
You could use dot-prop-immutable and an update would be as simple as:
return dotProp.set(state, 'quiz.0.answers.0.answered', true);
I use the multiselect from the Kendo UI.
I want to know if there is any way to trigger a function, when the user deletes an item from the multiselect.
So far I know that the 'change' event is triggered, but it is too generic and I can't find any info on what the user removed. Or is there?
What about define change as:
change : function (e) {
var previous = this._savedOld;
var current = this.value();
var diff = [];
if (previous) {
diff = $(previous).not(current).get();
}
this._savedOld = current.slice(0);
// diff has the elements removed do whatever you want...
}
What I do is save previous values on _savedOld and then compute the difference with current using jQuery.not. It's important to note the use of slice for cloning previous list of values, if we save current then we are actually saving a reference to current list and since it is a reference next time we try to use we get again current value.
EDIT: In order to save the values set during the initialization, you can do:
dataBound : function (e) {
saveCurrent(this);
},
change : function (e) {
var previous = this._savedOld;
var current = this.value();
var diff = $(previous).not(current).get();
saveCurrent(this);
// diff has the elements removed do whatever you want...
}
where saveCurrent is a function defined as:
function saveCurrent(multi) {
multi._savedOld = multi.value().slice(0);
}