IIOS IPAD No Unload,beforeunloador, paghide events - ios

I have a MVC web database application where the records are basically documents with items.
Documents are locked, not items and they locked by code when the user looks in any of 4 or 5 different screens for any given document.
there is a 10 minute time out on the record locks. The user does not do anything with the record for 10 minutes and another can take the record. There is code that detects the lock was lost and taken by someone else. It works fine and is technically sound.
The workflow of the application relies on the lock being released when the user leaves the screen or closes the browser, or if they press the refresh button.
These are work fine on windows and android but not on ipad.
I understand there is no
beforeunload
on ios but I though there was
unload
or
pageHide
neither of these work.
Here is my code.
var isOnIOS = navigator.userAgent.match(/iPad/i)||
navigator.userAgent.match(/iPhone/i); var eventName = isOnIOS ?
"pageHide" : "beforeunload";
window.addEventListener(eventName, function (event) {
ReleaseRecordLock(); } );
This code works on all mentioned platforms except that the events don't fire on IOS.
It looks to me that this is deliberate on Apple's part so I an not thinking it will change.
So now the question.
What can I do to ensure that these records get unlocked if a user changes screens or closes the browser. If they don't no users will be able to access the document for 10 minutes which will not be acceptable.
Thanks
Edit... I don't need pop ups or notification. I just need reliable unlocking

As mentioned above none of the events that are supposed to work actually fire. pageHide and unload do nothing.
I found mentions of how to get around this problem but no details so I though I would detail it here.
This solutions works with areas and standard sites.
My solution to get around part of this was to detect if the browser is running on IOS and if so to change the link in the menu.
<li>
#{
if(Request.UserAgent.Contains("iPad") || Request.UserAgent.Contains("iPhone"))
{
<a onclick="IOSReleaseLock('controller', 'action')" href="javascript:void(0);">LinkText</a>
}
else
{
#Html.ActionLink("link Text","action","controller",new { Area = "Tasks" },null);
}
}
</li>
Every single link in the application has to have a function called IOSReleaseLock() available or the solution will not work. Not all pages lock records, only those that actually change documents. Reports, and basic website functions such as change password, log out, and the sys admin stuff do not need record locks.
At this point I have 2 versions of IOSReleaseLock()
This is the version that is used on pages that do not required unlocking.
function IOSReleaseLock(_controller, _action)
{
var url = '/__controller__/__action__/';
url = url.replace('__controller__', _controller);
url = url.replace('__action__', _action);
window.location.href = url;
}
This is the version that is placed on pages that required unlocking.
function IOSReleaseLock(_controller, _action )
{
var url = '/__controller__/__action__/';
url = url.replace('__controller__', _controller);
url = url.replace('__action__', _action);
UnloadingRecordLockRelease();
window.location.href = url;
}
Every link has a wrapper so every single page must load a version of IOSReleaseLock(). This includes your /home/index or where ever your application starts. If you miss one then once you are on that page your menu system links will not work anymore.
Pages that require the UnloadingRecordLockRelease() function load that version and the pages that do not require unlocking load the first version.
On IOS every time you click a link, IOSReleaseLock() is called. This may seem to be obvious, but for clarity, the version of IOSReleaseLock() that executes is the version that is on the current page, not the version on the page you are going to.
So as long as the user stays on the site and does not close the browser then the records are unlocked correctly.
When the user logs out all records are unlocked but I have no solution for when the browser tab is closed or when the browser is closed without the user logging out.

Related

Why does this Spectron code block my Electron app after the first click?

This is the first time I'm trying to create automated tests for an Electron app using Spectron. It might be my rusty knowledge of async programming but I don't know why the code below is misbehaving:
it ('should allow me to create an account', function() {
return app.client
.waitUntilWindowLoaded()
.waitForExist('//a[text()="Create Free Account"]')
.click('//a[text()="Create Free Account"]')
.waitForExist('//button[text()="Create Account"]')
.setValue('#Email', "test#test.com")
.setValue('#Password', "Password1!")
.click('//button[text()="Create Account"]')
.waitForExist('//p[contains(text(),"Almost done.")]')
});
The test seems to get as far as the first click(), then it should wait for the App to request a new page, eventually displaying a "Create Account" button. However, for some reason, the app itself seems to block at this point. I know the click is occurring. When I try it manually, the app behaves properly.
Mark
I think you need to wait till the next page loads
Also chain
.pause(3*1000)
after clicking the button
(Or)
.waitUntilWindowLoaded(3*1000)

online/offline data management

I have to create an application that has functionality similar to the contacts app. You can add a contact on the client's iPhone and it should get uploaded onto the client's iPad. If the client updates the contact on their iPad, it should get updated on their iPhone.
Most of this is fairly straight forward. I am using Parse.com as my back end and saving contacts locally with Core Data. The only problem I'm encountering is managing contacts when the user is offline.
Let's say I have an iPhone and an iPad. Both of them currently have the same version of the online database. My iPhone is now offline. It is 9AM.
At 10AM I update the phone number for a contact on my iPad. It saves the change locally and online. At 11AM I update the email address for the same contact on my iPhone but I'm still offline.
At noon, my iPhone connects to the internet and checks the server for changes. It sees that its changes are more recent than the latest update (checking an updatedAt timestamp property), so instead of downloading the new phone number for the contact (which is "obsolete"), it overrides the phone number along with the email address (updates the new phone number to the old version it has because it was offline during the phone number update at 10AM and its changes are supposedly more recent).
How am I supposed to manage the online/offline problems encountered such as the one above? A solution I can think of would be to keep updated timestamps on every attribute for a contact instead of just a general updatedAt property for the entire contact, e.g. when was first name updated, when was last name updated, and then manually check if an offline device has more recent changes on every attribute instead of overwriting the whole object, but that seems sloppy.
I was also thinking on having an updatedLocally and updatedOnline timestamp property on every Core Data object. This way if the two don't match I can do a diff-check and use the most recent one for conflicts but this still doesn't seem like the cleanest solution. Has anyone else encountered something similar? If so, how did you solve it?
Pseudocode/Summary for what I think? covers every test case but still isn't very elegant/complete:
2 Entities on Parse.com: Contact and Contact History
Contact has first, last, phone, email, onlineUpdate
Contact History has a Primary Key to a Contact to refer to and the same attributes but with history. e.g. first: [{value:"josue",onlineUpdate:"9AM"},{value:"j",onlineUpdate:"10AM"},{value:"JOSUEESP",onlineUpdate:"11AM"}]
1 Entity on Core Data, Contact:
Contact has first, last phone, email, onlineUpdate, and offlineUpdate (IMPORTANT: this is only on Core Data, not on Parse)
for every contact in parse database as onlineContact {
if onlineContact does not exist in core data {
create contact in core data
}
else {
// found matching local object to online object, check for changes
var localContact = core data contact with same UID as onlineContact
if localContact.offlineUpdate more recent than onlineContact.onlineUpdate {
for every attribute in localContact as attribute {
var lastOnlineValueReceived = Parse database Contact History at the time localContact.onlineUpdate for attribute
if lastOnlineValueReceived == localContact.attribute {
// this attribute did not change in the offline update. use latest available online value
localContact.attribute = onlineContact.attribute
}
else{
// this attribute changed during the more recent offline update, update it online
onlineContact.attribute = localContact.attribute
}
}
}
else if onlineContact.onlineUpdate more recent than localContact.offlineUpdate {
// another device updated the contact. use the online contact.
localContact = offlineContact
}
else{
// when a device is connected to the internet, and it saves a contact
// the offline/online update times are the same
// therefore contacts should be equivalent in this else statement
// do nothing
}
}
TL;DR: How are you supposed to structure a kind of version-control system for online/offline updates without accidental overwriting? I'd like to limit bandwidth usage to a minimum.
I would suggest to use key based updates instead of contact based updates.
You should not send the whole contact to the server, in most cases the user would just change a few attributes anyways (things like 'last name' usually don't change very often). This also reduces bandwith usage.
Along with the applied changes of your offline contact you send the
old version number/last update timestamp of your local contact to the server. The server can now
determine whether or not your local data is up to date, simply by looking at your old version number. If your old version number matches the current version number of the server there is no need for your client to update any other information. If this is not the case the server should send you the new contact (after applying your requested update).
You can also save those commits, this would result in a contact history
which does not store the whole contact each time a key was changed but only the changes themselves.
A simple implementation in pseudo code could look like this:
for( each currentContact in offlineContacts ) do
{
if( localChanges.length > 0){ // updates to be made
commitAllChanges();
answer = getServerAnswer();
if(answer.containsContact() == true){
// server sent us a contact as answer so
// we should overwrite the contact
currentContact = answer.contact;
} else {
// the server does not want us to overwrite the contact, so we are up to date!
}
// ...
}
} // end of iterating over contacts
The server side would look just as simple:
for (currentContactToUpdate in contactsToUpdate) do
{
sendBackContact = false; // only send back the updated contact if the client missed updates
for( each currentUpdate in incomingUpdates ) do {
oldClientVersion = currentUpdate.oldversion;
oldServerVersion = currentContact.getVersion();
if( oldClientVersion != oldServerVersion ){
sendBackContact = true;
// the client missed some updates from other devices
// because he tries to update an old version
}
currentContactToUpdate.apply(currentUpdate);
}
if(sendBackContact == true){
sendBack(currentUpdate);
}
}
To get a better understanding of the workflow I will provide an example:
8 AM both clients and the server are up to date, each device is online
Each device has an entry (in this case a row) for the contact 'Foo Bar' which has the primary key ID.
The version is the same for each entry, so all of them are up to date.
_ Server iPhone iPad
ID 42 42 42
Ver 1 1 1
First Foo Foo Foo
Last Bar Bar Bar
Mail f#b f#b f#b
(excuse this terrible format, SO sadly does not support any sort of tables...)
9 AM your iPhone is offline. You notice Foo Bar's email changed to 'foo#b'.
You change the contact information on your phone like this:
UPDATE 42 FROM 1 TO 2 Mail=foo#b
// ^ID ^old version ^new version ^changed attribute(s)
so now the contact in your phone would look like this:
_ iPhone
ID 42
Ver 2
First Foo
Last Bar
Mail foo#b
10 AM your iPad is offline. You notice 'Foo Bar' is actually written as 'Voo Bar'! You apply the changes immediatly on your iPad.
UPDATE 42 FROM 1 TO 2 First=Voo
Notice that the iPad still thinks the current version of contact 42 is 1. Neither the server nor the iPad did notice how you changed the mail address and increased the version number, since no devices were connected to the network. Those changes are only locally stored and visible on your iPad.
11 AM you connect your iPad to the network. The iPad sends the recent update
to the server. Before:
_ Server iPad
ID 42 42
Ver 1 2
First Foo Voo
Last Bar Bar
Mail f#b f#b
iPad -> Server:
UPDATE 42 FROM 1 TO 2 First=Voo
The server can now see that you are updating Version 1 of contact 42. Since version 1 is the current version your client is up to date (no changes commited in the mean time while you were offline).
Server -> iPad
UPDATED 42 FROM 1 TO 2 - OK
After:
_ Server iPad
ID 42 42
Ver 2 2
First Voo Voo
Last Bar Bar
Mail f#b f#b
12 AM you disconnected your iPad from the network and connect your iPhone.
The iPhone tries to commit the recent changes. Before:
_ Server iPhone
ID 42 42
Ver 2 2
First Voo Voo
Last Bar Bar
Mail f#b foo#b
iPhone -> Server
UPDATE 42 FROM 1 TO 2 Mail=foo#b
The server notices how you try to update an old version of the same contact.
He will apply your update since it is more recent than the iPad's update but
will send you the new contact data to make sure you get the updated first name aswell.After:
_ Server iPhone
ID 42 42
Ver 2 2
First Voo Voo
Last Bar Bar
Mail foo#b foo#b
Server -> iPad
UPDATED 42 FROM 1 TO 3 - Ver=2;First=Voo;.... // send the whole contact
/* Note how the version number was changed to 3, and not to 2, as requested.
* If the new version number was (still) 2 the iPad would miss the update
*/
The next time your iPad connects to the network and has no changes to commit it should just send the current version of the contact and see whether it is still up to date.
Now you have committed two offline changes without overwriting each other.
You can easily extend this approach and so some optimizations. For example:
If the client tries to update an old version of the contact, don't send them the whole contact as answer. Rather send them the commits they missed and let them update their contact by themselves. This is useful if you store lots of information about your client and expect few changes to be done between updates.
If the client updated all information about a contact we can assume he does not need to know about the missed updates, however we would let him know about everything he missed (but it would/should have no effect to him)
I hope this helps.
I know nothing about iOs, core data and parse.com, so I can suggest only a general algorithmic solution. I think you can an approach similar to what is done in version control systems.
The simplest thing is to keep all the history on the server: keep all the revisions of the contact list. Now during the synchronization the phone sends information about the last server revision it has seen, and this revision will be the "common parent" for both the current phone revision and current server revision.
Now you can see what has changed on server and on phone since that revision, and apply the usual 3-way comparison: if some field has changed only on the server, then send the new field to the phone; if some field has changed only on the phone, then change it on server too, if some field has been changed both on phone and on server and the changes are different, then you have a conflict and have to ask user.
A variation of this approach might be to work with changes, not revisions. The primary data at both the server and the client will be not the contact list, but a history of its changes. (The current contact list, as well as a set of 'keyframes' can also be kept if needed; it will not be used for conflict resolving algorithm, but can be used so that it can be quickly shown and used.)
Then, when a user synchronizes the data, you download/upload only the changes. If there are any conflict changes, you have nothing left but to ask a user, otherwise you just merge them. How you define a change and which changes are considered conflicting, is up to you. A simple approach can be defining a change as a pair (field, new-value), and two change are conflicting if they have the same field. You can also employ a more advanced conflict resolving logic such as if one change changes only the first half of the email, and the other the second half, then you can merge them.
The correct way to do this is to keep a transaction log. Whenever you save in Core Data you create a log entry in your transaction log. When you are next online you play back the transaction log against the server.
This is how iCloud and other sync services work.
Instead of having separate flag for each of the core data objects, you can have separate table which will store IDs(primary key from database table which stores contact information) of all updated contacts.
Later when user comes online you just fetch those contacts from your actual contact detail table and upload them on your server.

OnBeforePlay .seek doesn't work on iPad

I've scoured the web, upgraded the player, rewritten it 5 times, and now completing my 5th day of failing, and still cannot accomplish what the folks at Longtail tell me will work. (Don't get me wrong, I love 'em there, but this has me ready to jump off a bridge).
I'm simply trying to load a video that will play with Flash or iOS, and upon loading it, immediately go to a specific point in the video useing the .seek() method. Longtail tells me to use the onBeforePlay() function because iOS apparently doesn't respect the start value of the playlist. This code works like smoke with Flash, but ignores the seek in iOS.
Can ANYone assist me with this - it has become the most expensive script I've ever worked on and I have made zero progress at all. :( :( :( Also, I removed all the console functions and tried that, but with the same result.
Full code/player can be seen at http://www.tempurl.us/jw6e.html. You can see that with Flash, the video starts at 60 seconds, but on iOS, it starts at 0.
jwp = jwplayer('jwp').setup({
title: 'Single File Player', width: '720', height:'240', autostart: 'false', listbar: {position: "right",size: 400},
sources:[
{ file: 'http://media3.scctv.net/insight/mp4:nursing_4_clips_400.mp4/playlist.m3u8'},
{ file: 'rtmp://fms.scctv.net/insight/nursing_4_clips_400.mp4'}
]
}
);
jwp.onReady(function() {
// Create a playlist item of the video to play
var newItem = [
{ title: 'Title4 ACUTE_ABDO_PAIN_400',
image: 'playlistitem.png',
sources:[
{ file: 'http://media3.scctv.net/insight/mp4:ACUTE_ABDO_PAIN_400.mp4/playlist.m3u8'},
{ file: 'rtmp://fms.scctv.net/insight/ACUTE_ABDO_PAIN_400.mp4'}
]
}
];
jwp.load(newItem);
});
jwp.onBeforePlay(function() {
// This Works on PC/Mac with Flash, but does nothing on iPad/iPhone
jwp.seek(60);
});
Simply to close the question, the bottom line on this problem was that iOS will not allow autostart - period. Knowing that, all the expected events that were not behaving as expected made sense. Once the user initiates the stream with Play, everything works as expected. In our case, this is still a problem because we want to start later in the stream, but knowing that made dealing with it more manageable.
If the problem is iOS will not allow autostart - period. Knowing that,
all the expected events that were not behaving as expected made sense.
Once the user initiates the stream with Play, everything works as
expected
then you can have a play button only for tablet and ios device and on Clicking the play button,
call jwplayer().play(), this could be a work around for your problem, and after you have invoked jwplayer.play, which is only possible with the touch event, after play is triggeredother events will work.
otherwise even if you try jwplayer().play() onReady(), or autostart nothing will work because of iOs will not allow autostart as you said
I've solved this problem on iOS using onBeforePlay with seek() and play(). This work on desktop flash and IOS. Doesn't work on Android using the parameter androidhls:true
jwplayer().onBeforePlay(function() { jwplayer().seek(60); });
jwplayer().play();
As Ethan JWPlayer mentioned in comment use onPlay event. To prevent "loop buffering" as you said just use flag variable:
var isFirstStart = true,
seekValue = 60;
jwplayer().onPlay(function(){
//exit if it's no first playback start
if( !isFirstStart ) {
return;
}
jwplayer().seek(seekValue);
isFirstStart = false;
});

How to add a weather info to be evalueated only once?

In a ASP.MVC (1.0) project i managed to get weather info from a RSS feed and to show it up. The problem i have is performance:
i have put a RenderAction() Method in the Site.Master file (which works perfectly) but i 'm worried about how it will behave if a user clicks on menu point 1, after some seconds on menu point 2, after some seconds on menu point 3, .... thus making the RSS feed requesting new info again and again and again!
Can this somehow be avoided? (to somehow load this info only once?)
Thanks in advance!
You could use the server cache:
public ActionResult Index()
{
// try getting the weather from the cache
object weather = HttpContext.Cache.Get("weather");
if (weather == null)
{
// weather not cached => fetch it from RSS
weather = Repository.FetchWeather();
// store in cache for the 5 next hours
HttpContext.Cache.Add(
"weather",
weather,
null,
DateTime.Now.AddHours(5),
Cache.NoSlidingExpiration,
CacheItemPriority.Normal,
null
);
}
return View(weather);
}
I think if I were writting this app I'd download the weather details and store them in a db or xml file locally and read from that.
Weather doesn't change that much that you need minute by minute updates unless you're the weather channel or something.
Alternatively you could write this as a thread and have it report back when it's done loading. VS 2010 makes this a piece of cake btw.
So to solve this might be to give your code the ability to cancel a request and to do that you can't run this as a single threaded application. You are going to need to have the ability to kick off a request on a thread and cancel that on another request.
Faced with that I'd host it locally and run a process every say hour to update the data from the RSS feed.

Silverlight 3 IncreaseQuotaTo fails if I call AvailableFreeSpace first

The following code throws an exception...
private void EnsureDiskSpace()
{
using (IsolatedStorageFile file = IsolatedStorageFile.GetUserStoreForSite())
{
const long NEEDED = 1024 * 1024 * 100;
if (file.AvailableFreeSpace < NEEDED)
{
if (!file.IncreaseQuotaTo(NEEDED))
{
throw new Exception();
}
}
}
}
But this code does not (it displays the silverlight "increase quota" dialog)...
private void EnsureDiskSpace()
{
using (IsolatedStorageFile file = IsolatedStorageFile.GetUserStoreForSite())
{
const long NEEDED = 1024 * 1024 * 100;
if (file.Quota < NEEDED)
{
if (!file.IncreaseQuotaTo(NEEDED))
{
throw new Exception();
}
}
}
}
The only difference in the code is that the first one checks file.AvailableFreeSpace and the second checks file.Quota.
Are you not allowed to check the available space before requesting more? It seems like I've seen a few examples on the web that test the available space first. Is this no longer supported in SL3? My application allows users to download files from a server and store them locally. I'd really like to increase the quota by 10% whenever the user runs out of sapce. Is this possible?
I had the same issue. The solution for me was something written in the help files. The increase of disk quota must be initiated from a user interaction such as a button click event. I was requesting increased disk quota from an asynchronous WCF call. By moving the space increase request to a button click the code worked.
In my case, if the WCF detected there was not enough space, the silverlight app informed the user they needed to increase space by clicking a button. When the button was clicked, and the space was increased, I called the WCF service again knowing I now had more space. Not as good a user experience, but it got me past this issue.
There is a subtle bug in your first example.
There may not be enough free space to add your new storage, triggering the request - but the amount you're asking for may be less than the existing quota. This throws the exception and doesn't show the dialog.
The correct line would be
file.IncreaseQuotaTo(file.Quota + NEEDED);
I believe that there were some changes to the behavior in Silverlight 3, but not having worked directly on these features, I'm not completely sure.
I did take a look at this MSDN page on the feature and the recommended approach is definitely the first example you have; they're suggesting:
Get the user store
Check the AvailableFreeSpace property on the store
If needed, call IncreaseQuotaTo
It isn't ideal, since you can't implement your own growth algorithm (grow by 10%, etc.), but you should be able to at least unblock your scenario using the AvailableFreeSpace property, like you say.
I believe reading the amount of total space available (the Quota) to the user store could be in theory an issue, imagine a "rogue" control or app that simply wants to fill every last byte it can in the isolated storage space, forcing the user eventually to request more space, even when not available.
It turns out that both code blocks work... unless you set a break point. For that matter, both code blocks fail if you do set a break point.

Resources