How does iOS 6 UIWebView state restoration work? - ios6

According the docs:
In iOS 6 and later, if you assign a value to this view’s restorationIdentifier property, it attempts to preserve its URL history, the scaling and scrolling positions for each page, and information about which page is currently being viewed. During restoration, the view restores these values so that the web content appears just as it did before.
I'm doing all that, but nothing is happening. And even if I manually save and restore the URL the user was looking at previously, the scroll position is not restored. Are the docs just wrong?

The docs are right, but very incomplete. Here's what's going on. If a web view participates in state restoration (I'm assuming you know what this means - everything has to have a restorationIdentifier, and so on), and if the web view had a request (not an HTML string) when the user left the app, the web view will automatically return to life containing the same request as its request property, and with its Back and Forward lists intact. Thus, you can use the state restoration mechanism to restore the state of the web view, but you have to perform a little extra dance. This dance is so curious and obscure that initially I was under the impression that a web view's state couldn't really be saved and restored, despite the documentation's assertion that it could.
There are two secrets here; once you know them, you'll understand web view state restoration:
A restored web view will not automatically load its request; that's up to your code.
After a restored web view has loaded its request, the first item in its Back list is the same page in the state the user left it (scroll and zoom).
Knowing this, you can easily devise a strategy for web view state restoration. The first thing is to detect that we are restoring state, and raise a flag that says so:
-(void)decodeRestorableStateWithCoder:(NSCoder *)coder {
[super decodeRestorableStateWithCoder:coder];
self->_didDecode = YES;
}
Now we can detect (perhaps in viewDidAppear:) that we are restoring state, and that the web view magically contains a request, and load that request:
if (self->_didDecode && wv.request)
[wv loadRequest:wv.request];
Now for the tricky part. After the view loads, we immediately "go back." This actually has the effect of restoring the user's previous scroll position (and of removing the extra entry from the top of the Back stack). Then we lower our flag so that we don't make this extra move at any other time:
- (void)webViewDidFinishLoad:(UIWebView *)wv {
if (self->_didDecode && wv.canGoBack)
[wv goBack];
self->_didDecode = NO;
}
The UIWebView is now in the state it was in when the user previously left the app. We have saved and restored state using the built-in iOS 6 state saving and restoration feature.

I have tried Matt's answer and it works well, however if there are 'forward' pages in the history stack-they will be replaced by the restored request within the web view object.
A better approach is to instead call the 'reload' method on the webview object, this will restore the history in both directions as well as the zoom and content scroll offset.
If you'd like an example of my approach or some more functionality for your own webview, take a look at my open source fork of SVWebViewController here.

Related

ajax request not refired when returning to website when pressing browser 'Back' button

I have a page that user can make changes to an item like changing title, description and etc, after user made the changes and they are saved, he can travel away from the website, then goes back using the 'Back' button and what he sees is the page actually before the changes he has made.
The reason for that, instead of actually making GET request for the item, Chrome returned request from the cache instead, you can see it in Network tab in the Size column as (from cache).
One of the solutions that colleague of mine proposed is to append query parameter with some random integer to request, but it's kinda hacky.
Do you take care of this situation, if yes then how or why not?

Oracle ADF: Refresh Form data

I am developing a web app using Oracle ADF. I have a bounded task flow. In that I have a search page like below.
I have created the above two forms using view object data controls.
Searching is performing well. But my problem is when I go some where else in my application using menus provided left side and come back to the search page , the page is not getting refreshed. I am getting a search page that contains old search results. At this point of time if I am trying to make any changes am getting some error called "Another user with this id already modifed data ....". After this error my app is not running. Means what ever am trying to do its showing the same error.
So I need to make this: "When ever the user come to this form, He should get fresh form. It should not contain old search results.
Please help me. How do I achieve this.
Thank you.
There are 2 ways of doing it:
1) Set your task flow as ISOLATED, from Task Flow Overview tab -> Behaviour -> Share Data Control with calling task flow -> unchecked (or isolated, if you are using JDev 12c)
This will ensure you always start FRESH when accessing the page, but it will potentially create a performance overhead because entire View Object cache will be recreated (requeried) on page load. Nevertheless, it is the quickest solution.
2) You may create a default Method Call Activity in your task flow from where you may call a AM's custom method that resets the view criteria. The method will be placed on application module's implementation class and it may look like this:
public void initTaskFlow() {
this.getViewObject1().executeEmptyRowSet();
}
This will clean the result data. If you want to reset the querying parameters as well, you can use this example:
http://www.jobinesh.com/2011/04/programmatically-resetting-and-search.html
When you made any changes to any viewObject then excute this viewObject to match entity state and viewState , i think excuting viewObject will solve your issue
Ashish

Backbone.js navigation working once but not again

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.

Preferred way of creating links with backbone.js

I'm trying to wrap my head around backbone.js but I'm finding it hard due to the lack of (IMO) good examples.
First of all, what is the best way of getting a link to an object.
If I want to get the edit url of an Album model I could do album.url() + '/edit', is this really the best way?
Also, I'm trying to make my application work 100% without javascript so I don't want my URLs/links to say /albums/#1/edit, I want it to be /albums/1/edit and override this in JS.
I'm thinking I create normal URLs and use jQuery.live to call router.navigate in backbone.js
I never got this to work however, when I call router.navigate('/albums/2', true) the URL changes but my show action is never called. If I refresh it's called so the route is matched.
What am I missing?
The basic answer, which is kind of frustrating, is "there is no preferred way!". Backbone.js doesn't tell you how to set up links, you can do it any way you like. I found this flexibility just as annoying as you do, at least at first.
So here's the way I'm approaching this on my current project, with the (big) caveat that this is just one of many ways to do things in Backbone:
For the most part, I don't use actual links. There's no explicit reason not to, but it means you have to keep track of a bunch of URL strings that have to be consistent. I would rather stick all the URL formatting in my routers and not deal with it elsewhere.
To open a new "top-level" view, like an editing screen, I set something that fires an event. In the application I'm currently working on, I have a global State model, and to open a new view I call state.set({ topview: MyTopView }). This causes the state object to trigger change:topview.
Any piece of the UI that needs to change when the top-level view changes has an update method bound to change:topview. When the event fires, they look at state.get('topview') and update as necessary.
I treat my routers as only marginally specialized parts of the UI - they're essentially views that render in the browser address bar, rather than the window. Like other views, they update the state object on UI events (i.e. a new URL), and like other views, they listen to the state object for changes that cause them to update. The logic that the editing screen has the URL albums/<albumid>/edit is fully encapsulated in the router, and I don't refer to it anywhere else.
This works well for me, but it adds an entirely new pattern, the global State object, to the Backbone structure, so I can hardly call this the "preferred" approach.
Update: Also note that .url(), in the Backbone idiom, refers to the model's URL in the back-end API, not the front-end URL (it's not like Django's get_absolute_url). There is no method in the default Backbone setup that gives you a user-facing URL for your model - you'd have to write this yourself.
Also, I'm trying to make my application work 100% without javascript; so I don't want my URLs/links to say /albums/#1/edit, I want it to be /albums/1/edit and override this in JS.
you can do exactly this w/ pushState. just enable it in your Backbone.history.start call:
Backbone.history.start({pushState: true})
this tells Backbone to use the HTML5 History API (a.k.a. "PushState"), which uses full URLs exactly like you're wanting.
read up on the history api here: http://diveintohtml5.ep.io/history.html
and I wrote up a 2 part series on using pushstate w/ the second part focusing on progressive enhancement in backbone, to do what you're needing:
http://lostechies.com/derickbailey/2011/09/26/seo-and-accessibility-with-html5-pushstate-part-1-introducing-pushstate/
and
http://lostechies.com/derickbailey/2011/09/26/seo-and-accessibility-with-html5-pushstate-part-2-progressive-enhancement-with-backbone-js/
hope that helps :)

Adobe Flex Caching Array

My program fills an array with data from a facebook page feed but every time i go from one tab to another it wants to reload this data, is there any way i can cache this array so that it will not reload the information unless its changed?
This is exactly why your Views should not contain Service logic. Instead, your View should dispatch an event asking for the service call and your Controller (you do have one, right?) should catch that event and decide whether to act on it or not.
How do you know the data hasn't changed without reloading it?
Maybe what you need is to store the timestamp of the last service call, than measure the amount of time before executing the service call again.
Perhaps with a 5-minute timeout, if the user continuously changes tabs within 5-minutes from the last service call, the array persists previously loaded data.
After 5-minutes, if the user changes back to that tab the service call can fire, load data, than update the timestamp to prevent loading.

Resources