8 Replies Latest reply on Aug 9, 2012 10:39 AM by ebloom29

    Just For Fun:  Triggering a script on iPad rotation

    steve_ssh

      Just for fun:

       

      I decided to see if I could realize a means for detecting iPad orientation changes which does not utilize an OnTimer script, or a looping script, i.e. something which doesn't require a FileMaker script to perform constant checking of the device's current orientation.

       

       

      The ideas that I wanted to work with were as follows:

       

      1) Layout objects which are anchored on two opposing sides are resized when the device is rotated.

       

      2) FM Go webviewer objects which are asleep are awakened upon being resized.

       

      3) FM Go webviewer objects which are awake can detect and respond to resize events.

       

      4) In response to either being awakened or receiving notification of a resize event, a webviewer can run custom Javascript.

       

      5) In FM Go 12, we can utilize Javascript in a webviewer to trigger a FileMaker script by using the FMP URL protocol.

       

       

      How well did this work?

       

      Using FM Go 12, on a first-generation iPad running iOS 5 it seems to work fairly well. There were a couple of times that, by performing a flurry of constant rotations and partial rotations of the device, I faked it out, and it was not able to keep up with my sequence of actions. Other than that, it seems to perform as desired on my device. I don't know how well (or even if) this works on more recent iPad models and/or other iOS versions.

       

       

      Would I utilize this technique in a real solution?

       

      Under most circumstances, I would not use this technique for a real solution. While I really like the fact that it doesn't require any periodic/looping FileMaker script, this technique is directly tied to the iOS webkit, and it is also closely tied to undocumented (subject to change) behavior of the FM webviewer. As such, either an FM upgrade or an OS upgrade has the potential to break this technique.

       

       

      I'm attaching a sample file for anyone who would like to take a look.

       

      Hope everyone is having a great weekend.

       

      Best,

       

      -steve

        • 1. Re: Just For Fun:  Triggering a script on iPad rotation
          BruceRobertson

          Very cool, thanks for sharing.

          • 3. Re: Just For Fun:  Triggering a script on iPad rotation
            ebloom29

            Bruce,

            This is a pretty cool idea. Do you have any more explanation on how you are triggering the layout change on rotation?

            • 4. Re: Just For Fun:  Triggering a script on iPad rotation
              BruceRobertson

              Steve is the one who developed the technique.

               

              My only part in this was complimenting him.

              • 5. Re: Just For Fun:  Triggering a script on iPad rotation
                ebloom29

                Bruce,

                You are correct, sorry about that

                 

                Steve,

                  Do you have any more explanation on how you are triggering the layout change on rotation?

                • 6. Re: Just For Fun:  Triggering a script on iPad rotation
                  steve_ssh

                  Hi Ebloom,

                   

                  I actually never went so as far to make the demo file change layouts -- I just triggered a script that used a SetField script step, but, presumably, one could change layouts or do any variety of desired tasks in the triggered script.

                   

                  If you enjoy pulling things apart to see how they work, most of the code that illustrates what is going on can be found in the last layout of the demo file: Inspect the webviewer object (upper right corner of layout) and check out the calculation used to define the source for the webviewer.

                   

                  Additionally:

                   

                  Below I'll list the gory details regarding what I tried, what worked, what didn't work, and what I wound up doing to make the demo file.  Please feel free to let me know if there's something that needs clarification, or if I leave something unanswered.  It is a lot of information, but not knowing which dots would be most helpful for me to connect, I'm trying sketch out most of the points that went into coming up with the demo.

                   

                  Hope you enjoy.

                   

                  Very best,

                   

                  -steve

                   

                   

                   

                  Introduction to how this technique works:

                   

                  Each layout that responds to device rotation needs to have a small webviewer object on it.

                   

                  The webviewer will take up some space, and it will take up more layout real estate when the device is in an orientation which causes the webviewer object to stretch.  We cause the webviewer to stretch/shrink by anchoring it on two opposing sides.

                   

                  This stretching of the webviewer object, along with the fact that it is 'visible', is key to how this technique works.  (I say 'visible' in quotes, because we set the color of the text in the webviewer to match the background color, which, in turn, should be set to match the background color of the containing object, so that the webviewer does not appear to be visible.)

                   

                  I think of webviewers in FM Go as having three states:

                   

                    - Awake and in focus

                    - Not in focus, but still 'awake', i.e. not asleep

                    - Asleep - where the webviewer 'freezes' its display, and the little circular arrow appears in the upper corner

                   

                  For the purposes of this technique, we can generalize to two states:  Awake (regardless of focus), and Asleep.

                   

                   

                   

                  Discovery which motivated this idea:

                   

                  While I was playing around one day, I discovered that an asleep webviewer object wakes up if it is resized.  This actually even seemed to hold true if the webviewer was to the right of the layout edge.

                   

                  This discovery was the motivation for pursuing this technique:

                   

                    I knew that, in FM Go 12, I could have webviewer Javascript invoke a FileMaker script by using the FMP URL protocol.

                    I also knew (from previous experimentation) that when a sleeping webviewer is awakened, it's Javascript code is re-evaluated.*

                   

                  This meant that if I wrote a small snippet of webviewer Javascript which triggers my target FM script, I could be assured that this script would be fired off whenever my webviewer was awakened.

                   

                  Thus:

                   

                  - We put a webviewer on the layout which will resize when the device is rotated.

                  - We have discovered that this webviewer will be awakened when it is resized, and therefore it will re-run its Javascript code.

                  - We create our webviewer with Javascript which will trigger our FM script, and we know that this script will run whenever our webviewer is awakened.

                   

                  What this means is that we have successfully managed to trigger an FM script for the case where our webviewer is 'asleep'.

                  At this point, we need to figure out how to trigger the FM script in the case where the webviewer is still awake.

                   

                   

                   

                  Trying to solve the case where the webviewer is still awake - dual webviewer:

                   

                  This did not work:

                   

                  For a while, I started playing with using a pair of webviewers, hoping that I could enforce a circumstance whereby at least one webviewer would always be asleep.  Had this worked, I would have been able to wrap up this endeavor by simply placing two small webviewers to the right of the layout edge, and know that at least one of them would be asleep, and therefore would wake up and trigger my FM script upon device rotation.  I was not able to get this dual-webviewer idea to work, and I left it to explore using Javascript event handlers.

                   

                   

                   

                  Trying to solve the case where the webviewer is still awake - Javascript event handler:


                  Using Javascript, an awake FM Go webviewer is capable of detecting and responding to events that happen within the webviewer.

                  ( Examples of events would be something like the user pressing/clicking somewhere, a field receiving focus, etc.)

                   

                  One responds to events by setting up what are referred to as 'Event Listeners' or event handlers.  Very roughly stated:  Event Listeners and event handlers are means for specifying that specific Javascript code should be invoked whenever an event of a specific type occurs for a target Element.

                   

                  This did not work: onorientationchange

                   

                  My first attempt to detect a change of orientation was what appeared to be most direct and obvious:

                   

                    There is an event called 'onorientationchange' which can be listened for on an iOS device.

                   

                  I thought for sure that I would be able to set up an Event Listener to listen for and respond to this event.  It didn't work.  At the time, I did not know why -- I tried using different methodologies for setting up the Event Listener for this event, but no luck.

                   

                  I've since seen a forum post which seems to explain this:

                   

                  http://stackoverflow.com/questions/5220999/how-to-handle-javascript-onorientationchange-event-inside-uiwebview

                   

                   

                  This did work: onresize


                  I discovered that responding to the 'onresize' event works, and so I added some Javascript code to the webviewer which responds to a resize of the page content.

                   

                  I discovered that, in order for this to work, I had to actually have some 'visible' page content displaying in the webviewer.  I chose to use a small amount of text with color matching the background of the webviewer object so as to be 'visible' with respect to the webviewer, though not visible to the eye.  This requirement of visibility also meant that I could no longer place the webviewer offscreen, i.e. to the right of the layout edge -- the webviewer had to be positioned on the layout, and of sufficient size, such that its content would be present.  Without both of these requirements being met, the resize event did not seem to trigger upon device rotation.  Presumably, the webviewer is smart enough to recognize that, without displayed content, no resize is necessary when the device orientation changes.

                   

                  Thus:

                   

                  At this point, we are able to trigger an FM script based on device rotation for both of our webviewer cases defined above: Awake, Asleep.

                   

                   

                   

                  Mitigating dual invocations of the target script:

                   

                  The last item to clean up was the fact that the target script was generally being called two times in a row for every orientation change of the device.

                   

                  I worked on this for a while, first seeing if I could utilize only the onresize event to detect device rotation, but the onresize event alone was not sufficient to catch all rotations.

                   

                  I also tried making the Javascript code more intelligent, i.e. self-aware enough to recognize if it had already fired off the target script in response to the last orientation change.  I'm not convinced that this can't be done, but in my attempts to do this, the Javascript code was beginning to look unnecessarily complex and I didn't like that, and moreover I knew that I could solve this issue from within the FileMaker script by tracking the last detected orientation in a global variable.

                   

                  As much as I would have liked to have everything self-contained within the Javascript, I opted to solve this issue on the FileMaker end using a global variable.

                   

                  Note: One of the issues that makes this particular part of the problem tricky is that state data stored in a Javascript global variable is lost every time the webviewer refreshes.*

                   

                   

                  --  That's pretty much everything.  Below are just a few extra references and elaborations  --

                   

                   

                   


                  Regarding the actual Javascript used to trigger the FileMaker script:

                   

                  The Javascript that triggers the FM script is nothing more than something like:

                   

                      document.location = varFmUrlValue;

                   

                  where:  varFmUrlValue  is the well-formed FMP URL that references our target script.

                   

                  The references I include below should tell you everything you might need to know about proper construction of the FMP URL.

                   

                  In my demo file, I use the following snippet of code to launch the URL:

                   

                     "document.location = 'fmp://$/" & GetAsURLEncoded( Get( FileName ) ) & ".fmp12?script=" & GetAsURLEncoded ( callbackScript ) & "&param=' + varParam;¶"

                   

                  where:

                   

                      varParam   was not essential; it was something I defied elsewhere in my Javascript code to help me debug/investigate the Event triggering.

                   

                      callbackScript   is defined earlier within the first part of the encosing Let function where the above snippet occurs.  Its value is the name of the FM script to be invoked.

                   

                   

                   

                   

                  * Regarding storing state data in a webviewer:

                   

                  Since an awakened webviewer re-runs its Javascript code, any (Javascript) variable initialization gets reinvoked.

                   

                  The consequence of this is that if you are tracking state/status information in a Javascript global variable, it seems that you lose this when the webviewer is refreshed.  ( If someone knows otherwise, please let me know! )

                   

                   

                  Example:

                   

                  I first ran across this with a 10-key numeric entry interface that I rendered in a webviewer on Go.  I did this to avoid having the user deal with the native iPad keyboard, as the data was strictly numeric entry, and a nice large 10 key interface was much more intuitive for our users.  Rather than build the 10-key UI natively in FM, I opted to use a webviewer to render the 10-key interface because the touch response was *much* snappier than waiting for FM to run a script in response to a button press event on the iPad screen.  As the releases of FM Go have progressed, the response time for clicking a button to run a script has improved considerably, making this technique less essential (though the webviewer still provides a snappier response).  In any event:  the webviewer used a Javascript local variable as a buffer to store the user input.  I found that this was being lost each time the webviewer was refreshed.  My workaround to this was to use HTML5 client side storage to 'backup' my buffer value so that I could retrieve it if/when the webviewer was refreshed.  This worked fine, but then when I upgraded from iOS4 to iOS5, I noticed that we seemed to lose the ability to use the client side storage from within a data URL-based webviewer (it became a security exception).

                   

                  One reason why I include this long side note is to illustrate the fact that when we hook into some of these cool HTML/webviewer/Javascript techniques, we do have to be careful and consider what might change with a simple OS update.  Certainly not all, but some, features may come, go, or change -- in this case, the client side storage no longer worked after an OS upgrade.

                   

                   

                   

                   

                  References for FMP URL protocol, especially combined with a webviewer:

                   

                      http://www.filemaker.com/12help/html/sharing_data.16.7.html

                   

                      http://buzz.beezwax.net/2012/04/21/native-web-2-0-controls-in-filemaker-12-layouts

                   

                      https://fmdev.filemaker.com/message/87429

                  • 7. Re: Just For Fun:  Triggering a script on iPad rotation
                    BeatriceBeaubien

                    Thank you for the details, Steve.

                     

                    Best wishes,

                     

                    Beatrice

                    • 8. Re: Just For Fun:  Triggering a script on iPad rotation
                      ebloom29

                      Steve,

                      Very helpful, I was able to play around and trigger a layout change.  Worked as expceted.

                      Thanks again.