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[edit]

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[edit]

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[edit]

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[edit]

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

Main App[edit]

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[edit]

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[edit]

top

Reference[edit]

top

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