Skip to content

Instantly share code, notes, and snippets.

@jasoncomes
Last active June 16, 2017 21:31
Show Gist options
  • Save jasoncomes/27af9f74f77b9e82c37a89ad26ac9107 to your computer and use it in GitHub Desktop.
Save jasoncomes/27af9f74f77b9e82c37a89ad26ac9107 to your computer and use it in GitHub Desktop.
When multiple forms or inputs are placed throughout a web page, the native iOS "next" or "previous" arrows jump from input to input. On pages with our widget, the arrows "jump" from the final widget field to the next input element no matter where it is on the page. This jarring to our users and takes users who have selected widget fields to an u…
/*!
MobileMultForm - 1.5
*/
(function($) {
'use strict';
var mobileMultiForm = {
/**
*
* Defaults
*
*/
excludeList: ':input[type=button], :input[type=submit], :input[type=reset], :button, :input[type=hidden]',
get $inputs()
{
return $(':input').not( this.excludeList );
},
get mobile()
{
return this.isMobile();
},
/**
*
* Detect if Mobile Browser
*
*/
isMobile: function()
{
return ( /iPhone|iPad|iPod/i.test( navigator.userAgent ) );
},
/**
*
* Returns active inputs based from the selected input
*
*/
inputsActive: function( $element )
{
var $parentForm = $element.parents('form');
var $inputsActive = ( $parentForm.length ? $parentForm.find( this.$inputs ) : $element );
return $inputsActive;
},
/**
*
* Any part of form or input is in Viewport
*
*/
isInView: function( $element )
{
var docViewTop = $(window).scrollTop();
var docViewBottom = docViewTop + $(window).height();
var elemTop = $( $element ).offset().top;
var elemBottom = elemTop + $( $element ).height();
return ( ( elemTop <= docViewBottom ) && ( elemBottom >= docViewTop ) );
},
/**
*
* Enable all disabled form inputs (Especially EDUWIDGET)
*
*/
removeDisabledInputs: function()
{
this.$inputs.removeAttr( 'disabled' );
},
/**
*
* Resets Inputs
*
*/
resetInputs: function()
{
this.$inputs.filter('[tabindex=-2]').removeAttr( 'tabindex disabled' );
},
/**
*
* Closes Native Keyboard
*
*/
closeInputKeyboard: function( $element )
{
if( $element.is('form') )
{
$element.find( this.$inputs ).blur();
}
else
{
$element.blur();
}
},
/**
*
* Go to Previous Input
*
*/
goPrevInput: function( $element )
{
var $inputsActive = mobileMultiForm.inputsActive( $element );
var $inputPrevious = $inputsActive.eq( $inputsActive.index( $element ) - 1 );
$( $inputPrevious ).triggerHandler( 'mousedown' );
},
/**
*
* If Input(selected) is Empty
*
*/
ifInputEmpty: function( $element )
{
var $options = $element.find('option');
if( $element.is('select') && ( $options.length == 0 || ( $options.length == 1 && $options.filter(':first-child').val() == 0 ) ) )
{
return true;
}
return false;
},
/**
*
* Listener - Window Scroll
*
*/
scrollListener: function( $element )
{
var $parForm = $element.parents('form');
var $elContainer = ( $parForm.length ? $parForm : $element );
$(document).on( 'scroll', function( event )
{
if( ! mobileMultiForm.isInView( $elContainer ) )
{
mobileMultiForm.closeInputKeyboard( $element );
$(this).unbind( event );
}
});
},
/**
*
* Listener - Input Focus
*
*/
focusListener: function()
{
mobileMultiForm.$inputs.on( 'focus', function( event )
{
mobileMultiForm.scrollListener( $(this) );
});
},
/**
*
* Listener - Focus Out Input Timer(500ms Reset)
*
*/
focusOutListener: function()
{
mobileMultiForm.$inputs.on( 'focusout', function()
{
setTimeout( function()
{
if( ! mobileMultiForm.$inputs.is( ':focus' ) )
{
mobileMultiForm.resetInputs();
}
}, 500);
});
},
/**
*
* Listener - Select Input (Prefocus)
*
*/
mouseDownListener: function()
{
this.$inputs.on( 'mousedown', function( event ) {
event.preventDefault();
var $inputsActive = mobileMultiForm.inputsActive( $(this) );
var $allOtherInputs = mobileMultiForm.$inputs.not( $inputsActive, '[tabindex="-1"]' );
$inputsActive.not( '[tabindex="-1"]' ).removeAttr( 'tabindex disabled' );
$allOtherInputs.attr( 'tabindex', '-2' ).attr( 'disabled', 'disabled' );
if( mobileMultiForm.ifInputEmpty( $(this) ) )
{
mobileMultiForm.goPrevInput( $(this) );
}
else
{
$(this).focus();
}
});
},
/**
*
* Init - Mobile Only
*
*/
init: function()
{
if( this.mobile )
{
$(window).load( function() {
mobileMultiForm.focusListener();
mobileMultiForm.focusOutListener();
mobileMultiForm.removeDisabledInputs();
mobileMultiForm.mouseDownListener();
});
}
}
};
mobileMultiForm.init();
})(jQuery);
@jasoncomes
Copy link
Author

Widget Mobile Fixes

iOS has long contained a web form feature that negatively affects pages with widgets. Here's the problem and our solution:

Mobile Issues

When multiple forms or inputs are placed throughout a web page, the native iOS "next" or "previous" arrows jump from input to input. On pages with our widget, the arrows "jump" from the final widget field to the next input element no matter where it is on the page. This jarring to our users and takes users who have selected widget fields to an unexpected part of the page, preventing them from submitting the widget.

EduWidget by default disables the Select by Category and Select by Subject select inputs. This removes the native iOS "next" arrow to the Category and Subjects select inputs. Without the "next" arrow, users lose the ability to move quickly through fields and complete the form easily.

If EduWidget select inputs are enabled, this creates the possibility of empty select fields (mainly Select by Category and Select by Subject). But by disabling the input fields causes other issues mentioned above.

Manual tab indexes should be avoided: they also create "jumps" that unexpectedly move users throughout fields on the page.

Mobile Solution

Isolate forms & orphaned inputs. This prevents the user from hitting the next button and being displaced to another part of the page. If inputs are in a form tag, you are allowed to jump from child input to child input.

Enable all EduWidget select inputs. Allows the user to quickly complete form inputs while keeping the user locked into the form not being displaced on page.

Disable inputs outside of the current form. If the select input field has no options besides the default "Select a {Field Name}", the user will be moved to the most previous input select that has options. Selecting an option in that previous input should correctly populate the option-less select.

Reset inputs when leaving current form. The script adjusts tabindex of elements outside the current form, then resets those inputs' tabindex to the default on leaving the form. We'll use tabindex="-2" for those elements and not change tabindex="-1": devs can still write forms to skip inputs without interference from this script.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment