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
Related
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.
I'm trying to page a data set in a WPF application.
My service method looks like this:
public ObservableCollection<MyModel> GetPageOfModels(int projectId, int numSkip, int numResults)
{
this.db.Set<MyModel>()
.Where(x => x.Project.Id == projectId)
.OrderBy(x => x.Name)
.Skip(numSkip)
.Take(numResults)
.Load();
return this.db.Set<MyModel>().Local;
}
This works great the first time it is hit. But when I move to a different page, Load() is adding the next page to the set, rather than replacing it.
If I recreate the data context before each call to GetPageOfModels it works, but I need to keep the same data context. Is there any way to achieve this?
If you must keep the context then you could detach everything before getting the next page.
foreach(var entity in db.Set<MyModel>().Local.ToList())
db.Entry(entity).State = EntityState.Detached;
When I use the Backbone.Collection.where function to filter the collection I get an array of models as return value but not an other filtered collection object. So I can't use other collection functions with that.
What is the purpose of such behavior?
where isn't the only method that returns an Array. where returns a new Array because you definitely don't want it mutating your existing Collection automatically. Also, many times you may want the result in Array form.
For whatever reason, the BB devs decided that it was better to return a new Array rather than a new Collection. One thought could be that, perhaps the returned data would be used in a different type of Collection. Another reason could be so that you always know what is returned from one of these methods. 2+ types of collections will ALWAYS return Arrays from these types of methods rather than having to try and inspect via instanceof or something else that isn't very reliable.
Edit
In addition, you COULD make your collections behave in a manner where you return new Collections. Create a base Collection to do something like this:
// Override the following methods
var override = ["where","find",...];
var collectionProto = Backbone.Collection.prototype;
BaseCollection = Backbone.Collection.extend({});
for (var key in collectionProto) {
if (collectionProto.hasOwnProperty(key) && override.indexOf(key) > -1) {
BaseCollection.prototype[key] = function () {
return new this.constructor(collectionProto[key].apply(this, arguments);
};
}
}
Instead over extending off Backbone.Collection, extend off BaseCollection.
Note that you can still use most of the underscore utilities on arrays. Here's how to use each() after a filter()
_.each( MyCollection.filter( filter_fn() {} ), each_fn() {} )
I would like to be able to call save() on a backbone model and have the backend return the entire collection of this model instead of only the changed attributes of the model. I would then like backbone to update the entire returned collection. The use case for this is the following:
A user has multiple addresses and can choose a shipping address from this collection. If she chooses a different shipping address from the collection the previous shipping address should be updated to the state of 'just another plain address'. For this the entire collection has to be updated instead of only the changed model.
Is this somehow possible in backbone.js?
Thanks a lot in advance!
models that are bound to collections, contain their collection parent as a property. Also, since your returning a list of models, we can assume that it is always in a list.
mymodel = Backbone.Model.extend({
parse: function (data) {
if(this.collection && typeof(data) === 'array') {
this.collection.reset(data);
}
return data;
}
});
I do not think that overriding sync or breaking the expectations of what save returns is necessary here.
It would be simpler I guess to override save on the model, something on the lines of:
save: function (key, value, options) {
var p = Model.prototype.save.call(this, key, value, options),
self=this;
if (this.collection) {
p.done(function () { self.collection.fetch(); });
}
return p;
}
which will save using the normal save obtaining its promise, and then if saving was successful and the model is part of a collection, it will fetch the collection from the server.
Another way would be to bind to the model's change event, check if it belongs to a collection and fetch, but that would also happen on set.
Yep, it's possible. You'll need to override the sync function on the model
MyModel = Backbone.Model.extend({
sync: function(method, model) {
if (method === 'create') {
//perform save logic and update the model's collection
} else if (method === 'update') {
//perform save logic and update the model's collection
} else if (method === 'read') {
...
} else if (method === 'destroy') {
...
}
}
});
Take a look at the Backbone.sync function for more information.
What your use case actually calls for is the updating of two models, not the updating of an entire collection. (Other than fetching, collections don't interact with the server in Backbone.) Assuming you have addresses A, B, and C, with A as the current shipping address and C as the new shipping address, your code can simply:
Update C to be the new shipping address.
Update A to be 'just another address.'
If your problem is that you don't know which address is the current address (i.e., address A), then you can just search inside your collection for it (i.e., "give me the current shipping address"), update C, and then update the returned address (address A).
If you absolutely need to update an entire collection (you don't), cycle through collection.models and update each one individually. There simply is no concept in Backbone of a collection being acted upon RESTfully.
Edit: I may have misread your question. If you meant update the collection on the client (i.e., you didn't intend to update the collection on the server), then the code is still similar, you just update both the old model and the new one.
I make this async webrequest call mutliple times(can be twice or thrice or even 6 times depending on conditions)
var request = HttpWebRequest.CreateHttp(url);
var observableRequest = Observable.FromAsyncPattern<WebResponse>(
request.BeginGetResponse, request.EndGetResponse);
Observable.Timeout(observableRequest.Invoke(), TimeSpan.FromSeconds(120)).
Subscribe(response => { HandleListResult(response); },
exception => { HandleListResultTimeOut(exception); });
I have a Collection (List) in the ViewModel which has a Binding to a LisBox and i would like to keep adding to the collection after each response returns.
What is the best practice to make this happen using Reactive Extensions ? Would be great if someone can show me some sample code !
Thanks in advance
You can translate url flow directly into streams:
public static IObservable<Stream> RequestToStream(
this IObservable<string> source, TimeSpan timeout)
{
return
from wc in source.Select(WebRequest.Create)
from s in Observable
.FromAsyncPattern<WebResponse>(wc.BeginGetResponse,
wc.EndGetResponse)()
.Timeout(timeout, Observable.Empty<WebResponse>())
.Catch(Observable.Empty<WebResponse>())
select s.GetResponseStream();
}
And then you need to observe your responces on UI, you need to use .ObserveOnDispatcher(), f.e.:
Observable
.Return("www.msdn.com")
.RequestToStream(TimeSpan.FromSeconds(1))
.ObserveOnDispatcher()
.Subscribe(request => UpdateUI(Request));
In ReactiveUI, this is done via CreateCollection()
IObservable<string> source; // Maybe this is a Subject<string> or whatever
myBoundCollection = source
.SelectMany(webServiceCall) // This is your FromAsyncPattern func
.CreateCollection(); // Pipe the Observable to a Collection
ReactiveUI handles all of the ObserveOn stuff to make sure it's synchronized and on the right threads, etc. This call returns immediately with an Empty list, then as results come in, the collection gets populated.