ViewPort-JS
Responsive Design is normally achieved by using the features of the CSS-Media queries. And that is also my preferred way of making Responsive applications. Personally I am using jQuery + Bootstrap. The responsive design of the input-forms is straightforward and the look-and-feel on Desktop and Mobile are good. The problem occurs when the app needs tables.
If you are using Bootstrap in combination with Bootgrid you have a problem when using the CSS. The columns of the grid are selectable in the applications and simply hiding a column with CSS won't help (the user will not understand that a selected column does not show up). So the app needs to change the selections in Bootgrid, but Bootgrid does not allow such an action. Also the GitHub-code [1] has not been changed since more than 2 years.
Bootgrid
Bootgrid [2] is an extension on jQuery especially designed for Bootstrap. Since the version-control-repository has not been altered since 2 years, I do not expect any major changes will be made.
Therefor I have created an extension to the code without altering the original Bootgrid code. The first extension makes it possible to change the selected columns in Bootgrid self <syntaxhighlight lang="javascript"> /**
* Renders special HaFr queries * Needs to be calls as renderHaFr.call(this, json) * Is called by the prototype.hafr * @param string json json-list with pairs: column => (in)visible */ function renderHaFr(json) { var that = this; var joParams = JSON.parse(json); var css = this.options.css; var selector;
$.each(Object.keys( joParams ), function( i, key) { $.each(that.columns, function (j, column) { if (column.visibleInSelection && column.id == key) { // Makes the column (in)visible. column.visible = joParams[key]; renderTableHeader.call(that); // Adjust the listbox selections setDomNameVal( column.id, joParams[key]); } }); loadData.call(that); });
} // renderHaFr </syntaxhighlight>
In my library setDomNameVal is a utility function <syntaxhighlight lang="javascript"> function setDomNameVal(domName, domValue) {
'use strict'; var nameSelector = '[name="' + domName + '"]';
if ( $(nameSelector).length ) { if ( $(nameSelector).is(':checkbox')) { if ( domValue == 1 ) { $(nameSelector).prop('checked', true); } else { $(nameSelector).prop('checked', false ); } } else { $(nameSelector).val(domValue); } }
} // setDomNameVal </syntaxhighlight>
The second Bootgrid extension is a prototype function which enables the calling from outside, which calls the render function with the 'this' parameter/content. The prototype only implements the 'viewport' action. <syntaxhighlight lang="javascript"> /**
* HaFr specific function for all kind of special not implemented bootgrid functionalities. * @param string action The requested HaFr action. * @param string json Parameters for the action */
Grid.prototype.hafr = function(action, json) {
var met = "grid.prototype.hafr";
if (action == 'viewport') { renderHaFr.call(this, json); }
}; </syntaxhighlight>
ViewPort-JS.php
The used server language is PHP. To make the co-existence of JavaScript and PHP as easy as possible the JavaScript file is embedded in a PHP-file. For the simplicity the PHP wrapper is removed in the given code example. <syntaxhighlight lang="javascript"> /**
* Anonymous function for ViewPort Width x Height Changes or Orientation Changes. * The changes has an implication on the settings of the Bootgrid lists. * @param object vpjs Name for the function to be used to call the functions. * @param object $ Object jQuery is passed in. * @param object undefined Prevents the overriding of the undefined. */
(function( vpjs, $, undefined ) {
'use strict';
/** Version information */ vpjs.versionNumber = "1.0.3.1"; vpjs.versionDate = "02 Jun 2017";
/* * Enumerations for the action to take on the change of the viewport orientation and/or ranges. */ vpjs.enumError = -1; // Error status. vpjs.enumStay = 0; // Do nothing. vpjs.enumHigh = 1; // Changed to the highest range. vpjs.enumMid = 2; // Changed to the mid-range. vpjs.enumLow = 3; // Changed to the lowest range.
/* Private initialization flag. */ var isInit = false;
/** * Private property placeholder for the current Viewport. */ var curViewPort = { 'innerHeight' : 0, 'innerWidth': 0, 'isMobile': false, 'isLandscape': false, 'enum': vpjs.enumError };
/** * Default Ranges for the Landscape mode. * Maybe overruled by the setter 'setRangeLandscape'. */ var rangeLandscape = { start: 100, low: 780, mid: 1040, high: 9999 }; /** * Default Ranges for the Portrait mode. * Maybe overruled by the setter 'setRangePortrait'. */ var rangePortrait = { start: 100, low: 680, mid: 1040, high: 9999 };
var cbOptions = { version: vpjs.versionNumber }; var cbFunction;
/** * Public Initialization function remembers the current viewport and sets the init-flag. * The entry point for the function to be called like 'vpjs.init()'. */ vpjs.init = function(options) { var met = "vpjs.init"; isInit = true; saveViewPort();
if (typeof options === 'undefined') { logts( sprintf( logFormatLBW, met, "options", "No options declared.")) return; }
if ( hasObjectProperty( options, 'cbFunction') ) { logts( sprintf( logFormatLBW, met, "cbFunction", "Set to " + cbFunction)); cbFunction = window[ options.cbFunction ]; }
if ( hasObjectProperty( options, 'rangeLandscape') && checkRange( options.rangeLandscape ) ) { logts( sprintf( logFormatLBW, met, "rangeLandscape", "Set to " + JSON.stringify(options.rangeLandscape) )); rangeLandscape = options.rangeLandscape; }
if ( hasObjectProperty( options, 'rangePortrait') && checkRange( options.rangePortrait ) ) { logts( sprintf( logFormatLBW, met, "rangePortrait", "Set to " + JSON.stringify(options.rangePortrait) )); rangePortrait = options.rangePortrait; } }; // vpjs.init
/** * Public performs the initialization-action. * The initialization action has to be performed immediate after the function-initialization. * The performInitAction is optional. * Use this function to set the initial state at opening of the website when no responsive grid column settings are used. */ vpjs.performInitAction = function() { var met = 'vpjs.performInitAction'; // Saves the enum into the curViewPort-object. saveEnum4ViewPort();
if (curViewPort.enum === vpjs.enumError) { logts( sprintf( logFormatLBW, met, "ERROR", "Requirements not met, nothing performed.")); }
// Calls the CallBack function. if (typeof cbFunction === "function") { cbFunction.call(null, cbOptions, curViewPort.enum ); } else { logts( sprintf( logFormatLBW, met, "cbFunction", "Not a function!")); } } // vpjs.performInitAction
/** * Sets the callback function and the options. * @deprecated Use the method vpjs.init(options) instead. * @param object callOptions The options * @param string callFunction The call back function as string without brackets and params. */ vpjs.setCallBack = function(callOptions, callFunction) { var met = "vpjs.setCallBack"; logts( sprintf( logFormatLBW, met, "Function", callFunction)); cbOptions = callOptions; cbFunction = window[callFunction]; } // setCallBack
/** * Setter for the limits for the Landscape. * @param object range Object with keys: start, low, mid, high. */ vpjs.setRangeLandscape = function(range) { if (checkRange(range) ) { rangeLandscape = range; return true; } logts( sprintf( logFormatLBW, met, "ERROR", "Invalid range.")); return false; } // setRangeLandscape
/** * Setter for the limits for the Portrait * @param object range Object with keys: start, low, mid, high. */ vpjs.setRangePortrait = function(range) { if (checkRange(range) ) { rangePortrait = range; return true; } logts( sprintf( logFormatLBW, met, "ERROR", "Invalid range.")); return false; } // setRangePortrait
/** * Private checks the range for Landscape/Portrait. * @param object range Range object with required keys. * @return boolean true | false */ function checkRange(range) { var met = "checkRange"; if ( hasObjectProperty( range, "start") === false || hasObjectProperty( range, "low" ) === false || hasObjectProperty( range, "mid" ) === false || hasObjectProperty( range, "high" ) === false) { logts( sprintf( logFormatLBW, met, "Error", "Invalid range specification given.")); return false; } return true; } // checkRange
/** * Public getter for the saved isMobile setting. * @return Boolean isMobile */ vpjs.isMobile = function() { return curViewPort['isMobile']; } // isMobile
/** * Public getter for the saved isLandscape setting. * @return Boolean isLandscape */ vpjs.isLandscape = function() { return curViewPort['isLandscape']; } // isLandscape
/** * Public entry for testing the change of orientation and/or range outbreak of the WxH in compare to the saved settings. * In case of an outbreak of saved settings: * - The needed actions are saved into the returned JavaScript object. * - The new settings overwrite the old saved settings. * @return JavaScript object aRet { transition: enumeration, ...} */ vpjs.hasTransition = function() { // 'use strict'; var met = 'hasTransition'; var aRet = { transition: vpjs.enumError, keywords: , users: , description: };
// Checks the initialization flag, returning an error if not set. if (! isInit) { return aRet; }
if ( vpjs.hasChangedOrientation() ) { logtrace(sprintf( logFormatLBW, met, "Transition", "Orientation change detected")); aRet['transition' ] = vpjs.enumStay; aRet['description' ] = sprintf("%s (%ux%u)", 'Orientation change detected ', window.innerWidth, window.innerHeight); if (isLandscape() ) { if (window.innerWidth < rangeLandscape.low) { aRet['transition'] = vpjs.enumLow; } else if (window.innerWidth < rangeLandscape.mid) { aRet['transition'] = vpjs.enumMid; } else { aRet['transition'] = vpjs.enumHigh; } } else { if (window.innerWidth < rangePortrait.low) { aRet['transition'] = vpjs.enumLow; } else if (window.innerWidth < rangePortrait.mid) { aRet['transition'] = vpjs.enumMid; } else { aRet['transition'] = vpjs.enumHigh; } } } else if (vpjs.hasChangedViewPort() ) { if (isLandscape()) { aRet['transition'] = landscapeRanges( window.innerWidth ); } else { aRet['transition'] = portraitRanges( window.innerWidth ); } aRet['description' ] = sprintf("Transition %u, %s (%ux%u)", aRet['transition'], 'WxH change detected ', window.innerWidth, window.innerHeight); logtrace(sprintf( logFormatLBW, met, "Transition", aRet['description'])); }
if (aRet['transition'] !== vpjs.enumStay) { saveViewPort(); if (typeof cbFunction === "function") { cbFunction.call(null, cbOptions, aRet['transition']); // , aRet['transition']); } else { logts( sprintf( logFormatLBW, met, "cbFunction", "Not a function!")); } } return aRet; } // vpjs.hasTransition
/** * Private saves the current to curViewPort WxH and isMobile settings. */ function saveViewPort() { 'use strict'; var met = 'saveViewPort'; curViewPort['innerHeight'] = window.innerHeight; curViewPort['innerWidth' ] = window.innerWidth ; curViewPort['isMobile' ] = isMobile(); curViewPort['isLandscape'] = isLandscape(); curViewPort['enum' ] = vpjs.enumError; }
/** * Saves the current enum (low-mid-high) into the viewport. * Needs the ranges to be set. */ function saveEnum4ViewPort() { var met = "saveEnum4ViewPort"; var range = rangeLandscape;
if ( curViewPort.isLandcape === false ) { range = rangePortrait; }
curViewPort.enum = vpjs.enumError; logts( sprintf(logFormatLBW, met, "range", JSON.stringify( range))); if (checkRange(range) === false) { return; }
if ( range.start < curViewPort.innerWidth && curViewPort.innerWidth <= range.low ) { curViewPort.enum = vpjs.enumLow; } else if ( range.low < curViewPort.innerWidth && curViewPort.innerWidth < range.mid ) { curViewPort.enum = vpjs.enumMid; } else if ( range.mid < curViewPort.innerWidth && curViewPort.innerWidth < range.high ) { curViewPort.enum = vpjs.enumHigh; } else { curViewPort.enum = vpjs.enumError; } } // saveEnum4Viewport
/** * Private test for changes in landscape mode of the WxH range settings the action-enumeration to none, high, mid or low. * @param integer x The current value for the width. * @return enumeration action */ function landscapeRanges(x) { if ( (isInRange( rangeLandscape.mid , rangeLandscape.high, x) && isInRange( rangeLandscape.mid , rangeLandscape.high , curViewPort['innerWidth'])) || (isInRange( rangeLandscape.low , rangeLandscape.mid , x) && isInRange( rangeLandscape.low , rangeLandscape.mid , curViewPort['innerWidth'])) || (isInRange( rangeLandscape.start, rangeLandscape.low , x) && isInRange( rangeLandscape.start, rangeLandscape.low , curViewPort['innerWidth'])) ) { return vpjs.enumStay; }
if ( x != curViewPort['innerWidth']) { if ( isInRange( rangeLandscape.mid , rangeLandscape.high, x) ) { return vpjs.enumHigh; }
if ( isInRange( rangeLandscape.low , rangeLandscape.mid, x) ) { return vpjs.enumMid; }
if ( isInRange( rangeLandscape.start, rangeLandscape.low , x) ) { return vpjs.enumLow; } } return vpjs.enumStay; } // landscapeRanges
/** * Private test for changes in portrait mode of the WxH range settings the action-enumeration to none, high, mid or low. * @param integer x The current value for the width. * @return enumeration action */ function portraitRanges(x) { if ( (isInRange( rangePortrait.mid , rangePortrait.high, x) && isInRange( rangePortrait.mid , rangePortrait.high , curViewPort['innerWidth'])) || (isInRange( rangePortrait.low , rangePortrait.mid , x) && isInRange( rangePortrait.low , rangePortrait.mid , curViewPort['innerWidth'])) || (isInRange( rangePortrait.start, rangePortrait.low , x) && isInRange( rangePortrait.start, rangePortrait.low , curViewPort['innerWidth'])) ) { return vpjs.enumStay; } if ( x != curViewPort['innerWidth']) { if ( isInRange( rangePortrait.mid , rangePortrait.high, x) ) { return vpjs.enumHigh; }
if ( isInRange( rangePortrait.low , rangePortrait.mid, x) ) { return vpjs.enumMid; }
if ( isInRange( rangePortrait.start, rangePortrait.low, x) ) { return vpjs.enumLow; } } return vpjs.enumStay; } // portraitRanges
/** * Has the Viewport VxH changed versus the saved curViewPort? * @return Boolean true | false */ vpjs.hasChangedViewPort = function() { 'use strict'; var met = 'hasChangedViewPort'; if (curViewPort.innerHeight !== window.innerHeight) { logtrace( sprintf( logFormatLBW, met, "Height", curViewPort.innerHeight + " => " + window.innerHeight)); return true; } if (curViewPort.innerWidth !== window.innerWidth) { logtrace( sprintf( logFormatLBW, met, "Width", curViewPort.innerWidth + " => " + window.innerWidth)); return true; } return false; } // hasChangedViewPort
/** * Has the Viewport Orientation changed versus the saved curViewPort? * @return Boolean true | false */ vpjs.hasChangedOrientation = function() { 'use strict'; var met = 'hasChangedOrientation'; if (isLandscape() != curViewPort.isLandscape) { if (isLandscape() ) { logverbose( sprintf( logFormatLBW, met, "Orientation", "Portrait => Landscape")); } else { logverbose( sprintf( logFormatLBW, met, "Orientation", "Landscape => Portrait")); } return true; } return false; } // has ChangedOrientation
/** * Gets the string representation of the enumeration. * @param enum enum Action-enumeration * @return string String representation. */ vpjs.strAction = function(enumVal) { switch(enumVal) { case vpjs.enumStay : return 'Stay'; case vpjs.enumHigh : return 'High'; case vpjs.enumMid : return 'Mid' ; case vpjs.enumLow : return 'Low' ; default : return 'Unknown'; } return 'Unknown'; } // vpjs.strAction
} ( window.vpjs = window.vpjs || {}, jQuery) ); </syntaxhighlight>
See also
Reference
- ↑ GitHub rstaib jquery-bootgrid, Git Repository from Rafael Straib for Bootgrid.
- ↑ jQuery Bootgrid an jQuery extension created by Rafael Staib.