ViewPort-JS

From HaFrWiki
Jump to: navigation, search

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.
  • Functions logts, logverbose and sprintf are utility functions which can be commented-out.

<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>

CallBack Function

The ViewPort implementation needs to call a CallBack Function which implements the app-specific changes to be made. An example is shown below. <syntaxhighlight lang="javascript"> /**

* Example Callback function for the vpjs implementation.
*/

function doExample_Transition4VPJS(options, action) {

   'use strict';
  var met = "doTransition4VPJS";
  if (options === 'undefined') {
     logts( sprint( logFormatBW, met, "FATAL ERROR", "Options are NOT defined. "));
     return;
  }
  switch(action) {
     case vpjs.enumHigh: options['keywords'] = { nr: true, name: true, description: true };
                         options['users'   ] = { nr: true, country: true, accessexpires: true, lastmodied: true };
                         break;
     case vpjs.enumMid : options['keywords'] = { nr: true, name: true, description: false };
                         options['users'   ] = { nr: true, country: true, accessexpires: false, lastmodied: false };
                         break;
     case vpjs.enumLow : options['keywords'] = { nr: false, name: false, description: false };
                         options['users'   ] = { nr: false, country: false, accessexpires: false, lastmodied: false };
                         break;
     default           : options['transition'] = vpjs.enumStay;
                         break;
  }
  if (gridLUB != undefined) {
     if (action !== vpjs.enumStay) {
        logverbose( 'TRANSITION' );
        logverbose( sprintf( logFormatLBW, met, "options[users]", JSON.stringify(options['users'])));
        $("#<?php echo LIST_ADMIN_USERS; ?>").bootgrid("hafr", "viewport", JSON.stringify(options['users']));
     }
  }
  if (gridKeyword != undefined) {
     if (action !== vpjs.enumStay) {
        $("#<?php echo LIST_LINKS4KEYWORDS; ?>").bootgrid("hafr", "viewport", JSON.stringify(options['keywords']));
     }
  }

} // doTransition4VPJS </syntaxhighlight> The app has 2 Bootgrids which are defined by the global variables

  • gridLUB, i.e. gridLUB = $("#xxxx").bootgrid( { /* your instantiation code. */ });
  • gridKeyword, i.e. gridLUB = $("#yyyy").bootgrid( { /* your instantiation code. */ });

Program Flow

Now that all ingredients are defined, the flow of the program can be show.

Main App

In the main app or where you have defined the jquery.ready function: <syntaxhighlight lang="javascript">

var gridLUB; var gridKeyword;

$(document).ready(

     function() {
         ...  
        $( window ).on( "orientationchange", function( event ) {
           // setStatus4Developer();
           // changeOnViewPort();
           vpjs.hasTransition();
        });
        
        $( window ).on('resize', function( event ) {
           // setStatus4Developer();
           // changeOnViewPort();
           vpjs.hasTransition();
        });
        ....
        setTimeout( "initVPJS()"               , 750);
        setTimeout( "vpjs.performInitAction();", 850);
    });
  /**
   * Initializes the vpjs object.
   * The options for the init are:
   * - cbFunction      : required callback function.
   * - rangeLandscape  : optional ranges for the Landscape viewport.
   * - rangePortrait   : optional ranges for the Protrait viewport.
   */
  function initVPJS() {
     'use strict';
     var met = "initVPJS";
     var options = {
        cbFunction     : "doTransition4VPJS",
        rangeLandscape : { start: 100, low: 780, mid: 1040, high: 9999 },
        rangePortrait  : { start: 100, low: 680, mid: 1040, high: 9999 },
        description    : "Version 1.0.3.1"
     }
     vpjs.init(options);
  }  // initVPJS
  /**
   * VPJS Callback function for the vpjs implementation.
   * This function is only called when a transition has been detected (as set in the action)
   * @param  object options Object with options (required)
   * @param  enum   action  The 'transition' enumStay, ..., enumLow
   */
  function doTransition4VPJS(options, action) {
     'use strict';
     var met = "doTransition4VPJS";
     if (options === 'undefined') {
        logts( sprint( logFormatBW, met, "FATAL ERROR", "Options are NOT defined. "));
        return;
     }
     logtrace( sprintf( logFormatLBW, met, "options keys"   , options !== 'undefined' ? Object.keys(options) : "Undefined"));
     logts   ( sprintf( logFormatLBW, met, "action"         , action + ", " + vpjs.strAction(action) + ", Version: " + options.version));
     switch(action) {
        case vpjs.enumHigh:
                             if (vpjs.isLandscape() ) {
                                options['keywords'] = { nr: true, name: true, description: true };
                                options['users'   ] = { nr: true, country: true, rolename: true, accessexpires: true, lastmodied: true };
                             } else {
                                options['keywords'] = { nr: true, name: true, description: true };
                                options['users'   ] = { nr: true, country: true, rolename: true, accessexpires: true, lastmodied: true };
                             }
                             break;
        case vpjs.enumMid :
                             if (vpjs.isLandscape() ) {
                                options['keywords'] = { nr: true, name: true, description: false };
                                options['users'   ] = { nr: true, country: true, rolename: true, accessexpires: false, lastmodied: false };
                             } else {
                                options['keywords'] = { nr: true, name: true, description: false };
                                options['users'   ] = { nr: true, country: true, rolename: false, accessexpires: false, lastmodied: false };
                             }
                             break;
        case vpjs.enumLow :
                             if (vpjs.isLandscape() ) {
                                options['keywords'] = { nr: false, name: true, description: false };
                                options['users'   ] = { nr: false, country: false, rolename: false, accessexpires: false, lastmodied: false };
                             } else {
                                options['keywords'] = { nr: false, name: false, description: false };
                                options['users'   ] = { nr: false, country: false, rolename: false, accessexpires: false, lastmodied: false };
                             }
                             break;
        default           :  options['transition'] = vpjs.enumStay;
                             break;
     }  // switch
     if (gridLUB != undefined) {
        if (action !== vpjs.enumStay) {
           logverbose( sprintf( logFormatLBW, met, "options[users]", JSON.stringify(options['users'])));
           $("#<?php echo LIST_ADMIN_USERS; ?>").bootgrid("hafr", "viewport", JSON.stringify(options['users']));
        }
     }
     if (gridKeyword != undefined) {
        if (action !== vpjs.enumStay) {
           logverbose( sprintf( logFormatLBW, met, "options[keywords]", JSON.stringify(options['keywords'])));
           $("#<?php echo LIST_LINKS4KEYWORDS; ?>").bootgrid("hafr", "viewport", JSON.stringify(options['keywords']));
        }
     }
  }   // doTransition4VPJS


</syntaxhighlight>

Questions

The above implementation is complex because of the interactions between all components. But the implementation is not that complicated. In case of questions please let me know.

See also

top

Reference

top

  1. GitHub rstaib jquery-bootgrid, Git Repository from Rafael Straib for Bootgrid.
  2. jQuery Bootgrid an jQuery extension created by Rafael Staib.