Why Is Vue Data Element Changing When I Only Set It Once? - vuejs2

I have created a component that allows users to select objects from a list and put them into another "selected list" lets say. So there are 100 items in the main list and however many in the users selected list. They can of course remove and add items as they want.
There are two buttons at the bottom of the modal. Cancel and Update. Where Cancel forgets anything they have added or removed (essentially an Undo of when they popped up the modal) and Update technically does nothing because when you are adding and removing, I am actually updating the real selected list.
So my data has these two properties (among others of course):
originalSelections: [],
selectedMedications: [],
There is a method that only gets called one time to set the selectedMedications property to whatever the current state of originalSelections is. I have a console.log to prove I am not running this method more than once and it NEVER gets hit again.
console.log('Post Initing');
let Selected = [];
for (let med of this.value.OtherNotFromEpic) {
let match = this.otherMedications.find(x => { return x.value === med });
if (match) {
Selected.push(JSON.parse(JSON.stringify(match)));
this.originalSelections.push(JSON.parse(JSON.stringify(match)));
this.selectedMedications.push(JSON.parse(JSON.stringify(match)));
}
}
I was baffled at why the selectedMedications was changing, so I added a watch to let me know if it was:
watch: {
originalSelections(newValue) {
console.log(' ** Original Selection Changed', this.init, newValue);
},
},
The Cancel method is as follows:
cancel() {
$('#' + this.modalID).modal('hide');
console.log('Cancel: this.originalSelections', JSON.parse(JSON.stringify(this.originalSelections)));
this.selectedMedications = this.originalSelections;
this.$nextTick(() => {
console.log(' Cancelled: this.selectedMedications', JSON.parse(JSON.stringify(this.selectedMedications)));
});
},
You can see that I am setting selectedMedications to whatever it was originally.
The odd thing is... This WORKS 100% the first time I bring up the modal. So I pop up the modal, remove an item, cancel the modal and it brings back the item I removed. Perfect!
If I bring the modal up again, remove that same item or any other item, cancel the modal and that item stays removed and the watch is actually hit. I have NO other place in the code where originalMedications = … or originalMedications.push or even originalMedications.slice ever happen. And it is my understand that the JSON.parse(JSON.stringify(match)) code I use to set it is making a new object and not a reference in any way.
Another odd thing I have found is that if I refresh the page, open the modal, cancel out without doing any adding or removing. Then I bring up the modal again and try either add or remove, then cancel the modal, those items do not revert back to the originalMedications state because originalMedications is the same as selectedMedications. Aargh!
So HOW can a property get altered when I never do anything to it after the initial setting of it?

In the cancel method, when below direct assignment is done here after both data is referring to same (since its array). So any time after the first cancel both originalSelections and selectedMedications array would have same reference and hence same data.
this.selectedMedications = this.originalSelections;
instead better to use concat as below? This will create a copy of originalSelections and assign that to selectedMedications. So any operations on selectedMedications will not affect the originalSelections.
this.selectedMedications = [].concat(this.originalSelections);

Related

Select Newly Added Row in Office UI Fabric DetailsList

I have a DetailsList for which I need to keep track of the current selection. My problem is I need to update my state when the user changes the selection, but also be able to set the selection when a new row is added. If I'm listening to user events via onSelectionChanged, I can't call setSelectionKey because it will just cycle back and forth.
Possibly related is that since I am adding a new row, I can't select it until the render that repopulates the items, but I'm not sure when I would even call setSelectedKey to ensure that's already done.
I have a version that kind of works by setting a flag to call setItems and then setSelectedKey while temporarily telling onSelectionChanged to ignore it, but I believe it's causing extra renders and doesn't work in all situations.
Thanks for any guidance.
I figured it out. The problem was I was just setting the previously selected item to selected, e.g. selection.setKeySelected(item.id, true). The secret turned out to be setting all of the other items to not be selected. Here are the relevant parts:
const selection = new Selection({
getKey: item => item.id,
onSelectionChanged: () => {
the_chosen_one = selection.getSelection()[0];
// ... do whatever with the item the user just selected ...
}
});
...
// In some handler when you need to select/re-select an item:
const onWhatever = () => {
setChangeEvents(false);
state.items.forEach(item => {
selection.setKeySelected(item.id, item.id === the_chosen_one.id);
});
setChangeEvents(true);
};
...
<DetailsList selection={selection} items={state.items} ...
I'm not sure the setChangeEvents part is necessary, but it didn't hurt. The documentation on this is non-existent so the only way to figure it out is reading the code and browsing through Github issues, but a lot of those turned out to be dead ends. I still don't understand why I have to explicitly de-select items that were never selected in the first place, but that's what it was.
I hope this helps someone else some day.

Checking canDrop() result

I have a type of dragSource, let's call it Card, and a dropTarget Stacks. There are many stacks, but each card can't be dropped to all of them. So in the Stack's canDrop method I get the Card being dragged, run some comparisons and return whether that card can be dropped.
So, all good until here. When a user tries dropping a Card to a wrong Stack, canDrop returns false and nothing happens.
My question now is, when the drop action fails, I want to display a message like "You can't drop this card here because of reasons". How do I do that using react-dnd? I can't use the result from canDrop, because the drop never happened so it isn't handled? I could move the canDrop logic to the function that moves the card to the desired Stack, but then react-dnd would only be specifying that a Card can be dropped on a Stack and all the logic for if a card can be dropped on a certain stack would be handled by my code.
Is this correct? Is there any other way of doing this using the library I'm not seeing? What would be a good approach for this?
You can access the status of the finished drag (whether successful or not) via the DragSourceMonitor. You will still need to get clever/dirty over how to pass different reasons, I'm pretty sure hover is called even if cant drop, so could cache a reason when you drag over an incompatible drop target, then clear it when drag is complete...
const reasons = '';
const dragSourceSpec = {
//...
endDrag: (props, monitor, component) => {
if (!monitor.didDrop()) {
console.warn('Drop could not be handled, because of: ', reasons)
} else {
console.info('Drop was handled, and returned: `, monitor.getDropResult())
}
// reset reason for next drag
reasons = ''
}
}
const dropTargetSpec = {
//...
hover: (props, monitor, component) => {
reasons = monitor.canDrop() ? '' : 'Stack is full'
}
}

Durandal - Correct way to disable .canDeactivate for 'Success' operations?

I have an edit page (in a DurandalJS single page app), where I use the .canDeactivate lifecycle method to check if there are any changes to the record, and optionally prompt them for confirmation before leaving the page.
I also have a 'Save' and 'View History' button. Is the correct thing to do to override the .canDeactivate method before calling router.navigate, to stop the modal popup invoking?
E.g.: As here:
self.onSave = function() {
self.repository.updateItem(self.model).done(function() {
self.canDeactivate = null; // Is this the correct way to do this?
router.navigate("#/home");
}
}
As this .canDeactivate will otherwise get called:
self.canDeactivate = function() {
if (!self.model.hasChanges()) {
return true;
}
return app.ShowMessage("Unsaved data will be lost", "Are you sure you wish to exit?", ["Yes", "No"]).done(function(result) {
return result !== "No";
}
};
Why dont you just set
self.model.hasChanges(false)
in your updateItem callback?
Then when your canDeactivate is called, it will return true.
Also you seem to have an error in your ShowMessage callback. I think you mean to do:
return result != "No";
I don't think the way Durandal decides whether to attempt to call a canDeactivate function is fully defined, other than the fact that if it's not in the view model, it won't try. Hence, even if it works as is, a future version of the framework could change its check to something like if (canDeactivate in viewModel) viewModel.canDeactivate(...); without further tests, and your code would break.
This is unlikely, but if you want to worry about it, you should thus delete self.canDeactivate instead of assigning it the null value.
Quote from the documentation:
To participate in the lifecycle, implement any (or none) of the
functions below on the object that you set the activator to (...)
Current implementation (activator.js, L126, 1eecbc2d3f84dc42eb7304bde761d88f300d8951):
if (item && item.canDeactivate) {
So it only checks if it's truthy (which would indicate using null works fine currently, too).
If you want to discuss the pattern, I don't see anything wrong with it, as long as it makes sense to you and everyone who should read the code.
You're not supposed to be activating and deactivating views programmatically in any critical path, so performance should be irrelevant either way (flag on view model or deletion of canDeactivate).

extjs 4.1 how to reset the itemselector

I am using extjs 4.1.1a for developing some application.
I had a form consisting of two combo-boxes and an item-selector.
Based on the value selected in first combo-box , the itemselector will load its data from database. This is working fine.
My problem is, if i reselect the first combo-box the new data will be displayed in itemselector along with previous data displayed in itemseletor .That is previous data displayed in itemselector will remain there itself.
for example: name "test1" consists of ids 801,2088,5000. on selecting test1 in firstcombobox itemselector must show output as below.
and if "test2" consists of ids 6090,5040. on selecting test2 in firstcombobox itemselector must show output as below.
problem is. for first time if i select "test1" from firstcombobox , output will come as expected. if i reselect "test2" from firstcombobox , output will come as below.
as you can see, previous data displayed (marked in red rectagle) remains there itself with new data displayed (marked with green rectangle).
I want for every reselection of first combobox, previously displayed data in itemselector to be erased before printing new data on itemselector.
How can I reset the itemselector for every reselection of first combobox?
You should remove all items from the store of the itemselector by the removeAll command. After that you should load the store of the itemselector.
itemselector.store.removeAll();
itemselector.store.load();
Any solutions above solve my problem.
i found solution from Sencha Forum.
https://www.sencha.com/forum/showthread.php?142276-closed-Ext-JS-4.0.2a-itemselector-not-reloading-the-store
in the itemselector.js file, change the line marked below.
populateFromStore: function(store) {
var fromStore = this.fromField.store;
// Flag set when the fromStore has been loaded
this.fromStorePopulated = true;
// THIS LINE BELOW MUST BE CHANGED!!!!!!!!!!!!
fromStore.loadData(store.getRange()); //fromStore.add(store.getRange());
// setValue waits for the from Store to be loaded
fromStore.fireEvent('load', fromStore);
},
You need to insert...
this.reset();
at the head of the function that is inserting the data.
As an example...
Ext.override( Ext.ux.ItemSelector, {
setValue: function(val) {
this.reset();
if (!val) return;
val = val instanceof Array ? val : val.split(this.delimiter);
var rec, i, id;
for (i = 0; i < val.length; i++) {
var vf = this.fromMultiselect.valueField;
id = val[i];
idx = this.toMultiselect.view.store.findBy(function(record){
return record.data[vf] == id;
});
if (idx != -1) continue;
idx = this.fromMultiselect.view.store.findBy(function(record){
return record.data[vf] == id;
});
rec = this.fromMultiselect.view.store.getAt(idx);
if (rec) {
this.toMultiselect.view.store.add(rec);
this.fromMultiselect.view.store.remove(rec);
}
}
}
});
are u got it?
when u select that combobox frist stoe of item selector is null after store load with ur pass the para meters
for example
store.load(null),
store.proxey.url='jso.php?id='+combobox.getrawvalue(),
store.load();
like that so when ur select a value in ur combobox that time ur used a listeners to ur combobox in that listners ur used above code , select ur some value in combobox that time frist store is get null after ur pass some values to json.php then store load with responce so that time old data is remove and new data load in that store
if u post ur code i will give correct code
I ran into the same issue with ExtJS 4.2.1. I got it to work by calling reload() on the data store and then setValue() with an empty string on the item selector in the data store's reload() callback.
Ext.create("Ext.form.field.ComboBox", {
// Other properties removed for brevity
listeners: {
change: function(field, newValue, oldValue, eOpts) {
Ext.getStore("ExampleStore").reload({
callback: function() {
Ext.getCmp("ExampleItemSelector").setValue("");
}
});
}
}
});
Ext.create("Ext.data.Store", {
storeId: "ExampleStore",
// Other properties removed for brevity
});
Ext.create("Ext.form.FormPanel", {
// Other properties removed for brevity
items:[{
xtype: "itemselector",
id: "ExampleItemSelector",
// Other properties removed for brevity
}]
});
For any folks that are curious, I'm fairly convinced there's a bug in the item selector's populateFromStore() function. When the function is called, it blindly adds all of the values from the bound store (store) to the internal store (fromStore). I suspect there should be a call to fromStore.removeAll() prior to the call to fromStore.add(). Here's the relevant code from ItemSelector.js.
populateFromStore: function(store) {
var fromStore = this.fromField.store;
// Flag set when the fromStore has been loaded
this.fromStorePopulated = true;
fromStore.add(store.getRange());
// setValue waits for the from Store to be loaded
fromStore.fireEvent('load', fromStore);
},
EDIT 12/18/2013
If you've configured any callback events on the item selector (e.g. change), you may want to disable the events temporarily when you call setValue(""). For example:
var selector = Ext.getCmp("ExampleItemSelector");
selector.suspendEvents();
selector.setValue("");
selector.resumeEvents();
I had the same problem and finally I decided to modify the extjs source code, not considering it a big issue as extjs itself its saying in the start of the file
Note that this control will most likely remain as an example, and not as a core Ext form
control. However, the API will be changing in a future release and so should not yet be
treated as a final, stable API at this time.
Based on that, as jstricker guessed (and sadly I didn't read and took me a while to arrive to the same conclusion), adding fromStore.removeAll() before fromStore.add() solves the problem.
Outside of the problem (but I think it can be interesting as well), additionally, I also added listConfig: me.listConfig in the MultiSelect configuration (inside createList), that way it's possible to format each item additional options (such as images, etc.) setting in the 'itemselector' the option listConfig as it's explained in the (irrealistic) documentation.
Need to reset the store used in ItemSelector that can be done by setting Empty object like below. Also need to call clearValue() method of ItemSelector component.
store.setData({});
ItemSelectorComponent.clearValue();

Back button on first level of nestedlist

I have a nestedList with a few levels that appears when the user presses a button on the screen. When the nestedList appears, there is no back button (because we're at the top of the tree, so understandably there is nowhere to go back to), whereas tapping on items in the list takes you to screens with a back button.
I'd like to add a back button to the first screen. I have managed to do this, but not without adding the same back button to every sublist in the nestedList - this has the effect of 1 back button at the top level, and 2 back buttons (one to take you out of the nestledList completely, and one to take you up a level) at every subsequent level.
Can anyone help me figure out how to have 1 back button on each screen, including the top level to close the list?
Many thanks
PS a nasty workaround that I'm using at the moment is to have a "close" button in the top right of every screen instead.
I don't know how comfortable you are with the inner workings of Sencha Touch so how you go about doing this is up to you--
The back button is there, hidden, when the nested list is shown (created in the initComponent function with hidden: true), and then onBackTap, onItemTap and setActivePath will all call syncToolbar near the end of their functions which is where the back button is hidden when you are at a depth of 0.
So there are 2 places you need to do something about, first is initComponent which is easy-- just implement initComponent in your nestedList, call the superclass' initComponent and then set the backButton visible
var myNestedList = new Ext.NestedList({
...,
initComponent: function() {
myNestedList.superclass.initComponent.call(this);
this.backButton.setVisible(true);
},
...
});
That takes care of showing it intially.. how you care to deal with fixing syncToolbar is up to you. You can use Ext.override, you can straight up copy and paste the whole syncToolbar function into your nestedList object which would also override it or you could do what you're told never to do and just edit the sencha-touch.js file directly. However you decide to do it, what you're looking to change is
syncToolbar: function(card) {
...
backToggleMth = (depth !== 0) ? 'show' : 'hide';
if (backBtn) {
backBtn[backToggleMth]();
if (parentNode) {
backBtn.setText(backBtnText);
}
}
... };
You can either change backToggleMth to = 'show' or just delete the if (backBtn {...} all together.
In the Sencha Touch 2.2 I had to use a different code:
Ext.create('Ext.NestedList', {
...,
listeners: {
initialize: function(obj) {
obj.getBackButton().show();
},
back: function(obj, node, lastActiveList, detailCardActive) {
switch(node.getDepth()) {
case 1:
obj.getBackButton().show();
break;
case 0:
alert("wohooooooo!");
break;
}
}
}
}