R&D – Mobiscroll Blog | Design, UI and UX for Successful Products https://blog.mobiscroll.com Building products and services for mobile and web? We're writing about how to stand out and make products that help people. Fri, 19 May 2017 10:36:07 +0000 en-US hourly 1 https://wordpress.org/?v=4.7.5 Annoying iOS Safari input issues with workarounds https://blog.mobiscroll.com/annoying-ios-safari-input-issues-with-workarounds/ https://blog.mobiscroll.com/annoying-ios-safari-input-issues-with-workarounds/#comments Wed, 24 Jun 2015 09:30:55 +0000 http://blog.mobiscroll.com/?p=1552 ios_safari

Applications, either for mobile, tablet and beyond rely mostly on input from the user through forms and although the tools have been around for some time now, to create forms that both improves the UX and works properly is still a tough grind.

For instance in the case of a date picker our goal is to restrict the user to enter the date in a certain format. The user is presented with either entering the date in a given format or choosing a date from a predefined set of values. Also there are cases when to the user is given the opportunity to enter the data in a desired format.

Mobiscroll controls are enhancing data entry and usually don’t allow the user to directly access the fields, the input is set to read-only, this way the chance of error is eliminated. To alleviate the problem of figuring out the multiple possible-entries a couple of event listeners are attached and the UI widget is being shown when the user interacts with the form.

There are 3 events which may trigger the opening of the Mobiscroll widget:

  • touchend (only if no or very little movement has occurred between touchstart and touchend) – Opens the widget when tapping the form input on a touch screen.
  • click – Opens the widget on pointer click (desktop, or hybrid devices).
  • focus – Opens the widget when the navigation between page elements is done with keyboard or assistive technologies (VoiceOver, TalkBack…).

We would like to share with you the issues we encountered and the solutions we have came up with.

The virtual keyboard opening issue

While setting a form input read-only should prevent the virtual keyboard (or other native controls, like a datepicker) from opening, there are some devices, where this still happens:

  • Some Android 2.3 devices (seen on a HTC Desire HD browser, although not consistently reproducible)
  • Older versions of Firefox OS
  • Older versions of Amazon Silk browser
  • iOS8 Safari

Since the mentioned first three events have a very low market share are not really a concern. On the other hand the iOS8 Safari issue is a real deal breaker.
The issues we have found are listed below:

  • When a read-only text input field gets focus, the virtual keyboard does not appear, but a toolbar at the bottom of the screen appears (with < and > buttons, and an extra ‘Done’ button on iPhone/iPod).
  • When a read-only date or time input gets focus, surprisingly the native date or time picker pops up and even allows editing the value of the input. This seems like a major bug, we submitted a ticket to apple when 8.0 was released, but neither an answer, nor a fix was delivered since than.

The virtual keyboard opening happens on the focus event, so one approach to solve the issue is to somehow prevent the firing of the focus event. Calling preventDefault on the mousedown event is well supported way of doing this.

One issue that is still unsolved is that when closing the Mobiscroll widget, the input field should regain the focus from the point of view of accessibility. Unfortunately this action activates the described buggy behavior on iOS8 Safari.

Touch events failing on iOS8

Another bug we discovered during our testing process is that under certain circumstances touch events are simply not firing on form input fields in the case when the app:

  • is a web application and added to the Home Screen
  • has the <meta name=”apple-mobile-web-app-capable” content=”yes”> to hide the native browser controls when opened from the Home Screen

This was tested by using plain javascript code:



    
        
        
        
        
        
        iOS Readonly Input Test

        
    
    

        
        
        
        
        

Fortunately we discovered that this issue is not present in the web browser, nor in the WebView.

The mousedown, mouseup, focus and click events are still firing. So the Mobiscroll widget will still appear, as it has the click event attached, but with the ~300 milliseconds latency of the emulated mouse events, making the app feel less snappy.

While testing the same issue with Ionic Framework we discovered that the content is scrolled using a custom scroll engine instead of the native browser scroll. Also in this scenario even the click events are not consistently fired, resulting in a frustrating user experience.

At this time the only possible workaround is to get rid of the actual input field, and use a div element that is styled to look like an input field and if needed, additionally store the value in a hidden input field. The content (“value”) of the div element should also be maintained.
Below we give you a sample implementation with Mobiscroll:

12/24/2014

We know that fixing technical issues on mobile is annoying so we expect that in the near future these issues will be resolved and we can take our form inputs for granted.
Until than let’s appeal to these handy practices.


What is your experience or frustration with browser inconsistencies? Let us know in the comments below!

]]>
https://blog.mobiscroll.com/annoying-ios-safari-input-issues-with-workarounds/feed/ 1
Working with touch events https://blog.mobiscroll.com/working-with-touch-events/ https://blog.mobiscroll.com/working-with-touch-events/#comments Wed, 16 Apr 2014 14:23:51 +0000 http://blog.mobiscroll.com/?p=1421 touchLast month I was attending the jQuery Europe conference in Vienna with the Mobiscroll team.
There was a session called Getting touchy which gave an insight into touch events and talked about why we need them.
There is a lot of ground that the presentation covers, so make sure to check out the slides. I would like to build on top of it and share my experience on the topic.

So why do we need touch events?

We don’t always need to use touch events in our apps or websites. Turns out that mostly we can get away of using the regular click events because mobile browsers emulate the mouse events rather well.
While emulation works in many cases, the functionality is limited.

The usual problems are:

  • Delayed event dispatch: mouse events are usually fired with a delay, which makes the app feel unresponsive.
  • Mousemove doesn’t track, only a single emulated mousemove event is fired. This makes impossible to make complex UI interfaces with gestures.

There are a ton of resources on the web targeting these issues, so I’m not reinventing the wheel here, I will just share my own personal experience on how I combined and extended different solutions to match our needs while building Mobiscroll.

The click delay

As you probably know, there is a delay between the actual tap and the firing of the click event. I’m not going into details on why this happens, you can read about it in the slides mentioned before.

A common technique to prevent the delay is to bind the handler to both touchend and click events. The challenge here is to prevent the so called “phantom click”, meaning that if your handler was called on touch end, don’t execute it again when the click event is emulated by the browser. The proposed solution here is to call e.preventDefault() either on touchstart or touchend which will prevent the firing of emulated mouse events.

However I find this problematic because:

  • Calling it on touchstart will kill native page scroll
  • Calling it on touchend will kill momentum scroll on some devices
  • It does not prevent at all emulated events on Android 4.x stock browsers

The solution we are using in Mobiscroll does the following:

  1. Attach touchstart, touchend and click events
  2. Remember start and end coordinates
  3. Call the handler e.preventDefault() on touchend, but only if movement was less then 20px in any direction (so the user did not have the intention to scroll)
  4. Set a flag to prevent executing the handler again in the emulated click event
  5. Start a timeout which will clear the flag in case when the click event was not emulated

Putting everything together:

var startX,
    startY,
    tap;

function getCoord(e, c) {
    return /touch/.test(e.type) ? (e.originalEvent || e).changedTouches[0]['page' + c] : e['page' + c];
}

function setTap() {
    tap = true;
    setTimeout(function () {
        tap = false;
    }, 500);
}

element.on('touchstart', function (ev) {
    startX = getCoord(ev, 'X');
    startY = getCoord(ev, 'Y');
}).on('touchend', function (ev) {
    // If movement is less than 20px, execute the handler
    if (Math.abs(getCoord(ev, 'X') - startX) < 20 && Math.abs(getCoord(ev, 'Y') - startY) < 20) {
        // Prevent emulated mouse events
        ev.preventDefault();
        handler.call(this, ev);
    }
    setTap();
}).on('click', function (ev) {
    if (!tap) {
        // If handler was not called on touchend, call it on click;
        handler.call(this, ev);
    }
    ev.preventDefault();
});

Working with gestures

When we started building the Listview, we imagined a widget with heavy gesture support. And this is where it shines.

slide-16-touch-gestures

We needed support of following:

  • Use native vertical scrolling
  • Handle left and right swipe gestures, disable page scroll during swipe gestures
  • Handle tap
  • Handle tap and hold (activate sort mode)

Native scroll

If we want to keep using native touch scrolling, this involves that we cannot call e.preventDefault() on any of the touch events unconditionally.

Horizontal swipe

We need to listen to the touchmove event and decide on the fly whether it will be a vertical or horizontal swipe. If it's horizontal swipe, kill the page scroll with calling e.preventDefault(). The following thresholds are used:

  • If the horizontal movement is more than 7px, we consider it a swipe
  • If the vertical movement is more than 10px, we consider it a scroll

Tap

We need to decide if the user is tapping on a list item or intends to swipe or scroll. If movement was less than 5px in any direction tap is being honored. We also need to take care of the duplicate firing of the events, so a flag is being set - similar to the one we used for the click delay.

Tap & hold

On touchstart we will start a timer which activates the "sort/reorder" mode after a delay. This timer is being reset in case of a scroll, swipe, or touchend.

Let's take a look at our event handlers. Also note that we attach the touch events and the mousedown to the element itself, while the mousemove and mouseup events are attached to the document element dynamically and removed at the end. That is because they behave differently: the touchmove and touchend will still be fired if the finger leaves the element, while the mousemove and mouseend event will not fire once the mouse pointer has left the element.

var touch,
    action,
    diffX,
    diffY,
    endX,
    endY,
    scroll,
    sort,
    startX,
    startY,
    swipe,

function testTouch(e) {
    if (e.type == 'touchstart') {
        touch = true;
    } else if (touch) {
        touch = false;
        return false;
    }
    return true;
}

function onStart(ev) {
    if (testTouch(ev) && !action) {
        action = true;

        startX = getCoord(ev, 'X');
        startY = getCoord(ev, 'Y');
        diffX = 0;
        diffY = 0;

        sortTimer = setTimeout(function () {
            sort = true;
        }, 200);

        if (ev.type == 'mousedown') {
            $(document).on('mousemove', onMove).on('mouseup', onEnd);
        }
    }
}

function onMove(ev) {
    if (action) {
        endX = getCoord(ev, 'X');
        endY = getCoord(ev, 'Y');
        diffX = endX - startX;
        diffY = endY - startY;

        if (!sort && !swipe && !scroll) {
            if (Math.abs(diffY) > 10) { // It's a scroll
                scroll = true;
                // Android 4.0 will not fire touchend event
                $(this).trigger('touchend');
            } else if (Math.abs(diffX) > 7) { // It's a swipe
                swipe = true;
            }
        }

        if (swipe) {
            ev.preventDefault(); // Kill page scroll
            // Handle swipe
            // ...
        }

        if (sort) {
            ev.preventDefault(); // Kill page scroll
            // Handle sort
            // ....
        }

        if (Math.abs(diffX) > 5 || Math.abs(diffY) > 5) {
            clearTimeout(sortTimer);
        }
    }
}

function onEnd(ev) {
    if (action) {
        action = false;
        
        if (swipe) {
            // Handle swipe end
            // ...
        } else if (sort) {
            // Handle sort end
            // ...
        } else if (!scroll && Math.abs(diffX) < 5 && Math.abs(diffY) < 5) { // Tap
            if (ev.type === 'touchend') { // Prevent phantom clicks
                ev.preventDefault();
            }
            // Handle tap
            // ...
        }

        swipe = false;
        sort = false;
        scroll = false;

        clearTimeout(sortTimer);

        if (ev.type == 'mouseup') {
            $(document).off('mousemove', onMove).off('mouseup', onEnd);
        }
    }
}

element
    .on('touchstart mousedown', onStart)
    .on('touchmove', onMove)
    .on('touchend touchcancel', onEnd)

Problems that are still unsolved

Android ICS

On Android ICS if no preventDefault is called on touchstart or the first touchmove, further touchmove events and the touchend will not be fired. As a workaround we need to decide in the first touchmove if this is a scroll (so we don't call preventDefault) and then manually trigger touchend - see the code above.

Windows Phone

In WP8 there is no way to prevent native scroll on the fly. To be able to listen to touch events, you need to set the following css property:

touch-action: none;

However this will kill all default behavior, like native page scroll. Fortunately this can be fine tuned, so for the Listview we use the following:

touch-action: pan-y;

Which tells to browser that vertical swipe will be handled by the browser, while our code will take care of the horizontal swipe. Unfortunately sorting won't be working, because it will not prevent native scroll while dragging an element. So in WP8 the only way to implement sorting is to use a "sort handle" element which has the touch-action: none; rule applied. So when the user "picks" up an item from the sort handle, we know he intends to reorder.

Firefox Mobile

In Firefox Mobile the native scroll can be killed only if preventDefault() is called on the touchstart event. Unfortunately at touchstart we don't really know if we want scroll or not. This has two consequences:

  • sort will work with the above mentioned sort handle only (by calling preventDefault() on touchstart if dragged by the handle)
  • while swiping an item left or right vertical scroll will still work

These issues can easily disappear in upcoming browser updates or releases, until then we need to come up with workarounds.

What are your hacks and workarounds for dealing with complex gestures? Let us know in the comment section below.

]]>
https://blog.mobiscroll.com/working-with-touch-events/feed/ 5
How to integrate your UI Widgets with AngularJS https://blog.mobiscroll.com/how-to-integrate-your-ui-widgets-with-angularjs/ https://blog.mobiscroll.com/how-to-integrate-your-ui-widgets-with-angularjs/#comments Tue, 18 Mar 2014 14:48:16 +0000 http://blog.mobiscroll.com/?p=1245 When building apps we tend to create reusable code, which helps us in the long run, makes our code easier to understand and all in all cleaner. We like to build modular things.
This modularity that we’re used to shows itself not just in the units that we write but the in UI and elements that we build. Sometimes it makes sense to think ahead, and create our widgets for re-usability.

That is how the Mobiscroll UI Library is built. We created our AngularJS integration plugins for the components and we are going to show you how you can do it as well.

A basic understanding of AngularJS is needed. For more on how to get started with Angular head on over to Angular Tutorials.

Angular JS

We are going to go through the steps you need to take:

A short intro

Integrating your widgets

A short intro

What are directives and how to use them?

We can think of angular directives as custom html attributes or elements that define behavior for the element. For example if  we want an image on a page that changes depending on a certain condition we can just write:

Whenever the user expression value changes, the image source will change too. The directive that is responsible for this behavior is the ngSrc. There is a set of built in directives that comes with Angular, like the ngRepeat, ngModel, ngView and so on. For more info and examples on the built in directives see the Angular documentation.

Why do we need them?

Custom directives let you define how the element should behave. Also the element can be the directive itself! Instead of creating the above img with ngSrc, you could create a directive called myuser and write it as follows:

Of course you will have to implement the actual behavior for the directive, but as you will see it’s not difficult at all.

Integrating your widgets

So you have your tool belt packed with your custom UI elements and need to plug them into your latest shiny app that you’re building. The question is: how to make them work the Angular way?

Lets take the Mobiscroll Calendar as an example:

Since the calendars logic is already implemented inside the mobiscroll control, we will focus on the following:

Two way - Photo by Phil Gilbert

Defining a directive

Defining a directive is relatively easy. Inside a module you just give it a name, set dependencies if any and return an object with some basic options (this is called the directive definition object). We will also use a service called $parse (more on that later).

Let’s call our directive calendar in the mobiscroll module:

angular.module('mobiscroll', [] /* no dependencies */)
   .directive('calendar', ['$parse', function ($parse) {
       return { /* the Directive Definition Object */ };
   }]);

For our calendar the directive definition object will contain the following:

{
   restrict: 'A',
   link: function (scope, iElement, iAttrs) {
      /* here comes the link function */ 
   },
   controller: ['$scope','$element', '$attrs', function($scope, $element, $attrs) {
      /* here comes the controller function */
   }]
}

The restrict: 'A' option will tell angular that our directive is an attribute.

Initializing the mobiscroll calendar

The link function will be called once for each element. This is where most of the custom logic comes regardless if we integrate a control into angular or create a new directive from scratch. For our calendar directive the initialization will take place in here. It is straight forward, since the link function gets the HTML element (iElement) as parameter. We only have to call the mobiscroll init.

Binding to a scope variable

Binding to a scope variable means two things:

  • when the calendar changes, the scope variable should be updated, and
  • when the scope variable changes the calendar should be updated

So one thing we also need to set up in the linking phase is when the element changes its value (you select a date on the calendar) we update the scope variable.

We do this by attaching a handler to the elements change event:

...
link: function (scope, iElement, iAttrs) {
     // initialize mobiscroll on the element
     iElement.mobiscroll().calendar();
     // add onChange handler
     iElement.on('change', function () {
          scope.$apply(function () {
               var getter = $parse(iAttrs['calendar']);
               var setter = getter.assign;
               // check for multiselect option
               var inst = iElement.mobiscroll('getInst');
               if (inst.settings.multiSelect || inst.settings.selectType == 'week') {
                    setter(scope, iElement.mobiscroll('getValues'));
               }
               else {
                    setter(scope, iElement.mobiscroll('getDate'));
               }
          });
     });
 },
...

Inside the link function the iAttrs parameter holds all the attributes the element has. The calendar attribute (iAttrs["calendar"]) will hold the scope variable the component should be bound to. The parse service will actually parse the expression and return a function. If the expression is assignable it will have an assign function. Here its name is setter, and calling it will set the variable.  The mobiscroll calendar has different methods to get the value based on the multiselect option. As you can see in the handler, we have to check that option first and get the value accordingly.

Another notable thing is that all the logic is wrapped around the $apply function. This is required for angular, to get notified about the changes made inside the function.

The next step is to notify the calendar if the variable changes. For this, we use the controller function and the scopes $watch function. This way we can register a listener to be executed when our variable changes:

$scope.$watch(
 function () {
    var getter = $parse($attrs['calendar']);
    return getter($scope);
 },
 function (newValue) {
    if (newValue) {
       var inst = $element.mobiscroll('getInst');
       if (inst.settings.multiSelect || inst.settings.selectType == 'week') {
          $element.mobiscroll('setValues', newValue);
          if (newValue.length) {
             $element.mobiscroll('setDate', newValue[0]);
          }
       }
       else {
          $element.mobiscroll('setDate', newValue);
       }
       $element.val(inst.val);
    }
 },
 true);

The first function returns the value that should be watched. The second function gets called when the value changes. Here we also have to distinguish between the multiselect and single select modes of the mobiscroll calendar. The last parameter is a boolean, which tells angular to compare objects for equality rather than for reference.

At this point we should have a working calendar directive already.

Passing properties

The mobiscroll calendar has many properties that can enhance user experience: display, theme, minDate, maxDate and so on…

Passing configuration options is easy. In the linking function we have access to all attributes of the element, so passing the initialization option as an attribute is a convenient way.

...
link: function (scope, iElement, iAttrs) {
   // prepare the initialization object for mobiscroll
   var initOptS = iAttrs['mobiscrollOptions'] || '{}';
   var initOpt = scope.$eval(initOptS);
   // initialize mobiscroll on the element
   iElement.mobiscroll().calendar(initOpt);

   iElement.on('change', function () {
...

Here we used the $eval function to create the mobiscroll initialization object. The $eval uses the parse service that we mentioned earlier and evaluates the expression on the current scope. This way we can pass the mobiscroll options in the mobiscroll-options attribute like this:

Handling events

Mobiscroll event handlers are functions that you pass. So where are these functions defined?
When we pass a setting like onSelect, Angular treats it just as any other properties. It evaluates it against the current scope. This means when we define a function in the current scope we can pass it as an event handler to mobiscroll.

However there is a small detail you should be aware of. When the mobiscroll event handlers are called, they are outside of the Angular world. If we change a scope variable inside a handler like this, Angular will not be notified about it and the change will not propagate. To overcome this the simplest solution is to put the event handler inside an $apply() function.

$scope.calendarSelected = function() {
   // your handlers logic here, for example:
   $scope.someVariable = "Date Selected!";
}
$scope.calendarSelectedHandler = function(){
   // wrapping the handler in the apply
   $scope.$apply(function(){
      $scope.calendarSelected();
   });
}

You can also read about handling events in the mobiscroll documentation.

Usage Examples

After writing our Angular directive, here come a couple of examples on how we could use it.

Using the calendar in inline mode and binding it to the “mybirthday” variable.

To select multiple dates we should just pass the multiselect option as true:

Adding event handlers from the scope:

Inside your controller:

...
$scope.dayChanged = function(){
   $scope.wasDayChanged = true;
};
$scope.dayChangedHandler = function(){
   $scope.$apply(function(){
      $scope.dayChanged();
   });
};
...

For more info on our AngularJS integration plugins please see our documentation.
This is the main idea behind the Mobiscroll AngularJS Integration. Let us know how and what do you plan to integrate next?


Did we leave something out, do you have anything to add, comment? Please let us know in the comment section below!

]]>
https://blog.mobiscroll.com/how-to-integrate-your-ui-widgets-with-angularjs/feed/ 3
Cross Platform Mobile Development – Things learned from building UI used by developers across 170+ countries https://blog.mobiscroll.com/cross-platform-mobile-development-things-learned-from-building-ui-used-by-developers-across-170-countries/ https://blog.mobiscroll.com/cross-platform-mobile-development-things-learned-from-building-ui-used-by-developers-across-170-countries/#comments Fri, 21 Jun 2013 14:08:35 +0000 http://blog.mobiscroll.com/?p=1045 Building a Mobile UI Library that is appealing, works across multiple platforms and performs well is not easy. It involves a lot of experimenting and testing. Bringing it to the tool-belt of tens of thousands of developers across the globe is even harder.

We’re going to share a couple of things about how we build and test our controls hoping it is helpful and gives you ideas on how to improve your development approach.

The good news is that the era of IE6 (and 7 (and 8)) is over. We don’t need four images anymore for rounded corners, no more 1px width gradients, no more hacks for min-width, min-height, and I could go on.

The bad news is that instead of testing on ca. 5 browsers running on 3 operating systems, the web is accessible nowadays from everywhere… phones, tablets, smart TVs, cars etc. running dozens of different browsers on different operating systems. So if you target the largest possible range of devices (and we are with Mobiscroll), you are in trouble.

Read on for a glance at how we are doing things here at Mobiscroll.

Our test devices

There is a CSS property for that. But is it supported?

When we decide to implement something, first I want to know where will it work. There are a lot of resources online helping you in it. My favorite is caniuse.com, which summarizes the HTML / CSS /JS API support across different browsers. There is a “known issues and resources” section for every feature.  It also has up to date usage statistics, so you can see approximately how much percent of the browsers support it.

quirksmode.org is a good resource as well.

Testing, testing and some more testing

As I said earlier: testing is difficult. Desktop browsers are not much of a problem and it is very rare that the same browser behaves differently on another op. system. The problem is caused by touch devices. And as they are the primary focus, sometimes they can cause us headaches. It is physically (and financially) impossible to test your product on all possible devices. But in most cases it is also unacceptable to test only on emulators / simulators instead of real handheld devices.

Let’s take a look at the most popular operating systems

iOS

The iOS simulator is good for basic testing. Unfortunately the official simulator  is not running on Windows. At this time I did not find any viable solution for running an iOS simulator on Windows (open for suggestions).

So I stick with the physical devices. Currently we test Mobiscroll on iOS5 and 6 (iOS7 will follow shortly), this covers most devices. The tested browsers are stock Safari and Chrome. Chrome for iOS also uses the Safari engine, so different behavior is very rare.

Android

The Android emulator shipped with the SDK is slow as hell. For Mobiscroll testing I use it very rarely, only show / hide can be tested, the scrolling of the wheels is very laggy.

A better solution is the Android x86 project. I use VirtualBox for running different Android versions (2.3, 4.0, 4.2), it runs very smooth so it’s useful for testing.

Of course we test on physical devices as well, the more the better. To cover more Android versions, I rooted a Samsung Galaxy S Plus and I got to a point when I can easily switch between different ROMs (It took some time to figure it out, trying different versions of ClockWorkMod, struggling with bootloops, backups not restoring, etc.). The ultimate resource for working with custom ROMs is the XDA Developers forum. We test on Google Devices (Nexus) and on others as well (Samsung, HTC).

Android brings some other difficulties as well compared to iOS: vendors may add their own proprietary UI modifications on top of Android which sometimes shows differences. What works well on stock Android 2.3’s browser, may not work or may be laggy on the same device with HTC’s Sense UI or Samsung’s TouchWiz UI.

Windows Phone

Windows Phone joined the game with Windows Phone 8, which runs IE10 and finally supports touch events. SDK with emulator is also released. Unfortunately it runs on Windows 8 only. Some claim they managed to run the WP8 emulator on Windows 8 running inside WMvare, but it’s to much of a hassle. So we ‘sacrificed’ a workstation by putting Windows 8 on it, and we were able to test on the WP8 Emulator.

At this time we don’t have any touch devices running Windows 8 or Windows Phone 8, so I don’t have experience with testing. But if Levi is nearby an electronics store, he never misses to run in and open up demo.mobiscroll.com on a testable WP device.

Blackberry

Blackberry doesn’t have a big market share, so we don’t put too much effort on testing. But from time to time we switch on our BlackBerry Torch 9810 (thanks again to Jim Ing) to ensure that nothing is broken.

Blackberry does supply simulators but last time I tried, I had trouble running it on Windows 7.

Other

Of course the list is not finished here… There are many other operating systems and browsers that may not have relevant market share, but some of them are worth to take a look at. We also test on Amazon Kindle, we have the Firefox OS add-on installed on Firefox, and even tried Tizen, which also provides an SDK with emulator.

Tips & tricks

  • Are there animation glitches and flickering on webkit browsers? Try adding the “-webkit-backface-visibility:hidden;” CSS property on some of the related html elements (sometimes the animated element, sometimes the parent). “-webkit-perspective: 1000;” is also needed sometimes. But be careful! Using it sometimes slows things down, or even brakes the animation. (Taken from here)
  • Animations are sluggish after showing / hiding an element or changing device orientation? Or elements are rendered incorrectly (extra borders, remaining shadow…)? Trigger a DOM redraw (It might slow things down in some cases):
 
elm.style.display = 'run-in';
setTimeout(function () {
elm.style.display = '';
}, 0);
  • Sometimes I end up in dead ends. If I fix it in one browser, it’s broken in the other. If I fix it in the second one, it brakes again in the first one. Often the best solution is to come up with a totally different approach. Be creative! Also there is a good chance that it won’t work in a third browser…
  • Never assume that if something works in an older version of a browser, it will also work in a newer version.

So…

As you see, developing cross platform can be a real pain sometimes. It can happen that we develop something in one day and spend the next 2 weeks fine-tuning it to make it work everywhere possible.

Is it going to be better? Sure. But if we want to use the latest technologies I don’t think we can avoid these problems. Since Mobiscroll brings out the most from the mobile browsers, that’s what we do…

Feel free to share your experience and ideas in the comment section below.

]]>
https://blog.mobiscroll.com/cross-platform-mobile-development-things-learned-from-building-ui-used-by-developers-across-170-countries/feed/ 3
Mobiscroll positioning https://blog.mobiscroll.com/mobiscroll-positioning/ https://blog.mobiscroll.com/mobiscroll-positioning/#comments Thu, 17 Jan 2013 10:32:30 +0000 http://blog.mobiscroll.com/?p=807 What you need

Time needed   About 15 minutes of time
Tools needed   A text editor and a browser
Knowledge needed   Basic HTML, CSS and jQuery knowledge

Mobiscroll started out as Date & Time picker simulating native UI controls (like datepicker, timepicker, spinning list etc.) of an OS, which were not possible to invoke in web apps, at least not on every platform.

For some time it supported only one display mode, a modal popup, which appeared in the center of the viewport. But native controls are positioned differently on different platforms:

Timepicker on Android 2.2

Timepicker on Android 2.2

Spinning list on iPhone

Spinning list on iPhone

Datepicker on iPad

Datepicker on iPad

So quickly the most requested feature was to be able to position Mobiscroll in different ways.
The first improvement we introduced was the ‘inline’ display mode, giving the possibility to developers to position the scroller where and how they want it (v2.0). But we wanted more. We wanted Mobiscroll to be extremely easy to use and take the burden off the poor developers.

So we decided to implement the following 3 new modes: ‘bottom’, ‘top’ and ‘bubble’. Position ‘top’ and ‘bottom’ imitates the iPhone’s behavior, while ‘bubble’ imitates the iPad’s behavior, where the picker appears in a bubble pointing at the triggering element – this is very useful on larger screens, like tablets or desktops, because you don’t have to move your hand or cursor in a different part of the screen, remaining close to the triggering touch or click.

How it’s made

In the following section I’ll explain how the positioning it’s done in a small tutorial.

The HTML

We will create a few divs and some buttons. #elm is the positioned element, #anchor is the relative element for ‘bubble’ positioning, #controls is a container for the triggering buttons.




    
    
    Positioning
    
    
    



    

The CSS

The body has a large, 2000px height, so we can test our positioning on a scrollable page.

The control container has fixed positioning aligned to left, so it is always visible, no matter where the page is currently scrolled.

body {
    height: 2000px;
    padding: 0;
    margin: 0;
    font-family: arial, verdana, sans-serif;
    font-size: 12px;
}

#elm {
    position: absolute;
    z-index: 10;
    display: none;
    width: 300px;
    height: 100px;
    padding: 20px 0;
    background: #ddd;
    text-align: center;
}

#controls {
    position: fixed;
    top: 50%;
    left: 0;
    height: 138px;
    margin-top: -69px;
    background: #aaa;
}

#controls button {
    display: block;
    width: 60px;
    margin: 10px;
}

#anchor {
    width: 150px;
    height: 30px;
    background: #aaa;
    position: absolute;
    top: 500px;
    right: 40px;
}

Calculating the position

The positioning is done by clicking on the buttons in the controls container. The buttons have the same .pos css class, we will use that to attach the click handler. We will use the clicked button’s id, to decide where to position the element.

We also need to calculate the dimensions of the different elements (see the figures below). Note that we’re using the outerWidth and outerHeight jquery functions, which also includes the padding and borders into the dimensions.

At the end of the handler we apply the calculated values to the element using jquery’s css function.

    $('.pos').click(function() {
        var top,
            left,
            width = '',
            elm = $('#elm').css({ width: '' }).show(), // Reset the width, and show the element
            anchor = $('#anchor'),
            scrollTop = $(window).scrollTop(),
            viewPortWidth = $(window).width(),
            viewPortHeight = $(window).height(),
            scrollerWidth = elm.outerWidth(),
            scrollerHeight = elm.outerHeight(),
            anchorWidth = anchor.outerWidth(),
            anchorHeight = anchor.outerHeight(),
            anchorOffsetTop = anchor.offset().top,
            anchorOffsetLeft = anchor.offset().left;

        switch (this.id) {
            case 'center':
                // ...
                // See calculations in the next section 
                break;
            case 'top':
                // ...
                // See calculations in the next section
                break;
            case 'bottom':
                // ...
                // See calculations in the next section 
                break;
            case 'bubble':
                // ...
                // See calculations in the next section 
                break;
        }
 
        // Apply the calculated values to the element
        elm.css({
            width: width,
            top: top,
            left: left
        });
    });

1. Centered

The element is positioned at the center of the viewport.

document
viewport
scroller
scrollTop
viewPortHeight
scrollerHeight
viewPortWidth
scrollerWidth

 

top = scrollTop + (viewPortHeight - scrollerHeight) / 2;
left = (viewPortWidth - scrollerWidth) / 2;

2. Top

The element is displayed at the top of the viewport in full width.

document
viewport
scroller
scrollTop
width = '100%';
top = scrollTop;
left = 0;

3. Bottom

The element is displayed at the bottom of the viewport in full width.

document
viewport
scroller
scrollTop
viewPortHeight
scrollerHeight
width = '100%';
top = scrollTop + viewPortHeight - scrollerHeight;
top = top < 0 ? 0 : top // Don't go out of screen
left = 0;

4. Bubble

The element is positioned relative to an anchor element (by default the input element on which the scroller was called), with an arrow pointing at this element. Positioning decides to display  the element above or below the anchor element.

document
viewport
scroller
anchor
scrollTop
viewPortHeight
scrollerHeight
anchorHeight
viewPortWidth
scrollerWidth
anchorWidth
anchorOffsetLeft
anchorOffsetTop

 

top = anchorOffsetTop - scrollerHeight - 3; // above anchor element
left = anchorOffsetLeft - (scrollerWidth - anchorWidth) / 2;

// If there’s not enough space above the anchor element, or anchor element is out of screen, position below anchor element:
if ((top < scrollTop) || (anchorOffsetTop > scrollTop + viewPortHeight)) {
    top = anchorOffsetTop + anchorHeight + 3;
}

// Keep a minimum 20px margin from left and right:
left = left > (viewPortWidth - scrollerWidth - 20) ? (viewPortWidth - scrollerWidth - 20) : left;
left = left < 20 ? 20 : left;

Hiding the popup

The positioned element contains a #hide button, so we need to attach a click event handler to the button, to hide the element on button click.

$('#hide').click(function() {
    $('#elm').hide();
});

Summary

Download the Source Files
Play with it on jsFiddle

The actual code that is used for positioning in Mobiscroll can be found in the position function of mobiscroll.core.js

More eye-candy

On the top of that we created the animation module, to make it more awesome. Beside the slide up / slide down animations known from iOS, we also added pop and fade, and the cool 3d flip and swing effects.

]]>
https://blog.mobiscroll.com/mobiscroll-positioning/feed/ 1
Mobiscroll HTML5 audio experiment https://blog.mobiscroll.com/mobiscroll-html5-audio-experiment/ https://blog.mobiscroll.com/mobiscroll-html5-audio-experiment/#comments Wed, 28 Nov 2012 15:27:00 +0000 http://blog.mobiscroll.com/?p=558 Mobiscroll + Sound

If you ever used the native datepicker on iOS, you probably noticed the sound effect while the wheels are spinning. We tried to add the same effect to Mobiscroll by using the HTML5 audio element. At a first glance it seemed pretty straight forward, as all the modern browsers claim to support HTML5, but because of some restrictions (especially on mobile browsers), we were unable to implement it in a satisfactory way.

The audio element:

<audio preload="auto">
  <source src="scroll.mp3" controls></source>
  <source src="scroll.ogg" controls></source>
</audio>

First approach

Create one audio element and play the sound every time the wheel passes over a value.

var audio = $('audio', dw)[0];
setInterval(function () {
  // ....
  if (p != last) { // Play sound if position changed
    audio.play();
    last = p;
  }
  // ....
}, 100);

Result

  • works fine on desktop browsers (tested on IE9, Chrome 23, Firefox 17)
  • works pretty good on Google Nexus 7

Problems

  • only one sound was played, even if more than one wheel was scrolled at once
  • on iOS6 and Android 2.3 audio.play() had no effect, if the previous play was not finished
  • preload is not possible on iOS and apparently on Chrome for Android, so on the first scroll after page load there was a lag of 1-2 seconds until the sound was played

Second approach

Create 10 audio elements and sequentially call the play method on them, to be able to play multiple sounds at once.

var audio = [];
for (i = 0; i < 10; i++) {
  audio.push($('audio', dw).clone()[0]);
}
var au = 0;
setInterval(function () {
  // ....
  if (p != last) { // Play sound if position changed
    audio[au++ % 10].play();
    last = p;
  }
  // ....
}, 100);

Result

  • as expected, it works fine on desktop browsers
  • surprisingly it kind of works on Kindle Fire’s Silk browser, but sometimes it needed a change of the device’s sound volume to make it work (that’s weird)

Problems

  • still no preload of course
  • no support for multiple sound channels on iOS and Chrome on Android 4.2, at least when the play method is called from javascript
  • very laggy on Android 2.3

Anyway, here’s a video of the behavior on the Nexus 7 tablet

Ideas, questions, suggestions? Let us know what you think in the comment section below.
]]>
https://blog.mobiscroll.com/mobiscroll-html5-audio-experiment/feed/ 4