Sort an array of objects based on another array of objects in angular 7 - arrays

I know it's been asked million+1 times. But i've found no help in those questions/answers.
I have 2 arrays of 2 different objects one string property is used to uniquely identify them. This would be the key to sort about, but said object prop names are not equal (accessValue, modifiedOption). But their values are!
Object1: { ... accessValue, ... };
Object2: { ..., modifiedOption, ... };
array1:Object1[];
array2:Object2[];
I'd like to sort array1 based on the object indencies of array2.
So all of array1 items'd be in the same order as array2.
These two arrays are used to model a connected dropdown selection system, which can be added to are removed from. The Addition is screwing me over (lastly added item is appended to the first place and not the last) probably because of filter below?
What I use to add new dropdowns:
addFieldRow() {
this.fieldRows.push(0); // since desired selection is not known yet but need to populate the array that represents the 1st selection so a 2nd main selection dropdown will appear on screen
...
}
public onSelect() {
// if a selection is happened check the values of editOptions (contains data about all main selectable options)
this.fieldRows = this.editOptions.filter(
option => this.selectedOptions.some(el => el.modifiedOption === option.accessValue)
);
this.disableSelected(); // disable already selected items (not related to my issue)
this.optionSelected = true; // this is just for button disabled toggle
}
So either i need to figure out my addRow logic (if it has any flaws) or implement a sorting utility to make sure that the objects of fieldRows are in the same order as selectedOptions' -> since this models the selection directly.
I cannot really add a stackblitz since it's hard to model my current state.

Okay I am a complete idiot!
Since I know the current index (since i am looping through fieldRows).
All I had to do is replace this:
public onSelect() {
this.fieldRows = this.editOptions.filter(
option => this.selectedOptions.some(el => el.modifiedOption === option.accessValue)
);
With this:
public onSelect(index) {
this.fieldRows[index] = this.editOptions.find(option => this.selectedOptions[index].modifiedOption === option.accessValue);
this.disableSelected();
this.optionSelected = true;
}
Now it works correctly.

Related

Showing the new row in react-table on the current page

I have been playing with ReactTable v7 for a while and have encountered the following problem: when the table is sorted and uses paginator sometimes adding (or editing) a row causes it to be outside the current page.
You can see the problem here:
https://codesandbox.io/s/github/tannerlinsley/react-table/tree/master/examples/material-UI-kitchen-sink
Sort the table by First Name
Press add
Enter a record with First Name "zzzzz"
The record is added but is currently hidden which confuses users.
Is there a "standard" way to fix the issue? Am I missing something?
In v6 I have done a workaround for it:
React.useEffect(() => {
if (editedElementId && reactTable) {
const { data } = reactTable.props;
if (data && data.length > 0) {
const internal = reactTable.getResolvedState();
let position = -1;
internal.sortedData.forEach((row, i) => {
if (row._original.id === editedElementId) position = i;
});
if (position >= 0) {
const pageNumber = Math.floor(position / pageSize);
setPage(pageNumber);
} else {
alert.info("Element not visible");
}
}
}
}, [editedElementId]);
...
<ReactTable
ref={(r) => {setReactTable(r);}}
...
But maybe there is a bulit-in way to achieve it?
There is not currently a way to only sort the elements which are currently being displayed, no.
React Table v7's useSortBy hook sorts the entirety of the input data array, so sorting by First Name descending (A->Z) naturally places 'ZZZZZZ' at the end of the list, which will be hidden due to pagination. The v7 way of doing it would probably be similar to what you are doing already, using the exposed properties from the useTable hook instead of reactTable.resolvedState() and etc. Another way to do it would be to write your own custom sortBy method and only sort the items [0...n] where n is the number of currently displayed items.
As a side note, since the autoResetSortBy property is true by default in the example you linked, the step of sorting on First Name is irrelevant -- since the function which adds a new user to the list mutates the data array, the sorting method is reset. That function appends the new user to the end of the list, so it will always be on a new page, even if the "Rows per page" option is set to "All". Both issues can be fixed by setting autoResetSortBy to false, and changing the pageSize in addUserHandler.

In angular Pushing objects in array getting duplicated

EventEmitter in service
toshoppinglist = new EventEmitter<Ingredients[]>()
Emitting method
toshoppinglist() {
this.slservice.toshoppinglist.emit(this.item.ingredients);
}
ingredients : Ingredient []
Subscribing to emit and pushing emitted values
this.slservice.toshoppinglist.subscribe(
(ingredients: Ingredients[]) => {
for (let item of ingredients) {
this.ingredients.push(item);
}
}
)
Now, when pushing new values into the array,it's getting duplicated.It's work fine for first pushing,but getting duplicated after that.
Ok, first in my opinion you use wrongly EventEmitter. Eventemitters used only inside DUMB components to raise an event to Smart component. And not inside services. Second, yes it will be duplicated. Imagine you have button which will raise this eventemitter and we emit the same ingredient every time. Inside the subscribe you didnt check that new ingredient are different. And because you use list, it doesnt care if you have duplicates. So a solution is to add to the subscribe a check that it will push only - new- ingredients.
You need to add a check in your subscription function.
currentIngredients: Ingredients[];
this.slservice.toshoppinglist.subscribe(
(ingredients: Ingredients[]) => {
> //here you need the if condition comparing it to a current
> ingredients array to see if it exists or not
for(let item of ingredients) {
if(this.currentIngredients.includes(item) {
// in this case you should increment the count of ingredient
currentIngredients[item].count++;
}
else {
// this should only add non-existing items ingredients list
this.currentIngredients.push(item)
}
}
}

How to keep "empty" selection on my combobox list in JavaFx?

In my application I hava combobox which is holding some values from databse ( some real time updated list ). This ComboBox list is updated every 1 minute.
List dosen't have null values. When I'm setting this list to ComboBox..
ComboBox box = new ComboBox(items);
.. there is one extra "empty" row, which is perfectly fine because non value is selected.
Right after I'm selecting some value my "empty" value disappears from the list.
My main question is How to keep this value on the list?
Why this is a problem..
Scenerio values is selected in database, first application start
List is loaded ( wiht selected empty value ).
Value is selected.
During first background refresh, empty values disappears, and combobox value is selected to n+1, next value is selected.
If I want to select empty values I have to all clearSelection from selection model / by some extra control.
While it is an old question, I spent quite bit of time trying to solve the same issue in my application and thought i might as well add my solution here.
One possible workaround is to create an extra list that contains null and the items you wish to be selectable.
ObservableList<String> selectableActivities = FXCollections.observableArrayList("a", "b", "c");
ObservableList<String> selectableActivitiesWithNull = FXCollections.observableArrayList();;
selectableActivitiesWithNull.add(null);
selectableActivitiesWithNull.addAll(selectableActivities);
In order to support updates of the original list you would need a ListChangeListener that updates the extra list according to changes in the original list.
selectableActivities.addListener((ListChangeListener<String>)(change -> {
while (change.next()) {
if (change.wasPermutated()) {
selectableActivitiesWithNull.sort((a, b) -> {
return Integer.compare(selectableActivities.indexOf(a), selectableActivities.indexOf(b));
});
} else if (change.wasRemoved()) {
selectableActivitiesWithNull.remove(change.getFrom()+1, change.getTo()+2);
} else if (change.wasAdded()) {
selectableActivitiesWithNull.addAll(change.getFrom()+1, selectableActivities.subList(change.getFrom(), change.getTo()));
} else if (change.wasUpdated()) {
for (int i = change.getFrom(); i < change.getTo(); ++i) {
selectableActivitiesWithNull.set(i+1, selectableActivities.get(i));
}
}
}
}));
And finally you use the extra list for the ComboBox items.
ComboBox<String> combobox = new ComboBox<String>();
combobox.setItems(selectableActivitiesWithNull);
Now you can modify the original list as usual and the ComboBox will update accordingly while also having an empty selection as the first item. And most importantly your original list will not be polluted by placeholder objects that could cause issues in other parts of the application.
This will also work with other objects, assuming that you add an apropriate StringConverter to the ComboBox. Note that the converter must also be able to handle null values if using the above approach.
StringConverter<Object> activityConverter = new StringConverter<Object>() {
#Override
public String toString(Object object) {
return object != null ? object.toString() : "";
}
#Override
public ActivityDataRow fromString(String string) {
return null;
}
};
combobox.setConverter(activityConverter);
While this approach is not exactly what you desired, I believe this is a close you can get without implementing a custom combobox.

Ordering an observable collection with Reactiveui

I am having some difficulty with ordering an observable collection in my ViewModel.
Here is my situation:
In my view model, I have the following list:
public List<TicketModel> Tickets
{
get { return _Tickets.Value; }
set
{
{
this.RaiseAndSetIfChanged(c => c.Tickets, value);
}
}
}
private ObservableAsPropertyHelper<List<TicketModel>> _Tickets;
This list is populated using a ReactiveAsyncCommand:
LoadTickets.RegisterAsyncFunction(x => loadTickets())
.ToProperty(this, x => x.Tickets);
All works so far.
I have another command, SortByCommand which gets called whenever the user wants to sort a collection. The command looks like this:
SortByCommand = new ReactiveCommand(this.WhenAny(c => c.Tickets, ((tickets) => tickets.Value != null && tickets.Value.Count > 0)));
SortByCommand.Subscribe(c => sortTickets((SortByModel)c));
The command also calls a function that orders the collection using an order by clause:
private void sortTickets(SortByModel model)
{
Tickets = Tickets.OrderBy(model.Selector).ToList();
}
Whenever the sortTickets function is called, there is an exception thrown which says:
Unable to cast object of type 'ReactiveUI.ObservableAsPropertyHelper`1[System.Collections.Generic.List`1[Bugmine.Modules.MyPage.Models.TicketModel]]' to type 'System.Collections.Generic.List`1[Bugmine.Modules.MyPage.Models.TicketModel]'.
I have several questions:
1) Why can't I set directly the Tickets model? Do I need to first convert the result of the OrderBy to some sort of observable collection?
2) Is there a better way of doing this?
EDIT: Clarification
The approach I am taking right now is:
The Tickets collection gets reset every x seconds.
As soon as the sortTickets function is called, I will sort and reset this collection by:
Tickets = Tickets.OrderBy(c => c.Name).ToList(); //for example
When the Tickets collection is loaded again, I will check if it should be sorted and sort it before setting the Tickets property.
This feels a bit hacky because I am basically setting the collection at two points - upon loading and upon sorting. Moreover, upon loading I am using the ReactiveUI helper -> ToProperty:
LoadTickets.RegisterAsyncFunction(x => loadTickets())
.ToProperty(this, x => x.Tickets);
Whereas, upon sorting I am doing that myself:
Tickets = Tickets.OrderBy(model.Selector).ToList();
I am wondering if there is a better way to do the sorting using the ReactiveUI approach which I already use upon loading.
Thanks in advance!
Another way to solve this is via CreateDerivedCollection:
SortedTickets = Tickets.CreateDerivedCollection(
x => new TicketViewModel(x),
orderer: (l,r) => SortModel.Selector(l, r), // Returns CompareTo() result
signalReset: this.WhenAny(x => x.SortModel, x => x.Value)); // Reorder on SortModel change
Note that this breaks down if Tickets is set repeatedly (which in this case it is) - you might change your model to initializing Tickets in the ctor, then Clearing and Adding all the items, i.e.
LoadTickets.RegisterAsyncFunction(x => loadTickets())
.Subscribe(x => {
// TODO: Make sure Tickets is a ReactiveCollection
Tickets.Clear();
Tickets.AddRange(x); // Will trigger resorting of SortedTickets
});
Just had a look on Ana's blog http://blog.paulbetts.org/index.php/2010/07/05/reactivexaml-series-implementing-search-with-observableaspropertyhelper/
//
// This is the canonical way to make a read-only property whose value
// is backed by an IObservable
//
ObservableAsPropertyHelper<List<FlickrPhoto>> _Photos;
I think the read-only is the important point there.
Instead you could try using a normal observable where you can use OnNext to push in the new value
private Observable<List<TicketModel>> _Tickets = new Observable<Lis<TicketModel>>();
_Tickets.OnNext(newValue);
Or use a ObservableForProperty<> and just use the property normally
public List<TicketModel> _Tickets { get;set;}
private Observable<List<TicketModel>> _ticketsObservable= ObservableForProperty<..>(x=>x.Tickets);
Both of these methods expose an Observable which we can use later in the sort.
Why not try handling the two inputs into your sort in the same way, then it wont feel so hacky. That way you'll also have an
public SortModel SortModel {get;set;}
Your sort command implementation becomes
SortByCommand.Subscribe(c => _Sort = c));
but then you subsribe to both ticket changes AND sort criteria changes in one, see http://rxwiki.wikidot.com/101samples#toc44 for CombineLatest
new ObservableForProperty<..>(x=>x.SortModel)
.CombineLatest(_ticketsObservable)
.Subscribe( (x,y)=>
{
//Refactor to SortMethod
_tickets = y.OrderBy(x.Selector);
});
I'm pretty sure that the result of OrderBy(..) is an IEnumerable instead of an ObservableCollection. Fortunately though, it has a constructor that can do the conversion, i.e.
Tickets = new ObservableCollection<...>(Tickets.OrderBy(...));
"Better" can be very subjective measure. Firstly you haven't explained the full lifespan of the Tickets collection and the tickets it contains, so we can't really tell.
You might consider using a SortedTickets field/property instead of overwriting your tickets property which you might find you wouldn't need to be an observable collection as you would know when it needs to NotfyProperryChanged from the Tickets observable. It all depends on how frequently your tickets is likely to change.
P.S. Also make sure you have tested what happens to your sorted list when you add another ticket as you can't just

backbone routes - ordering and filtering

In my Backbone app, on my collection I have numerous sorting methods, when rendering the views based on the collection I am currently using a global var set via the route (I do it with a global as other actions add to the collection and I want the last ordering to be used). For example
routes : {
"" : "index",
'/ordering/:order' : 'ordering'
},
ordering : function(theorder) {
ordering = theorder;
listView.render();
},
then in my view
if (typeof ordering === 'undefined') {
d = this.collection.ordered();
}
else if(ordering == 'owners') {
d = this.collection.owners();
}
_.each(d, function(model){
model.set({request : self.model.toJSON()});
var view = new TB_BB.OfferItemView({model : model});
els.push(view.render().el);
});
Where ordered and owners are the 2 ordering methods.
So my first question is, based on routes could someone advice a better way of implementing above? This view gets rendered in multiple places hence me using a global rather than passing a ordered var to the method?
Second question is - I would like to also add some filtering, so lets say I want to sort by 'price' but also do some filtering (lets say by multiple categories id). How could I add a flexible 'route' to deal with filtering.
I guess I could do
routes : {
"" : "index",
'/ordering/:order/:filter1/:filter2' : 'ordering'
},
So the filter1 and filter2 would be the subsequent filtering, but if the filters could be 0 or 100 this will not work. Could anyone offer a solution?
Well, first you should be using Backbone's built-in ability to auto-sort collections. You can take advantage of this by defining a comparator function on your collection. This gives you all kinds of wins right out of the box — for example, the collection will re-sort itself every time you add or remove something from it, based on your comparator. If you want to define multiple sort functions, just define them all as functions and then update comparator when you need to. Then you can ditch that ugly global var.
For your second question, I'm not totally sure what you mean by "if the filters could be 0 or 100 this will not work." If you mean that you'll run into trouble if you don't specifiy all of the filters, then that's true. But you can use a wildcard to fix that. Here's what that might look like:
// your routes look like this:
routes : {
'/ordering/:order/filters/*filters' : 'ordering' // your routes will look like: /ordering/price/filters/filter_one/filter_two/filter_three
},
ordering: function (order, filters) {
filters = filters.split('/'); // creates an array of filters: ['filter_one', 'filter_two', 'filter_three']
listView.render(filters); // pass your filters to the view
}
// listView.render() looks like this:
render: function(filters) {
collection = this.collection;
_.each(filters, function (filter) {
collection = collection.filter(function () {
// your actual filtering code based on what the filter is
});
});
}

Resources