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.
Related
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);
Hopefully I'm describing this correctly. I'm making a windows store app and I have the following setup
WinJS.Namespace.define("Model",
{
WorkOrders: new WinJS.Binding.List(),
selectedWorkOrder: {}
});
WinJS.Namespace.define("ViewModel",
{
WorkOrders: Model.WorkOrders,
selectedWorkOrder: Model.selectedWorkOrder
})
When the page is loaded an ajax request populates a list of WorkOrders, after they're populated a user can select one, at which point Model.selectedWorkOrder is set to one of the objects in Model.WorkOrder.
I want ViewModel.selectedWorkOrder to reflect whatever Model.selectedWorkOrder is, but it seems to bind only to the originally empty object, how can I make it bind to that property (even if the object changes, like a pointer).
I'm doing something like this to set the selectedWorkOrder
Model.selectedWorkOrder = results[i];
Thanks!
Not sure if I understand your question correctly.
What you're looking for is some kind of event to get in ViewModel.selectedWorkOrder whatever Model.selectedWorkOrder has, right?
If that's the case you could try RxJS-WinJS (which I'm honestly not too familiar with) or you could just make ViewModel.selectedWorkOrder a method that gets and returns whatever Model.selectedWorkOrder has at any given time.
Something like this:
WinJS.Namespace.define("Model",
{
WorkOrders: new WinJS.Binding.List(),
selectedWorkOrder: {}
});
var _getWorkItem = function(){
return Model.selectedWorkItem;
}
WinJS.Namespace.define("ViewModel",
{
WorkOrders: Model.WorkOrders,
selectedWorkOrder: _getWorkItem
})
This was originally posted on discuss.emberjs.com. See:
http://discuss.emberjs.com/t/what-is-the-proper-use-of-store-filter-store-find-for-infinite-scrolling/3798/2
but that site seems to get worse and worse as far as quality of content these days so I'm hoping StackOverflow can rescue me.
Intent: Build a page in ember with ember-data implementing infinite scrolling.
Background Knowledge: Based on the emberjs.com api docs on ember-data, specifically the store.filter and store.find methods ( see: http://emberjs.com/api/data/classes/DS.Store.html#method_filter ) I should be able to set the model hook of a route to the promise of a store filter operation. The response of the promise should be a filtered record array which is a an array of items from the store filtered by a filter function which is suppose to be constantly updated whenever new items are pushed into the store. By combining this with the store.find method which will push items into the store, the filteredRecordArray should automatically update with the new items thus updating the model and resulting in new items showing on the page.
For instance, assume we have a Questions Route, Controller and a model of type Question.
App.QuestionsRoute = Ember.Route.extend({
model: function (urlParams) {
return this.get('store').filter('question', function (q) {
return true;
});
}
});
Then we have a controller with some method that will call store.find, this could be triggered by some event/action whether it be detecting scroll events or the user explicitly clicking to load more, regardless this method would be called to load more questions.
Example:
App.QuestionsController = Ember.ArrayController.extend({
...
loadMore: function (offset) {
return this.get('store').find('question', { skip: currentOffset});
}
...
});
And the template to render the items:
...
{{#each question in controller}}
{{question.title}}
{{/each}}
...
Notice, that with this method we do NOT have to add a function to the store.find promise which explicitly calls this.get('model').pushObjects(questions); In fact, trying to do that once you have already returned a filter record array to the model does not work. Either we manage the content of the model manually, or we let ember-data do the work and I would very much like to let Ember-data do the work.
This is is a very clean API; however, it does not seem to work they way I've written it. Based on the documentation I cannot see anything wrong.
Using the Ember-Inspector tool from chrome I can see that the new questions from the second find call are loaded into the store under the 'question' type but the page does not refresh until I change routes and come back. It seems like the is simply a problem with observers, which made me think that this would be a bug in Ember-Data, but I didn't want to jump to conclusions like that until I asked to see if I'm using Ember-Data as intended.
If someone doesn't know exactly what is wrong but knows how to use store.push/pushMany to recreate this scenario in a jsbin that would also help too. I'm just not familiar with how to use the lower level methods on the store.
Help is much appreciated.
I just made this pattern work for myself, but in the "traditional" way, i.e. without using store.filter().
I managed the "loadMore" part in the router itself :
actions: {
loadMore: function () {
var model = this.controller.get('model'), route = this;
if (!this.get('loading')) {
this.set('loading', true);
this.store.find('question', {offset: model.get('length')}).then(function (records) {
model.addObjects(records);
route.set('loading', false);
});
}
}
}
Since you already tried the traditional way (from what I see in your post on discuss), it seems that the key part is to use addObjects() instead of pushObjects() as you did.
For the records, here is the relevant part of my view to trigger the loadMore action:
didInsertElement: function() {
var controller = this.get('controller');
$(window).on('scroll', function() {
if ($(window).scrollTop() > $(document).height() - ($(window).height()*2)) {
controller.send('loadMore');
}
});
},
willDestroyElement: function() {
$(window).off('scroll');
}
I am now looking to move the loading property to the controller so that I get a nice loader for the user.
I have goggled a lot, but have not found a solution for my issue. The author of the widget references to the last answer of FAQ, but the FAQ does not have the answer or I cannot find it. I suppose it was updated since that time. Other fellows who faced the same issue and asked the same question just gone and did not provide any solution.
Anyway, in my case I have a table with button Pictures:
When a user clicks one of pictures button, modal dialog is shown. The user now can manage pictures for the chosen row. He can upload, delete pictures and so on. When the user opens the dialog for second row in the table he should see pictures for the second row only. It tells me that I have to clean the list of uploaded files every time user hits Pictures button to see the dialog. He will receive list of pictures which corresponds to chosen row from the server. Unfortunately, when I retrieve the list for the chosen row, the received files are added to the existing list.
Could you tell me how I can clean the list or reset the widget without removing files on the server side?
UPDATE I have used the following piece of code as a temporary solution.
jQuery.ajax({
url: "<YOUR URL HERE>",
dataType: 'json',
context: $('#fileupload')[0]
}).done(function (result) {
jQuery("#fileupload").find(".files").empty(); //this line solves the issue
jQuery(this).fileupload('option', 'done').call(this, null, { result: result });
});
Thank you.
i was also trying for one hour to get my upload work ;)
here is, how i solved this problem:
$('#html5FileInput').fileupload({
....
add: function (e, data) {
$.each(data.files, function (index, file) {
var newFileDiv = $(newfileDiv(file.name));
$('#fsUploadProgressHtml5').append(newFileDiv);
newFileDiv.find('a').bind('click', function (event) {
event.preventDefault();
var uploadFilesBox = $("#fsUploadProgressHtml5");
var remDiv = $(document.getElementById("fileDiv_" + event.data.filename));
removeFileFromArray(event.data.filename);
remDiv.remove();
data.files.length = 0;
...
});
data.context = newFileDiv;
});
...
)};
as you can see i create inside the add-event my file-dataset with 'newfileDiv(file.name)'. this creates a div with all information about the file (name, size, ...) and an ankor that exists for deleting the file from the list. on this ankor i bind a click-event in which i have the delete implementation.
hope this helps!
I know this isn't the most elegant solution, but I needed a very quick and dirty...so here's what I did (using jQuery).
//manually trigger the cancel button for all files...removes anything that isn't uploaded yet
$('.fileupload-buttonbar .cancel').first().trigger('click');
//check the checkbox that selects all files
if(!$('.fileupload-buttonbar .toggle').first().checked) {
$('.fileupload-buttonbar .toggle').first().trigger('click');
}
//manually trigger the delete button for all files
$('.fileupload-buttonbar .delete').first().trigger('click');
I know this isn't the best way. I know it isn't elegant...but it works for me and removes everything from the plugin.
If you have added file names or anything else from the plugin to any local arrays or objects, you'll need to clean those up manually (I have several handlers that fire on fileuploadadded, fileuploadsent, fileuploadcomplete, fileuploadfailed, and 'fileuploaddestroyed` events).
protected function get_file_objects($iteration_method = 'get_file_object') {
$upload_dir = $this->get_upload_path();
if (!is_dir($upload_dir)) {
return array();
}
return array_values(array_filter(array_map(
array($this, $iteration_method)
//scandir($upload_dir)
//File listing closed by Doss
)));
}
there is a scandir function responsible for listing the files. just uncomment it like i have done above and your problem is solved. it can be found in the UploadHandler.php file.
So my problem is this. I can remove a record from localstorage proxy just fine the first time. But if I do it again, it gives me an error, where everything in the Store is undefined, like it didnt exist anymore.
onTapRemoveKegelReminder: function(button) {
console.log(button.getData());
//Find and delete the button and the record
var store = Ext.getStore('KegelReminders');
store.load();
store.filter('button_id', button.getData());
var record = store.first();
console.log(record);
console.log(button.getData());
console.log('Remove count'+ store.getCount());
if (typeof record !== 'undefined'||record!=null ) {
store.remove(record);
store.sync();
console.log('removed record correctly')
this.trainingCount--;
var rmButton = this.getKegelExercises().down('#container-' + button.getData());
this.getKegelExercises().remove(rmButton);
}
But if I restart my application, and then remove again it works fine. I cant seems to remove more than once without having to restart the application.
FYI in case anyone else finds this, removing a record from a Store only removes it from that instance of the store, not from the storage mechanism (for example, localstorage). If you want to do that you have to use the erase method on the model object.
store.remove(record); // may not even be necessary
record.erase();
store.sync();