In my viewModel i bind the user-property to userDataSource.at(0):
vm.set('userDs',userDataSource);
userDataSource.fetch(function(){
vm.set('user', userDataSource.at(0));
});
This works fine as long as I just use the user data. But it doesn't work when i try to update the data.
vm.user.set('DisplayName','John Doe');
vm.userDs.sync();
This will not work. The problem is that vm.user now is updated, but the first element of vm.userDs is not changed. It seems like user is not a pointer to the first element in the userDs but rather a copy of the first element.
It works if I do it like this:
vm.userDs.at(0).set('DisplayName','John Doe');
vm.userDs.sync();
But that's a pretty ugly workaround. I would like the vm.user to be a pointer to the element in the userDs.
Turns out the reason why this wasn't working was because I changed the filter of the dataSource after defining it.
userDataSource = {
...
}
userDataSource.filter({/*filter config*/});
Moving the filter inside the userDataSource definition solved the problem:
userDataSource = {
...
filter: {/*filter config*/}
}
I still don't completely understand why this makes such a huge difference, but at least I know how to work around it.
Related
I'm having trouble figuring out how to accomplish what seems to be a pretty standard pattern when doing a RANGE_ADD mutation.
Say on page load I pull in and render a connection chatmessages with first: 10 pagination. I now do a AddMessageMutation which does a prepend to the that same connection. Since the connection is paginated by first: 10 the last item of the connection is now gone to give room for my new edge and is thus removed from rendering. I can of course add +1 to first on the onSuccess of the mutation, but this often leaves a weird flickering effect of removing and reinserting the edge at the end.
This problem seem to get even more difficult if I want to do an optimistic update to the connection since there is no onOptimistic callback.
Since this seems like a pretty common pattern I figured I'd ask if I'm approaching this the wrong way.
Referenced in issue:
https://github.com/facebook/relay/issues/384
I think that the problem is that you're incrementing the count in the onSuccess handler (ie. after the server has responded) when what you want to do is to increment it in tandem with the optimistic mutation (ie. right away).
Try this:
_handleMessageCreated() {
Relay.Store.update(new AddMessageMutation(
{/* ... */},
{onFailure: () => this._handleMessageCreationRollback()}
);
// Optimistically increment the count
this.props.relay.setVariables({
numMessagesToShow: this.props.relay.variables.numMessagesToShow + 1,
});
}
_handleMessageCreationRollback() {
this.props.relay.setVariables({
numMessagesToShow: this.props.relay.variables.numMessagesToShow - 1,
});
}
See also: https://github.com/facebook/relay/issues/135#issuecomment-134400856
I know how to make the cache cleared for view :
.state('app.list', {
cache : false,
url: "/lists/:listId",
views: {
'menuContent': {
templateUrl: "templates/listDashboard.html",
controller: 'listDashboardCtrl'
}
}
})
, but I need something else - delete all the cache for the app in controller method. how to do it?
I found a solution, Wrap the clearCache and ClearHistory in a $timeout. Something Like this.
$scope.logout = function(){
$location.path('/signin')
$timeout(function () {
$ionicHistory.clearCache();
$ionicHistory.clearHistory();
$log.debug('clearing cache')
},300)
}
Edit:Changed Timeout seconds
You can use $ionicHistory. From documentation:
clearCache()
Removes all cached views within every ionNavView. This both removes the view element from the DOM, and destroy it's scope.
In your listDashboardCtrl write this:
function listDashboardCtrl($scope, $ionicHistory){
$ionicHistory.clearCache();
}
Well this is an old issue, but for anyone that's coming 2017 or later I will explain what really happens and how to solve it:
The code of $ionicHistory.clearCache():
clearCache: function(stateIds) { return $timeout(function() {
$ionicNavViewDelegate._instances.forEach(function(instance) {
instance.clearCache(stateIds); }); }); }
So, as you can see, it takes 1 parameter cllaed stateIds which is an array of stateId. Indeed i struggled to find out that stateId is nothing more than stateName.
So, let's go deeper. The code of $ionicNavView.clearCache which is used in the line above "instance.clearCache(stateIds)" is:
self.clearCache = function(stateIds) {
var viewElements = $element.children();
var viewElement, viewScope, x, l, y, eleIdentifier;
for (x = 0, l = viewElements.length; x < l; x++) {
viewElement = viewElements.eq(x);
if (stateIds) {
eleIdentifier = viewElement.data(DATA_ELE_IDENTIFIER);
for (y = 0; y < stateIds.length; y++) {
if (eleIdentifier === stateIds[y]) {
$ionicViewSwitcher.destroyViewEle(viewElement);
}
}
continue;
}
if (navViewAttr(viewElement) == VIEW_STATUS_CACHED) {
$ionicViewSwitcher.destroyViewEle(viewElement);
} else if (navViewAttr(viewElement) == VIEW_STATUS_ACTIVE) {
viewScope = viewElement.scope();
viewScope && viewScope.$broadcast('$ionicView.clearCache');
}
}
};
And as you can see in the code, this clearCache DOES NOT CLEAR ALL CACHES, instead, it destroy all cached views that matches a value in the stateIds array. If there's no parameter IT JUST DESTROY THE ACTUAL VIEW.
So the solution for this, using just the Ionic way is to call $ionicHistory.clearCache() with all your state names in an array as parameter.
E.g:
$ionicHistory.clearCache(['login', 'map', 'home']);
I cannot belive any Ionic developer didnt dug into the code before, or missed this simple datail.
I Hope someone takes advantage of this, even being so late.
UPDATE: Just to make it crystal clear, i want to point out where the bug itself is (if we can call it bug), maybe can be handy for devs:
self.clearCache = function(stateIds){
[...]
var viewElements = $element.children();
}
What the whole function does is basically:
Get all elements using JQLite
Loop the elements
Check if an element equals one in the StateIds array and destroy it; go to next element.
Check if element in the loop is cached or active, and in both true cases destroy it
I wont dig deeper into this but debugging it i could see that the elements gotten from var viewElements = $element.children(); is not an array of all your views content, not even the cached ones, intentionally or not it does not loop through out all your states to clear all those that matches 'ACTIVE' or 'CACHED'. If you want it to loop through ALL your states and destroy all cached views and data you need to explicity pass the stateIds array parameter.
Besides there's another strange behavior, because when i was debugging it i saw when the var viewElements array was filled up with 2 elements, and these 2 elements were from the same state, one resolved to 'CACHED' another resolver to 'ACTIVE', even resolving to the 2 types used in the if conditions the cache was not cleared at all.
I personally think that this is somekind wrong implemented or is wide used wrongly. The fact is that there's a lot of people cracking their heads on this and devs don't even give this simple explanation.
I'm would like to know the recommended way to bind to ReactiveCommand's IsExecuting.
The problem is the initial command execution (started at the end of the constructor) is not updating the WPF control using IsLoading as a binding, although subsequent calls work as expected.
Update 2 Add test binding code
This shows the adorner content when IsLoading is true
<ac:AdornedControl IsAdornerVisible="{Binding IsLoading}">
<ac:AdornedControl.AdornerContent>
<controls1:LoadingAdornerContent/>
</ac:AdornedControl.AdornerContent>
<fluent:ComboBox
ItemsSource="{Binding Content, Mode=OneWay}"
DisplayMemberPath="Name"
SelectedValuePath="ContentId"
SelectedValue="{Binding SelectedContentId}"
IsSynchronizedWithCurrentItem="True"
/>
</ac:AdornedControl>
Update
I found this:
https://github.com/reactiveui/rxui-design-guidelines
and figured I should be able to do something like:
this._isLoading = this.WhenAnyValue(x => x.LoadCommand.IsExecuting)
.ToProperty(this, x => x.IsLoading);
but it gives the compilation error:
The type arguments for method
'ReactiveUI.OAPHCreationHelperMixin.ToProperty<
TObj,TRet>(System.IObservable< TRet>, TObj,
System.Linq.Expressions.Expression< System.Func< TObj,TRet>>, TRet,
System.Reactive.Concurrency.IScheduler)' cannot be inferred from the
usage. Try specifying the type arguments explicitly.
I also tried:
this._isLoading = this.WhenAnyValue(x => x.LoadCommand.IsExecuting)
.ToProperty<TheViewModel, bool>(this, x => x.IsLoading);
but get the compilation error:
'System.IObservable< System.IObservable< bool >>' does not contain a
definition for 'ToProperty' and the best extension method overload
'ReactiveUI.OAPHCreationHelperMixin.ToProperty<
TObj,TRet>(System.IObservable< TRet>, TObj,
System.Linq.Expressions.Expression< System.Func< TObj,TRet>>, TRet,
System.Reactive.Concurrency.IScheduler)' has some invalid arguments
and
Instance argument: cannot convert from
'System.IObservable>' to
'System.IObservable'
Original Below
The code listed at the end of my post works for the initial bind by accessing the IsLoading property and it sounds like that kicks off a subscription. But from further reading it seems I should be using WhenAny and I can't seem to figure out what has been put in front of my nose:
ToProperty and BindTo - Get initial value without Subscribing
Adding:
this.WhenAnyValue(x => x.LoadCommand.IsExecuting);
also works, but is there a better way?
I was thinking removing the ObservableAsPropertyHelper as it doesn't seem to be doing much for me and making IsLoading a normal property like:
private bool _isLoading;
public bool IsLoading
{
get { return _isLoading; }
set { this.RaiseAndSetIfChanged(ref _isLoading, value); }
}
And doing something like the following, but it doesn't compile because it is trying to assign a IObservable< bool> to a bool:
this.WhenAnyValue(x => x.LoadCommand.IsExecuting)
.Subscribe(x => IsLoading = x);
Current code:
private readonly ObservableAsPropertyHelper<bool> _isLoading;
public bool IsLoading
{
get { return _isLoading.Value; }
}
LoadCommand = ReactiveCommand.CreateAsyncTask(async _ =>
{
//go do command stuff like fetch data from a database
}
LoadCommand.IsExecuting.ToProperty(this, x => x.IsLoading, out _isLoading);
//works if I have this line
var startSubscription = IsLoading;
LoadCommand.ExecuteAsyncTask();
and figured I should be able to do something like:
You've got the right idea, but the syntax is a bit off, try:
this.LoadCommand.IsExecuting
.ToProperty(this, x => x.IsLoading, out _isLoading);
If you were to do this with objects that can change (i.e. you've got a long expression), there's a special method called WhenAnyObservable that you use instead of WhenAnyValue:
this.WhenAnyObservable(x => x.SomeObjectThatMightBeReplaced.IsExecuting)
.ToProperty(this, x => x.IsLoading, out _isLoading);
I have run into this before and I think what you are experiencing lies here.
ToProperty / OAPH changes
ObservableAsPropertyHelper no longer is itself an IObservable, use WhenAny to observe it.
ObservableAsPropertyHelper now lazily Subscribes to the source only when the Value is read for the first time. This significantly improves performance and memory usage, but at the cost of some "Why doesn't my test work??" confusion. If you find that your ToProperty "isn't working", this may be why.
It is lazy, so you must subscribe to it (i.e. request a value from the property if using OAPH) for it to work. That is why you notice that your var startSubscription = IsLoading; 'fixes' the issue.
Knowing that made it easier for me to determine whether or not this was even an issue, or just something to keep in mind during my unit tests, knowing that in my application these properties would be bound to and hence subscribed to, making it moot in practice. You know, the whole "tree falling in the forest with no one there to hear it" idea.
I think you should stick with the ToProperty that you have, that seems the way to go IMHO.
I am trying to figure why my ajax $each alters the way my list of names gets printed?
I have an json string like this:
[{"name":"Adam","len":1,"cid":2},{"name":"Bo","len":1,"cid":1},{"name":"Bob","len":1,"cid":3},{"name":"Chris","len":1,"cid":7},{"name":"James","len":1,"cid":5},{"name":"Michael","len":1,"cid":6},{"name":"Nick","len":1,"cid":4},{"name":"OJ","len":1,"cid":8}]
Here all the names are sorted in alphabetic order, but when getting them out they are sorted by "cid"? Why, and how can I change this?
Here is my jQuery:
var names = {};
$.getJSON('http://mypage.com/json/names.php', function(data){
$.each(data.courses, function (k, vali) {
names[vali.cid] = vali.name;
});
});
I guess its because "names[vali.cid]", but I need that part to stay that way. Can it still be done?
Hoping for help and thanks in advance :-.)
Ordering inside an object is not really defined or predictable when you iterate over it. I would suggest sorting the array based on an internal property:
var names = [];
$.getJSON('http://mypage.com/json/names.php', function(data){
$.each(data.courses, function (k, vali) {
names.push({name: vali.name, cid: vali.cid});
});
names.sort(function(a, b) {
return a.name.localeCompare(b.name);
});
});
Now you have an array that is ordered and you can iterate over it in a predictable order as well.
There is no "ajax $each" - you probably mean the jQuery function.
With "when getting them out" I presume you mean something like console.debug(names) after your $each call
Objects aren't ordered in javascript per definition, so there is no more order in your variable "names". Still, most javascript implementations today (and all the ones probably important to you - the ones used in the most used browsers) employ a stable order in objects which normally depends on the order you insert stuff.
All this said, there can probably be 3 reasons you're not getting what you're expecting:
Try console.debug(data) and see what you get - the order as you want it?
As you don't explicitly state how you debug your stuff, the problem could be in the way you output and not the data is stored. Here too try console.debug(names).
You're using a function which dereferences on expection, like console.*. This means if you console.debug() an object, the displayed values will depend on the moment you unfold the displayed tree in your browser, not when the line was called!
I am tring to assign a value to ViewBag in the controller for later usage in the View, It complaines with the following error.
Assigning the value in the Controller like this.
ViewBag["isAdmin"]=true;
Error:
Cannot apply indexing with [] to an expression of type 'System.Dynamic.DynamicObject'
Does anyone had this before?
All you need is ViewBag.isAdmin = true. the you can access is with
if(ViewBag.isAdmin)
{
//do stuff
}
As a follow-up, the idea behind ViewBag (and ViewData) is that you can store off key-value pairs of stuff and conveniently access them over in the View.
With ViewData, you reference these things like so:
ViewData["SomeKey"] = someObject;
If you want to do the same using the ViewBag instead (which provides a wrapping around that ViewData dictionary construct and makes it a little less verbose and a bit more readable) you reference things like so:
ViewBag.isAdmin = true;
and can check them, as tyrongower stated above, like so:
if (ViewBag.isAdmin)
{
// do stuff
}
I typically use the ViewBag syntax when I do use this construct, but they really do reference the same stuff. So if you did something like so outside the View:
ViewData["isAdmin"] = true;
you could reference it like this, if you were so inclined:
ViewBag.isAdmin
or vice-versa.
Just a little more detail on the concept.