Overview

Skill Level: Intermediate

Performance optimizations for user interfaces in IBM Business Process Manager (IBM BPM) are discussed. From Human Service architecture to Coach composition and individual controls various measures specific to the Coach Framework are presented.

Step-by-step

  1. Introduction

    IBM Business Process Manager (IBM BPM) is a great tool for modelling and executing business processes. With its powerful capabilities for creating user interfaces developers and business users alike can quickly create the forms and dashboards needed in their processes. The Coach Framework’s flexibility and reusability allow the creation of virtually any user interface conceivable for web-based applications. However, as the number of requirements rises, it may become necessary to employ patterns and good practices that keep complex applications fast and responsive.

    Firstly, the golden rule for achieving performance is to use the latest technology. This includes IBM BPM itself as well as the related tools and systems such as the web browser. With each new release IBM BPM is further optimized for a fast experience. As of Cumulative Fix 2017.061 there is a powerful new default library for UI controls – the BPM UI toolkit – which is based on the SPARK UI toolkit2 and performs significantly better than any previous toolkit. Make sure to use the latest technologies of IBM BPM such as Client-Side Human Services and the SPARK-based controls to get the best results in the easiest way.

    There have been publications discussing IBM BPM performance in general [1] and how to develop user interfaces in IBM BPM [2] [3]. This document explores performance optimizations for IBM BPM user interfaces. From the service architecture to the Coach composition and the individual controls various aspects of UI development are covered. In addition to considerations specific to the Coach Framework, concepts and insights from modern front- end development, such as lazy-loading and code minification, are presented and turned into applicable good practices for IBM BPM UIs.

    Most optimizations for performance need to be done while developing a process application. The guidelines and considerations discussed here apply to the user interfaces and their related assets, i.e. the Human Services with their Boundary Events and variables, the Coaches and how they are set up, and the Coach Views and the way they are written and how referenced web files such as CSS and JavaScript are used.

  2. Coach Services

    The human services that contain a Coach, i.e. the actual user interface that is shown to the user and with which the user interacts, are called Coach Services. This are either Client-Side Human Services (CSHS, as of IBM BPM 8.5.5) or Heritage Human Services (HHS, as of IBM BPM 8.0).

    The image below shows a sample Client-Side Human Service with a number of flows that represent common use cases in a BPM user interface.

     

    The image below depicts a similar implementation with a Heritage Human Service. The main difference between the two is the way how to handle validations. See the IBM Knowledge Center for details:

     

    coach-services-common-actions-heritage

     

    Postpone and On Load Logic

    Practice: Avoid Redundancy between Postpone and On Coach Load

    Postpone is often used as a “Save as Draft” functionality. However, you can never rely on the user to press a “Save as Draft” button before closing it to continue work later. In certain situations it may be necessary to load the most current data (e.g. from a System of Record). This can be done by triggering a Boundary Event whenever the Coach loads (i.e. whenever the user opens the task to resume work). If this cannot be prevented by using Ajax calls instead (see below), make sure that coming from Postpone does not redundantly execute the same services and server-side scripts as the “On Coach Load” Boundary Event.

    The Client-Side Human Service below is an example of how to make updates either after resuming a task from a Postpone event or when opening the task and by firing a Boundary Event. The variables resumeAfterPostpone and showWarningMessage are used to control this behavior.

     

    Boundary Events

    Practice: Avoid firing Boundary Events where possible

    Boundary Events persist all variables that are somehow bound to the Coach (i.e. they or any of their properties is bound to any Coach View) [3, p. 71]. This happens every time, even if none of them were changed since the last boundary event.

    The SPARK UI toolkit introduced a programming model that allows to execute JavaScript code directly as part of specific events of Coach Views (e.g. “On Click” of a button or “On Change” of a text control). This allows to implement behavior that was previously only possible by writing custom Coach Views and/or firing Boundary Events to execute scripts as a workaround. As of Cumulative Fix CF2017.03 the event engine is part of the BPM product3 and the controls from the new default BPM UI toolkit (CF2017.06) fully support them. Make sure you take advantage of these possibilities and only use Boundary Events were necessary. This will result in much simpler code that also requires less maintenance and does not clutter the diagram.

     

    Practice: Enable Optimization for Coaches

    The amount of data that is sent from Boundary Events can be optimized as described in this Knowledge Center article:

    https://www.ibm.com/support/knowledgecenter/en/SSFTN5_8.5.7/com.ibm.wbpm.wle.editor.doc/topics/t_enablingoptimizationforcoaches.html

     

    Practice: Prevent multiple Clicks from Buttons

    Sometimes it cannot be circumvented that a Boundary Event takes longer than users expect, either because of external system calls, complex scripts or for other reasons. To prevent users from clicking a button repeatedly because they are unaware that the event is still being processed and thus causing unnecessary load, use the appropriate configuration option of the button control to disable the button in the meantime. The button will be disabled as soon as it is clicked and once the Boundary Event is done (i.e. the corresponding Ajax call returns) the button gets enabled again.

     

    The Dojo-based stock buttons prevent multiple clicks by default

    The Dojo-based stock buttons prevent multiple clicks by default

     

    The buttons from the responsive coaches  toolkit prevent multiple clicks by default

    The buttons from the responsive coaches toolkit prevent multiple clicks by default

     

    For SPARK UI buttons enable the Prevent Multiple Clicks option

    For SPARK UI buttons enable the Prevent Multiple Clicks option

     

    Practice: Avoid executing expensive server-side logic from Boundary Events

    Note that Boundary Events are processed sequentially. If you trigger two boundary events directly after another, the second Boundary Event is only sent off once the previous one has returned from the server. Also, note that the current variable values at the actual time of making the Ajax call are taken – not the values at the time of when the Boundary Event was initially triggered per the JavaScript API. So if the variable has changed before the Ajax call is actually made, the most current value will be used. This can lead to unexpected results if the server-side processing of Boundary Events takes longer.

    Another reason why it is problematic to have expensive Boundary Events be triggered regularly is that they do not actually run in the background. They can block or slow down the user’s interaction with the page. It is common to style the out-of-the-box loading icon so that it shows as a modal overlay which then fully blocks the user.

    Instead of executing logic in server-side scripts, try to have this done directly in the browser. This is more performant, in fact, it is instantaneous in most cases.

     

    Practice: Use End Points for Exposed Human Services

    Human Services which are not started as part of a process’ human activity but as a Startable Service, by URL or as a Dashboard, should be finished by navigating to an end event. Otherwise they continue to take up memory [1, p. 30].

     

    Coach Data

    Practice: In Heritage Human Services avoid binding large business objects to the Coach

    In Heritage Human Services, if a Coach View is bound to a property of a complex object, the entire object is sent to the browser and – what is more relevant in terms of performance – the entire object is persisted every time a boundary event is sent off [3, p. 71]. Imagine an object like this:

    tw.local.request = {
    id: “RQ0001”,
    creator: { ... }, // object with many properties (name, address, etc.) creationDate: new Date(),
    fields: { ... } // many form fields

    }

     If there is a Text control bound to tw.local.request.id, the entire request object is sent to the browser and persisted with every boundary event, although all that is actually needed is a String for the id. Variables that are defined in the Human Service but not bound to any Coach View are not sent to the client.

    It would be much more efficient to use a local variable tw.local.requestId instead, bind that to the Text control and upon submission of the task (or whenever necessary) assign it to the request object:

    tw.local.request.id = tw.local.requestId

     

    Practice: Use efficient UI objects to map from and to business data

    The idea of Business Objects is that they are logically structured in a way that is best for business users. This is not necessarily (actually, rarely) the best way when it comes to performance. Therefore, you should not rely on the Business Data structure if there is a more efficient way to structure it. Instead, create a dedicated UI object that is optimized for the Coach by containing only the properties relevant for the UI.

     

    Map data in Heritage Human Services

    When using Heritage Human Services, initialize the UI object at the beginning of the service by mapping the Business Data properties in the respective equivalents of the UI object. Then, when the task is completed, map the data back from the UI object to the actual Business Object.

     

    Map data in Client-Side Human Services

    In Client-Side Human Services, all data that is mapped from a BPD or Process to a User Task activity is exposed in the browser. The instancedata request returns a JSON object containing all passed data irrespective of whether it is actively bound or otherwise accessed later on in the human services. Mapping business data structures to UI data structures therefore should be done on process level: Instead of invoking User Tasks directly, move them to Suprocesses where these types of technical activities do not clog up the high level business process.

    Useing a subprocess to prepare coach data

      

    Load relevant data from System of Record

    Another approach is to retrieve the data that is needed from a system of record4 such as an external database or shared business object. Instead of passing large business objects you only pass an ID between process and human services. By that ID the business object can be loaded from the system of record when it is needed. This way, you can already prepare the data for the specific use case and only provide the data that is actually needed.

    If you already employ a system of record, chances are that this approach is already in use. The important aspect is to carefully consider what data to retrieve and prepare it properly instead of simply writing and reading large business objects.

  3. Coaches & Custom Coach Views

    This section deals with the set-up and configuration of Coach Views in the Coach.

     

    Practice: Use CustomHTML to output Static Data

    Using the CustomHTML element is the most efficient way to output static data. No Coach View is created and the variable is evaluated server-side. However, make sure to use this element correctly: Do not use it to output variables that can change dynamically. [3, p. 82] 

     

    Practice: Use Output Controls instead of Readonly Input Controls

    Controls that are intended to only display data may be more efficient than their equivalent input controls, which are more powerful. So rather than setting a Text control, where the user never needs to enter any data, to readonly, use an Output Text control instead.

     

    Practice: Avoid using many nested Coach Views

    The less Coach Views on a Coach, the better the performance. Try to keep the number of nested Coach Views low [3, p. 70]. As of version 8.5.7 IBM BPM provides a powerful grid system for structuring the layout of Coaches. Previous approaches relying on layout Coach Views to build a grid should be avoided.

    Tables

    Practice: Minimize the number of Columns in Tables

    It was already mentioned that you want to keep the number of Coach Views in Coaches as low as possible in general. But it is worth stressing that this is especially true for tables. Unnecessary columns or nested Coach Views in a table quickly add up so special care should be taken when designing tables.

    When using SPARK tables make sure to take advantage of the render as HTML function to further optimize the rendering of the table data [3, p. 249]. 

    Custom Coach Views

    This section contains a number of good practices when developing your own custom Coach Views.

     

    Practice: Make proper use of the Unload event handler

    The Unload event handler is called when a Coach View is destroyed and should be used for clean-up actions [3, p. 107].

    • Remove any (global) listeners that were set
    • Set references such as _this to null
    • Unbind any previously set bind()/bindAll() handlers

     

    Practice: Filter the Change event so that it only triggers when necessary

    The Change event handler is called whenever the binding variable, a configuration option’s variable or other configuration is changed. Make sure that any logic in the Change handler is only executed when intended [3, p. 149].

     

    Practice: Reduce the amount of redundant Ajax calls

    Ajax services are called in numerous situations by Coach Views. If a custom Coach View relies on Ajax service calls, ensure that those calls are not made redundantly. Especially in tables Ajax calls may be made for every row in the table.

    One thing you can do is enable caching for a service:

    ConfigAjax 

    However, this does not prevent controls from firing their Ajax services. It prevents the execution of the service on the server-side so a bit of execution time is saved. Especially if you are dealing with globally distributed users you may be dealing with slow networks and latency. In that situation, the server round-trip may be the bottle-neck, less so the actual processing on the server. You can utilize JavaScript patterns to avoid redundant calls and provide caching on the client-side; the vuQuery JavaScript library provides an implementation to do this. 

    Calling an Ajax service in the load event potentially resulting in redundant requests

    Calling an Ajax service in the load event potentially resulting in redundant requests

     

     Calling an Ajax service via the vuQuery library and using client-side caching

    Calling an Ajax service via the vuQuery library and using client-side caching

     

    Practice: Use prototype when defining JavaScript functions in Coach Views

    Define JavaScript functions in custom Coach Views by using constructor.prototype. This will lower the memory print of the control [3, p. 146].

    Check the option Prototype-level event handlers in Overview > Usage of the Coach View. Below is an example of a helper function that is defined in the Inline JavaScript section.

    Picture8-1

     

    The function can then be called like this: 

    Picture9-1 

     

     

    Practice: Do not encode web assets as Base64 strings

    In an effort to reduce the number of requests to the server it is sometimes suggested to include images as Base64 encoded strings directly in the CSS stylesheet instead. However, generally this comes with a number of disadvantages such as worse performance and caching [4].

    • Base64 encoded strings do not benefit from gzipping
    • Base64 encoded strings increase the size of the stylesheet, which is a blocking resource (unlike images).
      As a result the browser must wait longer before the web page can be rendered.
    • While images can be cached by the browser, any changes to the containing stylesheet will also cause the Base64 string bytes to be sent again as they are part of the stylesheet. 

     

     

    Practice: Put Inline CSS and Inline JavaScript into separate minified consolidated CSS and JS files

    The code in the Inline CSS and Inline JavaScript sections of Coach Views is placed directly into the HTML document that is served by the initial request when a Coach is opened. If you place this code into separate files you get a number of benefits:

    • The initial request is decreased in size.
    • The CSS/JS files can be cached.
    • The CSS/JS files can be minified to further lower the load.
    • The CSS/JS files can be consolidated to reduce the number of requests

    The downside is that additional files impose additional roundtrips to the server which is detrimental. To mitigate this issue, combine the code into as few files as possible, preferably only one single “custom-controls.js” and one “custom-layout.css” file. Once these files have been downloaded, they should be cached by the browser.

    Note: As of IBM BPM 8.5.7 you can employ Themes for customizing the appearance of UI controls. For the purpose of styling your controls this is the preferred way due to ease of maintenance.

     

    Externalize CSS code

    Moving Inline CSS code from Coach Views to a separate file and referencing that is rather straight-forward. In most cases it should just work. However, note that for CSS selectors of the same specificity it is important in which order they occur because the last one wins. So if you style existing UI controls to make them match a corporate style or other specific design, chances are that there are competing CSS selectors and you may encounter problems when moving them to a file.

    Note that referenced CSS files are added to the HTML document’s head from inner to outer in case of nested controls. Also, any Inline CSS is put into a style tag in the head, again with nested Coach View’s Inline CSS coming before outer Coach Views’.

     

    Externalize JavaScript code

    Unlike externalizing CSS code, putting the JavaScript of a Coach View into a separate file requires a bit of refactoring. As a first step, you can consolidate the code for all event handlers of the Coach View and put it into the Inline JavaScript section like this:

    Picture10-1

     

    To externalize the code, move the consolidated JavaScript into a separate file and wrap it by a function definition with an appropriate name like this:

    My_Custom_Control = function(){
    // <Put your consolidated Inline JavaScript code here>
    ...
    }

     

    Example:

    Picture11-1

     

    Upload the JavaScript file as a Web File and reference it in the Coach Views as an Included Script. Then, call the function in the Inline JavaScript section.  

    Picture12-1

     

    Consolidation

    Combine CSS and JavaScript files into as few files as possible to reduce the number of requests that are sent to the server. So instead of creating separate JavaScript files for every custom control, combine them into one project file.

     

    Minification

    Now that the code has been consolidated, minify the CSS and JavaScript files as it increases download, parsing and execution for these files. There are many tools available for minification, such as Douglas Crockford’s JSMin, yahoo’s YUI Compressor, Dead Edward’s Packer, Microsoft Ajax Minifier or UglifyJS to name just a few.

    Note: Since your process application/toolkit also serves as the main repository for all related assets, you may want to upload the original CSS and JavaScript files (not minified, not consolidated) to the app/toolkit. You can use a GitHub repository for your files and set up a build process that creates both the consolidated files and the original ones (e.g. as a zip archive) for convenient uploading.

     

    Practice: Bundle JavaScript files in zipped Dojo modules

    Use AMD modules to deliver your custom JavaScript via zip archives [3, p. 149].

     

    Practice: Employ an effective Caching Strategy with Toolkit Snapshots

    Web assets in IBM BPM are retrieved by their snapshot version. With every new snapshot of a Process Application or Toolkit the contained web assets will be requested anew from the browser.

    Web asset URL structure:
    <domain>/teamworks/webasset/<snapshotId>/W/<assetName>

    Example:
    http://sample.bpm/teamworks/webasset/2064.bf2a9462-69e3-4be0-a621-b05b28183621/W/test.js

    This means, that with every new snapshot that is deployed, all web assets have to be requested again, even if they did not change at all. Because of this (and for other reasons, such as maintenance and ease of development), it makes sense to put these assets into a separate toolkit. While a process application has to be deployed via a new snapshot, the assets from referenced toolkits may continue to be used from their previous snapshot so no new requests have to be made. If you have a large number of static resources such as images, font files or core JavaScript libraries (e.g. jQuery), it may make sense to keep them in a dedicated toolkit that is less often updated than your typical custom UI toolkit.

  4. Lazy Loading

    Lazy loading is a design pattern that is intended for improving performance by initializing and loading data objects until the point that they are actually needed. In the context of web applications this means that the page loads faster and the user gets to start interact quicker with it. Additional content is only loaded once the relevant part of the page is accessed. Typical examples are collapsible sections or tabs whose content is loaded when the user expands or selects certain sections. Wherever possible, you should make use of lazy loading [3, p. 147].

     

    Lazy Loading Coach Views

    In IBM BPM the term lazy loading often refers to not the loading of (business) data, but to the instantiation and creation of UI controls. Especially the Dojo-based stock controls of BPM version 8.0 to 8.5.6 were rather expensive when it came to the initiation of the controls. Holding this off until when these controls are actually needed makes a great difference in how quickly the user can start interacting with the web page.

    This way of lazy loading Coach Views can be implemented by using the Content Box element explicitly controlling when its content (i.e. the contained Coach Views) should be generated. You can find an example for this in [3].

     

    Practice: Use Lazy Loading for Collapsible Panels, Tabs and Dialogs

    All UI controls that contain other Coach Views and whose content does not have to be accessible right away can benefit from lazy loading. The most common controls that fall in this category are collapsible panels/sections, tabs and dialogs/modals/popups. An example for this can be found in [3, pp. 250-252].

    Example: Collapsible Panel with Lazy Loading and SPARK UI

    The SPARK UI toolkit provides a general purpose control to conveniently implement lazy loading Coach Views: the Deferred Section. You can e.g. easily implement a lazy loading collapsible panel by simply placing a Deferred Section into a Collapsible Panel and have the section lazy load when the panel is expanded.

    PictureLL1-1

       

    Practice: Use Lazy Loading for Content below the Fold

    If the page layout is set up so that not all content is put inside sections which can be initially collapsed and lazy loading, you may want to consider lazy loading all the content below the fold. This can be easily achieved by placing the Coach Views which are not initially visible when the Coach loads – as they are further down the page – inside of a Deferred Section control from the SPARK UI toolkit. Assign a small timeout e.g. 500ms for the control to wait before generating the controls inside the Deferred Section. This allows the controls above to full load and give the user an impression of better performance.

     

    Lazy Loading Data

    Typically lazy loading refers to data objects being created on demand. However, in IBM BPM you will typically initialize your business data in an earlier stage of the process. The UI is then used to let the user set the values for certain properties of the business objects.

     

    Practice: Use the Service Data Table where possible

    A typical use case for where lazy loading data has a great benefit on the performance is the display of large data sets in tables. The SPARK UI toolkit provides the Service Data Table control specifically for this. It allows using an Ajax service to retrieve data and performs very well for even large data sets [3, p. 219].

     

    Practice: Load large Business Objects with Ajax Services where sensible

    Instead of binding large Business Objects to a Coach View, you can fire an Ajax Service when the Coach loads and have that service retrieve the data that should be displayed. This may require using a System of Record for storing the required data so that it can be accessed from inside the Ajax service.

  5. Performance Analysis

    This section lists some of the capabilities of IBM BPM that help debugging and analyzing the front-end of your processes. Refer to the Knowledge Center links for detailed articles on how to use them.

    Uncompressed JavaScript code

    If you set the isDebug flag for the application server instance where IBM BPM is installed, the JavaScript files for Dojo and the Coach Framework will be delivered in an uncompressed form. This makes them readable and can be helpful when debugging your code. Read more about this in the Knowledge Center.

    Coach Performance Statistics

    As of CF2017.06 IBM BPM provides two types of statistical data about your coaches: at design time and at run time. (See Knowledge Center).

     

    Statistics at design time

    In the coach layout palette there is a section Static Analysis which displays statistics about the current Coach. This helps point out areas where you may apply optimizations. E.g. it tells you the number of Coach Views and gives a quick overview of repeating elements such as tables and the number of Coach View instances that would be created during run time (compare Practice: Minimize the number of Columns in Tables). The number of Coach Views can be low (0-50), moderate (51-500) and high (>500).

    The number of coach views for the Add Candidates coach in the Find Candidates CSHS of the Hiring Sample app

    The number of coach views for the Add Candidates coach in the Find Candidates CSHS of the Hiring Sample app

     

    Statistics for the Prospective Candidates table coach view of the Add Candidates coach

    Statistics for the Prospective Candidates table coach view of the Add Candidates coach

     

    Statistics at run time

    For further analysis, you can enable performance statistics to be collected dynamically during run time. To do so execute the following code in the browser console when debugging your human service:

    localStorage["CoachPerformanceMonitor"] = true;

    Now, if you run the service again, a small icon will appear in the upper-right corner of the Coach. Click on it to open the Performance statistics.

     

    After activating the performance statistics, the icon appears in the upper-right corner

    After activating the performance statistics, the icon appears in the upper-right corner

     

    Sample statistics from the Hiring Sample app

    Sample statistics from the Hiring Sample app

  6. References

    [1]  M. Collins, Z. H. Duan, A. Fried, B. Hoflich, C. Richardson and T. Wilms, IBM Business Process Manager V8.5 Performance Tuning and Best Practices, IBM Redbook, 2015.
    [2]  J. Reynolds, M. Collins, E. Ducos, D. Frost, I. Kornienko, D. Knapp, B. Naumann, P. Pacholski and G. Pfau, Leveraging the IBM BPM Coach Framework in Your Framework, IBM Redbooks, 2014.
    [3]  R. Boren, E. Ducos, G. Gao, T. Hooker, M. Oatts, P. Pacholski, D. Parrott and C. Tagliabue, Deliver Modern UI for IBM BPM with the Coach Framework and Other Approaches, IBM Redbook, 2016.
    [4]  H. Roberts, “Base64 Encoding & Performance, Part 1: What’s Up with Base64?,” CSS Wizardry Ltd, 12 02 2017. [Online]. Available: https://csswizardry.com/2017/02/base64-encoding-and-performance/. [Accessed 24 02 2017].
    [5]  K. Basques, “How to Use the Timeline Tool,” Google, 06 02 2017. [Online]. Available: https://developers.google.com/web/tools/chrome-devtools/evaluate-performance/timeline-tool. [Accessed 08 02 2017].
    [6]  M. Kearney and F. Copes, “Timeline Event Reference,” 06 02 2017. [Online]. Available: https://developers.google.com/web/tools/chrome-devtools/evaluate-performance/performance-reference. [Accessed 08 02 2017].
    [7]  J. Garbee, “Understanding Resource Timing,” Google, 06 02 2017. [Online]. Available: https://developers.google.com/web/tools/chrome-devtools/network-performance/understanding-resource- timing. [Accessed 08 02 2017].

7 comments on"IBM BPM UI Performance"

  1. Christian Templin March 28, 2018

    This article is also available in PDF format via this box link:
    https://ibm.box.com/s/ptjt0ug01pbqdq9ig1z5ymn63qx17x53

  2. Luis E. Villasenor March 28, 2018

    Congrats on this great article, Christian. Well summarized best practices!

  3. Michael Scheible April 24, 2018

    Christian, you have created a really great article that is in high demand by IBMers and customers. The content yo created is much more specific and up to date with the latest product releases than some of the pretty thick redbooks available on the topic.

  4. Christian Templin April 25, 2018

    Thanks a lot for your feedback, Michael!

  5. MikeZilbergleyt June 14, 2018

    Hi, you mentioned that “Especially in tables Ajax calls may be made for every row in the table.”. Can you, please, expand on this?
    Are you talking about Table or Service Data Table?

    Thanks.

  6. Christian Templin June 15, 2018

    Hi, Mike! I have described the superflous Ajax calls scenario and caching Ajax results in the frontend in great detail in this post:
    http://cianty.de/shared-data-across-coach-views/

    In short, a Select control (aka Dropdown) that uses an Ajax Service to retrieve its option list will make that Ajax call during load (or view, depending on implementation). If such a control is used inside of a table control, this will end up in multiple Ajax calls as every time the control is rendered (once per row) one such Ajax call is made. This is unnecessary as each of the Controls will probably make the exact same call and retrieve the same result (the options list). Therefore, I developed the solution where these calls are cached in the browser (by storing the service/parameter combination along with the result) so that if the same call is made multiple times, the previously stored result is returned instead of making the actual Ajax call.

    I hope this clarifies what I meant to say. 🙂

    • thanks for the great write-up, thoroughly impressed and very useful.

      Just FYI, the website link is broken and throws MySQL errors.
      Error: Your PHP installation appears to be missing the MySQL extension which is required by WordPress.

Join The Discussion