Lets say, that I’ve got simple app, which is created with Backbone.Marionette.
For sake of simplicity, assume that I’ve got „Show” view for some model (it doesn’t matter here what it is), where I can click „Add new”, which shows me „new” view in „modal” window.
I’m displaying there simple form, which (after validation) is sent to my API. During that, I’m displaying other view (which displays sth. like „Saving, please wait…”).
In case of success, I’m closing this modal window, and everything is OK.
However (that is the tricky part): I would like to handle situations, when from some reason model was not saved (API temporary downtime, connection issue, race condition etc.), and display same view another time (I thought that it should be easy) – however, when I’m doing this, events aren’t handled anymore (ie: submitForm action is not executed second time :( )
Here is my my proof of concept for this:
class MyApp.SampleView extends Marionette.View
events:
"submit form": "submitForm"
submitForm: (event) ->
event.preventDefault()
data = $(event.currentTarget).serializeObject()
model = SampleModel.new(data)
if model.isValid()
MyApp.popupRegion.show(SavingPopup)
savePromise = model.save()
savePromise.success =>
#close()
#displayNotification("Model has been added")
savePromise.error =>
#displayNotification("Something went wrong, please try again")
MyApp.popupRegion.show(this) # displays correct view
# but does not handle events :(
TL;DR version:
How to re–append view to region in such way that my events still will be handled?
Before a region show()'s a view it calls close() on the currently displayed view. close() acts as the view's destructor, unbinding all events, rendering it useless and allowing the garbage collector to dispose of it.
When you execute MyApp.popupRegion.show(SavingPopup), this is close()'d. You will need to create a new instance of the view in savePromise.error, or find some other way to signal your application that the view needs to be recreated.
Something like this should work:
savePromise.error =>
#displayNotification("Something went wrong, please try again")
MyApp.popupRegion.show(new MyApp.SampleView)
Related
Say,there is a requirement that a customer object is loaded from the db to a silverlight application, so that the customer details are shown in the UI. We need to detect if the user is changing any data in the UI.
We are listening to property changed notifications from the view model. However, when the notifications are result of property change as part of the loading process we have to discard it.
class CustomerLoader
{
Helper helerobj;
Address addressobj;
Profile profileobj;
void LoadFromDb()
{
helperobj.Load();
addressobj.Load();
profileobj.Load();
//start listening after the loading finished
this.propertychanged += new PropertyChangedEventHandler(handlepropertychanged);
}
The trouble with this is the inner objects might be calling asynchronous functions which might set properties. So by the time we started the property change listening, the loading might not have been finished.
We need to know when the loading is actually done. As of now we are asking the developers who are developing the inner object classes to accept a callback in the parameter which they should call when the function is finished.
Is there any other way to do it?
You want nothing but a really classic asynchronous objet loading.
So yes, the only solution is to ask developers working on the loading to propose an asynchronous function. Now you hav several solution to achieve asynchronicity in Silverlight.
You could either provide a callback as you do, or use async and await to manage your asynch task as explain here: http://10rem.net/blog/2012/05/22/using-async-and-await-in-silverlight-5-and-net-4-in-visual-studio-11-with-the-async-targeting-pack
I'm using Backbone.js 0.9.2 from the Backbone-on-rails gem. I'm also trying to use the new 'pushState' instead of the old hash URL.
The Problem
I'm building a standard Rails like CRUD interface to keep track of my appointments. I have a 'new' link on the main index.jst.eco page:
<h1>Appointments</h1>
<p>New Appointment</p>
I load the page and click on that 'new' link and backbone fires off the event and doesn't have to reload the whole page. Here is that event:
class BackboneOnRails.Views.AppointmentsIndex extends Backbone.View
template: JST['appointments/index'],
events: ->
'click .new': 'newAppointment'
newAppointment: ->
Backbone.history.navigate("/appointments/new", {trigger: true})
return false
# The rest of the index methods omitted for brevity
This then invokes the backbone router:
class BackboneOnRails.Routers.Appointments extends Backbone.Router
routes:
'': 'index'
'appointments': 'index'
'appointments/new': 'new'
initialize: ->
this.appointments = new BackboneOnRails.Collections.Appointments()
this.appointmentsIndexView = new BackboneOnRails.Views.AppointmentsIndex({collection: this.appointments})
this.appointmentsIndexView.render()
index: ->
$("#container").html(this.appointmentsIndexView.el)
this.appointments.fetch()
new: ->
appointments = new BackboneOnRails.Collections.Appointments()
view = new BackboneOnRails.Views.AppointmentNew({collection: appointments})
$("#container").html(view.render().el)
The problem happens when I hit the browsers back button, then try using the 'new' link again. This time around it does a full reload of the page.
What is happening to the javascript bindings when I hit back on the browser?
I have a show event for the item and with that I can go back and forth no problem. I've compared both and they look like the same sort of calls.
The problem is in your attempted re-use of the appointmentsIndexView instance. Removing the view from the DOM destroys the DOM event handlers. Re-adding the view's el to the DOM does not re-connect them.
Overview Of The Problem
When you load that view with the initialize and index methods of your router the first time, everything is fine because you have a fresh instance of the IndexView. The DOM events are attached to the view properly, and life is good.
When you hit the new route / method of your router, you're effectively trying to remove the index view from the screen and replace it with the add new view. This works from a visual stand point and from the standpoint of the add new view.
When you hit the back button, though, you're staying within the same live application instance in your browser tab. Hitting the back button with pushstate enabled tells the browser not to reload the entire app, just to update the url and fire off the router method for the index.
In this case, you're index view is not re-built from the ground up. You're re-using the same view instance, but re-loading it with data from the server. The data load works perfectly fine because your view and collection are still attached. The DOM event bindings fail, however, because they bindings were previously removed and not re-added.
2 Common Solutions
There are two common solutions for this, and many variations of these solutions.
1) Don't re-use view instances.
This is my strongly recommended suggestion. In every instance where I have tried to re-use a view instance, I have consistently run into very large problems - including the exact problem you're having.
Instead, re-create a new view instance every time you need to show the index of appointments. That means you create the new index view in your index method of the router, instead of the initialize method.
2) Clear and re-bind the DOM events
If, for some reason, you feel that you really need to re-use the view instance (which should never be true), you can solve it with some information that Tim Branyen posted on his blog a while back:
http://tbranyen.com/post/missing-jquery-events-while-rendering
I do not recommend this approach. Re-using a view instance might seem like a good idea off-hand, but it will lead down a bad path toward other problems, including bloated memory usage by leaving too many unused parts around in your app.
Side Note: Zombies And Memory Leaks
In either case - whether you decide to re-use view instances or re-create them when you need them - you're likely to run in to some memory leaks.
In the case of re-using a view, you're explicitly holding on to an object in memory when you don't need to. This isn't really a "leak" but it's an excessive use of memory. You should de-reference the object when it's not needed and re-create it when it is needed. This will cut down on memory usage and allow your app to perform better.
I have a blog post covering how this works, here: http://lostechies.com/derickbailey/2012/03/19/backbone-js-and-javascript-garbage-collection/
In the case of not re-using a view, you may wind up with a true memory leak by leaving model and collection event bindings hanging around after a view has been removed from the visible DOM. If you decide not to re-use your views, you will need to make your code that replaces the #container html more robust, and have it clean up the old view.
I've got a blog post detailing a solution for that, as well: http://lostechies.com/derickbailey/2011/09/15/zombies-run-managing-page-transitions-in-backbone-apps/ - be sure to read the comments from Johnny Oshika in this post, as he points to a very useful StackOverflow answer where he shows a simple method of handling model and collection events.
I'm looking for a good way to implement a truly modal dialog in Silverlight 5. Every example that I find that claims to create a modal dialog really isn't modal in that the calling code waits until the dialog is closed.
I realize this is a challenge because we can't actually block the UI thread because it must be running in order for the dialog (ChildWindow) to function correctly. But, with the addition of the TPL in SL5 and the higher level of adoption Silverlight has seen over the past few years, I'm hoping someone has found a way around this.
A good representative scenario I am trying to solve is an action (say clicking a button or menu item) that displays a login dialog and must wait for the login to complete before proceeding.
Our specific business case (whether logical or not) is that the application does not require user authentication; however, certain functions require "Manager" access. When the function is accessed (via button click or menu item selected, etc), and the current user is not a manager, we display the login dialog. When the dialog closes, we check the user's authorization again. If they are not authorized, we display a nice message and reject the action. If they are authorized, we continue to perform the action which typically involves changing the current view to something new where the user can do whatever it is they requested.
For the sake of closure...
What I ended up with is a new TPL-enabled DialogService with methods like:
public Task<Boolean?> ShowDialog<T>()
where T : IModalWindow
The method creates an instance of the dialog (ChildWindow), attaches a handler to the Closed event and calls the Show method. Internally, it uses a TaskCompletionSource<Boolean?> to return a Task to the caller. In the Closed event handler, the DialogResult is passed to the TrySetResult() method of the TaskCompletionSource.
This lets me display the dialog in a typical async way:
DialogService.ShowDialog<LoginDialog>().ContinueWith(task =>
{
var result = task.Result;
if ((result == true) && UserHasPermission())
{
// Continue with operation
}
else
{
// Display unauthorized message
}
}, TaskScheduler.FromCurrentSynchronizationContext());
Or, I could block using the Task.Wait() method - although this is problematic because, as I mentioned in the original post, it blocks the UI thread, so even the dialog is frozen.
Still not a true modal dialog, but a little bit closer to the behavior I am looking for. Any improvements or suggestions are still appreciated.
I'm fairly new to MVVM, so please excuse me if this problem has a well-known solution.
We are building a bunch of model classes which have some core properties that are loaded up-front, as well as some additional properties which could be lazy-loaded on demand by making a web API call (update: to clarify, it would be a web API call per lazily-loaded property).
Rather than having multiple models, it seems sensible to have a single model with the lazy-loading logic in there. However, it also seems that the lazy-loaded properties should not block when accessed, so that when the View binds to the ViewModel and it binds to the Model, we don't block the UI thread.
As such, I was thinking of a pattern something along the lines of when a lazy property on the Model is accessed it begins an asynchronous fetch and then immediately returns a default value (e.g. null). When the asynchronous fetch is complete, it will raise a PropertyChanged event so that the ViewModel/View can re-bind to the fetched value.
I've tried this out and it seems to work quite nicely, but was wondering:
Are there any pitfalls to this approach that I haven't found out about yet, but will run into as the app increases in complexity?
Is there an existing solution to this problem either built into the framework, or which is widely used as part of a 3rd party framework?
I did something like this in the past and the one thing I kept forgetting about is you can't call your async property through any kind of code behind and expect it to have a value.
So if I lazy-load a list of Customer.Products, I can't reference Customer.Products.Count in the code-behind because the first time it's called the value is NULL or 0 (depending on if I create a blank collection or not)
Other than that, it worked great for the bindings. I was using the Async CTP library for making my async calls, which I found was absolutely wonderful for something like this.
public ObservableCollection<Products> Products
{
get
{
if (_products == null)
LoadProductsAsync();
return _products;
}
set { ... }
}
private async void LoadProductsAsync()
{
Products = await DAL.LoadProducts(CustomerId);
}
Update
I remember another thing I had issues with was data that actually was NULL. If Customer.Products actually returned a NULL value from the server, I needed to know that the async method had run correctly and that the actual value was null so that it didn't re-run the async method.
I also didn't want the async method to get run twice if someone called the Get method a 2nd time before the first async call had completed.
I solved this at the time by having an Is[AsyncPropertyName]Loading/ed property for every async property and setting it to true during the first async call, but I wasn't really happy about having to create an extra property for all async properties.
I have a WPF gui that has an instance of a class called Manager, which essentially manages certain communication and data functions.
I have try/catch blocks in my Manager, but I would like to know the best practice to communicate this to the GUI.
For example, clicking a button generates an onClick event, and in that code I would call Manager.DoProcessing(). Trouble is, how do I know if DoProcessing bombed out? I don't want to surround the function call with another try catch...
Would it be sufficient to return my own ErrorType enum which identifies the error:
enum ErrorType
{
NoError, TimeOut, DBCorrupt
}
etc. Or is this simplistic? It should be added that the calls to Manager will be threaded with a BackgroundWorker...
Don't return error codes. C# is better than that. Pretty soon you'll have a method that needs to return something: public Person GetPerson(int personID){} and you don't want to have to start working with out parameters. If GetPerson can't "Get" a "Person", that's exceptional, therefore you should throw an exception.
Is your Manager class a ViewModel? If not, wrap it in one. Your viewmodel should catch the exception (whatever thread it occurs on), and take responsibility for presenting it to the view. One option is to create an ObservableCollection of ErrorMessages (or strings, but I would write an ErrorMessage class with message, severity, time of occurance etc). Then your XAML can bind straight to that. Whenever your catch block adds an ErrorMessage to the collection, your view will update itself.
If you are using a BackgroundWorker, then maybe you'll need to check for exceptions in the RunWorkerCompleted event instead of using a 'catch' block. This documentation explains how.