Behaviors - quandis/qbo3-Documentation GitHub Wiki

QBO's javascript library leverages the Mootools framework, which includes a very powerful Behaviors pattern to add dynamic functionality with simple HTML markup. Behaviors is a framework for automating binding of javascript-enabled functionality to HTML elements, with the following advantages:

  • multiple behaviors can be attached to 1 element,
  • initialization can be deferred until a user clicks on the element (e.g. don't create the menu until you need to)
  • onCleanup methods prompt developers to "clean up" memory

For example:

<input type="text" name="PaidDate" data-behavior="Date" data-date-options={"some options"}>

will automatically bind the PaidDate form element to offer a date picker UI when a user clicks on it.

QBO offers many behaviors, including:

Behavior Description
DatePicker Render a date picker UI associated.
Depend Renders or enables form elements based on the data conditions of other elements.
Dropdown Renders a dropdown list by fetching data from an AJAX API call.
Filter Adds or removes parameters from AJAX calls based on user actions.
Labels Renders labels for all elements (usually rows) via a single AJAX call.
Math Adds or multiplies values from form elements.
MatrixLookup Evaluates a matrix-based rule.
MethodSignature Executes a QBO method signature, and makes the results globally available as JSON.
MethodElement Render UI to enable a user to configure a method signature.
ObjectBind Binds a div tag to a QBO object, to enable rendering of method signatures via AJAX.
OrderBy Enables clickable column headers in a table to change sort order.
Paginate Renders pagination at the bottom of a table of data.
Popup Renders modal dialogs from hidden elements or AJAX calls.
Remember Caches HTML content using localStorage to improve performance.
Selectable Makes table rows (tr tags) selectable by clicking anywhere on the row.
Visualization Renders a chart from the Google Visualization library.

Behaviors are implemented as follows:

Behavior.addGlobalFilter(function(element, api) {
  var something = new SomeObject(...);
  var myOption = api.get('someOption');

  var options = api.getAs({
    optionA: String,
    optionB: Number,
    optionC: String
  });
  alert(options.optionA);
  ... extra code ...
  api.onCleanup(function(){
    // remote events, extra DOM elements created, etc. to manage memory
  });
});

Applying Behaviors to Dynamic Content

If DOM content is created dynamically (either by an AJAX call or via javascript), one should apply behaviors with:

qbo3.behavior.apply(element);

For example, creating a new data control with javascript could be done with:

myDate = new Element('input', {
  'type': 'text',
  'name': 'myDate',
  'data-behavior': 'Date',
  'data-date-options': {some options here}
}).inject(someLocation);
qbo3.behavior.apply(myDate);

Dropdown Lists

HTML select elements can be rendered from the database using the Dropdown behavior:

<select id="Status" name="Status" data-behavior="Dropdown" data-dropdown-options="{{
  'url': 'Application/ObjectStatus.ashx/List?output=Json', 
  'jsonEval': 'ObjectStatusCollection.ObjectStatusItem', 
  'value': 'ObjectStatus', 
  'text': 'Label', 
  'selected': 'MyCurrentValue', 
  'data': {{ 'Object': 'Organization', 'cache': '300' }} }}">
  <option value="">--</option>
</select>

The options passed to the behavior comprise:

Option Description
url The URL to call to populate the select element.
jsonEval A json expression that, when evaluated, returns an array of 'option' objects corresponding to each select option tag.
value The option object property to use as the option element's value.
text The option object property to use as the option element's HTML.
data Parameters to pass to the url.

The qbo3.DropdownType object contains the standard options for commonly used drop down lists, including:

  • ObjectStatus
  • ObjectType
  • State

To leverage these predefined options, specify a 'type' in the behavior options. For example:

<select id="Status" name="Status" data-behavior="Dropdown" data-dropdown-options="{{ 
  'type': 'ObjectStatus' , 
  'data': {{ 'Object': 'Loan' }} }}">
  <option value="">--</option>
</select>

ObjectBind

The ObjectBind behavior is used to bind an HTML element (typically a div tag) to some dynamically generated content. It is the most commonly used method for fetching data via AJAX from QBO.

Render a Decision Search panel:

<div id="search" class="tab-pane" data-behavior="ObjectBind" data-objectbind-options="{{ 'class': 'qbo3.DecisionObject', 'method': 'Search', 'render': false, 'listen': ['search'], 'cacheKey': 'Decision.Home.Search' }}">.</div>

Render a Contact Select panel:

<div id="select" class="span12" data-behavior="ObjectBind" data-objectbind-options="{{ 'class': 'qbo3.ContactObject', 'method': 'Select', 'remember': false, 'data': {{'ID': '{ContactID}' }} }}">.</div>

Render a Smart Worklist Dashboard:

<div data-behavior="ObjectBind" data-objectbind-options="{{ 'class': 'qbo3.SmartWorklistMemberObject', 'cacheKey': 'SmartWorklistHome-Current', 'method': 'Dashboard', 'listen': ['refreshAll'], 'data': {{'Dimension': 'SmartWorklistID', 'Transform': 'SmartWorklistMember.Dashboard.xslt', 'SortBy': 'SmartWorklist'}} }}">.</div>

The key ObjectBind options include:

Option Desccription
class (Required) Name of the qbo3.AbstractObject-derived class to use to render the data.
method The name of the method or operation to execute when rendering the data
data Parameters to pass to the server when making the AJAX call
render (Defaults to true) Whether to invoke the class/method immediately (if false, the panel will 'wait' for some other javascript event to cause the panel to make the AJAX call).
listen An array of events to listen for on the behavior api; when any of these events are raised, the panel will make an AJAX call.
remember (Defaults to true) Whether to cache the panel's content in local storage, so when the user revisits the page, the content is fetched from disk instead of over the wire.
cacheKey Name of the key to store the panel's content as.
maxCacheDuration Maximum length content may remain in cache; once this time is exceeded, the cached content will be ignored and refreshed from the server.

In the examples above:

  • The Decision Search panel will call the URL Decision/Decision.ashx/Search
  • The Contact Select panel will call the URL Contact/Contact.ashx/Select?ID={some ContactID}
  • The Smart Worklist Dashboard panel will call the URL Decision/SmartWorklistMember.ashx/Dashboard?Dimension=SmartWorklistID&Transform=SmartWorklistMember.Dashboard.xslt&SortBy=SmartWorklist

Note that the ObjectBind behavior does not dictate what is in the data option; this depends on the method/operation being called.

Labels

The Labels behavior will display label (CollectionMember) rows based on an AJAX lookup of one or more records. It is designed to work with both select and search panels, using just one AJAX call regardless of the number of source objects being looked up.

For an select panel, add a div tag like this:

<div class="control-group">
  <label>Labels</label>
  <div class="controls">
    <span>
      <div data-behavior="Labels" data-labels-options="{{'object': 'Organization'}}" data-label="{OrganizationID}"/>
    </span>
  </div>
</div>

Note that the div tag containing the behavior includes both data-labels-options and data-label attributes.

For a search panel, add the label behavior to the tbody, and a data-label tag to each row:

<tbody data-behavior="Selectable Labels" data-labels-options="{{'object': 'Organization'}}" data-selectable-options="...">
...
  <tr>
    ...
    <td>
      <div data-label="{OrganizationID}"/>
    </td>
    ...
  </tr>
...
</tbody>

Options comprise:

Option Default Description
object (Required) The object the labels are bound to (e.g. Organization, Contact, Person, Loan, etc.).
selector div[data-label] The css selector used to determine what IDs to get labels for.
tokenHtml {Collection} <i class="icon-remove icon-white"></i> The html to render inside the token create for a label.
tokenCss badge The css class to add to the token.
activeCss badge-success The css class to add to the token if the label is active.
inactiveCss The css class to add to the token if the label is not active.
deleteSelector i.icon-remove The css selector triggering a delete.

Popup Windows (Modal Dialogs)

Popup windows are typically used to perform quick editing functions on web pages without redrawing the entire web page. Examples include:

  • Adding a new item from a search or list panel
  • Modifying an existing item from a search or list panel
  • Labeling items with a collection
  • Deleting items (in which case, the popup merely confirms the user's desire to Delete items)

For such purposes the qbo3.Popup class and corresponding Popup behavior is used to render the popup. It can be used as follows:

// Create a popup with script directly
// Note we store and cache popups with a global Popup object: qbo3.Popups
var popup = qbo3.Popups.get('myPopupDivTag');

// Create a popup from a remote URL 
qbo3.Popups.get('Theme.ashx/Render?Transform=Templates/Contact/CollectionMemberInsert.Popup.xslt');

// Create a popup from a remote URL that will be reused
// Step 1: define a key for the URL
qbo3.Popups.defineKey('MyReusablePopup', 'Theme.ashx/Render?Template=Templates/Contact/CollectionMemberInsert.Popup.xslt&cache=12000');
// Step 2: open the popup via the key
qbo3.Popups.get('MyResuablePopup');

// Create a popup from within a qbo3.AbstractObject-derived class to show information only
qbo3.getObject('MyObjectContainer').popup('Describe');

// Create a popup from within a qbo3.AbstractObject-derived class to send data to the server, and refresh the panel
qbo3.getObject('MyObjectContainer').popup('TagInsert', {method: 'CollectionMemberInsert'});    

// Create a popup from within a qbo3.AbstractObject-derived class to send data to the server, and display the results in the panel
qbo3.getObject('MyObjectContainer').popup('SmartSearch', {method: 'SmartSearch', invoke: 'html'}); 

// Pass form data to the method being called, via the popup.
qbo3.getObject('MyObjectContainer').popup('Confirm', {method: 'MyMethod', formData: {'Foo': 'Bar'}}); 


// Define commonly used popups
qbo3.Popups.define({
 'SmartSearch': 'Theme.ashx/Render?Transform=Templates/Application/SmartSearch.Popup.xslt&cache=12000',
 'TagInsert': 'Theme.ashx/Render?Transform=Templates/Contact/CollectionMember.Popup.xslt&cache=12000'
});

First and foremost, popups can be rendered simply by render some HTML tag (usually a div tag) with a hidden class. Calling qbo3.Popups.get() will render the popup with effects, based on the BootStrap UI. This is simple, straight-forward, and a variation on a common javascript library theme: show modal popups.

In QBO however, we frequently wish to show fundamentally the same popup across multiple pages or panels. For example, the notion of adding a bunch of items to a Collection may be used for a list of Organizations, Contacts, Documents, Messages or Tasks. Rather than including the HTML markup for adding items to a collection in the Organization, Contact, Document, and Message rendering code, we define a key in qbo3.Popups, from which all these panels can reference it. The components involved include:

  • Bootstrap.Popup.js (part of mootools.js):
    • one of many slick modal popup javascript classes (this is a behavior)
    • if you want a different modal popup class, you can wrap it with qbo3.Popup.js, and still leverage the rest of qbo3.Popup functionality.
  • qbo3.Popup.js:
    • defines the qbo.Popup class
    • defines the Popup behavior
    • defines commonly used Popup URLs (e.g. 'TagInsert': CollectionMember.Popup.xslt)
  • {Project}.js: (project-specific javascript files, such as qbo.Contact.js, qbo.Message.js, etc.) defines project specific Popup URLs
  • qbo3.AbstractObject:
    • popup(element, options): shows a popup, binding the popup's save and cancel methods to the popupSave and popupCancel,
    • popupSave(data): accepts data from the popup, and invokes a method with the data, then hides the popup
    • popupClear(): hides the popup
  • Theme.ashx: the Render method renders an XSLT without making any database calls directly

Leveraging Popups from qbo3.AbstractObject

Often, popups are used as dialogs prior to executing some AJAX call, including:

  • Deleting items
  • Copying templates
  • Prompting for custom parameters to a statement or method

The qbo3.AbstractObject javascript class provides popup, popupSave, popupCancel, and popupClear methods. Usually, all you will need to use is popup.

Popup Delete example

Deleting items is usually accomplished from a list of records (e.g. a Search page), by adding a menu item like this:

<li onclick="qbo3.getObject(this).popup('Confirm', {{method: 'Delete', addIds: true }});">

Items to note:

  • qbo3.getObject(this) will return a javascript class derived from qbo3.AbstractObject (e.g. a qbo3.MessageObject)
  • popup(element, options):
    • element is the name of an id tag, an entry in the qbo3.Popups object, or a URL
    • options controls how the qbo3.AbstractObject deals with the popup, including:
      • method: name of the method to call
      • addIds: if true, the method called will include form elements from the popup, as well as IDs from the calling panel

Popup Copy Example

Copying a template is usually accomplished from a Summary page, by adding a menu item like this:

<li onclick="qbo3.getObject(this).popup(
  'Theme.ashx/Render?Transform=Templates/Decision/DecisionTemplate.Copy.xslt&amp;ID={DecisionTemplateID}', {{
    'method': 'Copy', 
    'invoke': 'json', 
    'callbacks': function(json) {{
      document.location = 'Decision/DecisionTemplate.ashx/Summary?ID=' +  json.DecisionTemplateCollection.DecisionTemplateItem[0].DecisionTemplateID;
    }} 
  }}
);">

Items to note:

  • There is no pre-defined popup in qbo3.Popups (since this is not reused between pages)
  • popup(element, options):
    • element is a relative URL to the popup to be rendered
    • options.method is 'Copy'; the Copy statement will be called if the user clicks Save on the popup
    • options.invoke is json; qbo3.AbstractObject.invokeJson() will be called when the user clicks save on the popup
    • options.callbacks defines what popupSave should pass to invokeJson as callbacks
    • in this case, we grab the new DecisionTemplateID, and redirect the user to the new DecisionTemplate

Matrix Lookups

The MatrixLookup behavior sets a form element value based on an AJAX call to Matrix/Lookup. For example, assume a Matrix named 'Consultant Rates':

Consultant Activity Rate
- - 125.00
[email protected] 400.00
DB Tuning 200.00
Project Management 175.00
[email protected] Project Management 150.00

From here, we can create a form containing a Consultant select element, an Activity select element, and a Rate input element:

<form>
  <select class="dimension" name="Consultant">...</select>
  <select class="dimension" name="Activity>...</select>
  <input name="Rate" data-behavior="MatrixLookup" data-matrixlookup-options="{{'matrix': 'Consultant Rates', 'measure': 'Rate', 'dimensions': '.dimension'}}"/>
  ...
</form>

As a user changes the Consultant or Activity select elements, the Rate element will automatically be adjusted to the proper value.

Options Type Description
matrix String, required Name of the Matrix.
measure String, required The output measure to use when setting the behavior element's value.
dimensions String, defaults to .dimensions) A CSS selector that determines which form elements are to be passed to the Matrix/Lookup method.
useSelectText Boolean, defaults to true If true, select dimensions will be based on the selected text, rather than the selected value.

When the behavior is applied, each element returned by the options.dimensions css selector will have a change event handler added to it, causing a Matrix/Lookup AJAX call to be fired. The callback from this call sets the value of the behavior's element.

Depend Behavior

A 'dependency' includes the following use cases:

  • Field A should be enabled only if Field B is not empty
  • Field A should be enabled only if Field B, C, ... or N is not empty
  • Field A should be enabled only if Fields B, C, ... and N are not empty
  • An element should be visible only if Field B is not empty
  • An element should be visible only if Field B, C, ... or N is not empty
  • An element should be visible only if Fields B, C, ... and N are not empty

Note that the Depend behavior works well with the 'required' validation; if elements are disabled, they are not considered required.

Field A should be enabled only if Field B is not empty

<input type="text" id="FieldA" name="FieldA" value="{//FieldA}" 
  data-behavior="Depend" data-depend-options="{{'depends': 'FieldB'}}"/>
<input type="text" id="FieldB" name="FieldB" value="{//FieldB}"/>

Field A should be enabled only if Field B or C is not empty

<input type="text" id="FieldA" name="FieldA" value="{//FieldA}" 
  data-behavior="Depend" data-depend-options="{{'depends': 'FieldB,FieldC'}}"/>
<input type="text" id="FieldB" name="FieldB" value="{//FieldB}"/>
<input type="text" id="FieldC" name="FieldC" value="{//FieldC}"/>

Field A should be enabled only if Fields B and C are not empty

<input type="text" id="FieldA" name="FieldA" value="{//FieldA}" 
  data-behavior="Depend" data-depend-options="{{'depends': 'FieldB,FieldC', 'condition': 'and'}}"/>
<input type="text" id="FieldB" name="FieldB" value="{//FieldB}"/>
<input type="text" id="FieldC" name="FieldC" value="{//FieldC}"/>

Show a group of elements only if a checkbox is checked

<input type="checkbox" id="ExtraStuff" value="1"/>
<div data-behavior="Depend" data-depend-options="{{'depends': 'ExtraStuff'}}">
  <input type="text" name="FirstName" value="//FirstName"/>
  <input type="text" name="LastName" value="//LastName" class="required"/>
</div>

Options for the Depend behavior:

Option Required Description
depends Yes a list of element names that determine whether an element is enabled or not
condition No (default is or) and or or
'resetOnDisable' No (default is true) if true, disabled fields will have their value reset to the original values at rendering time when disabled.
emptyOnDisable No (default is false) if true, disabled fields will have their value removed when disabled.
required No (default is true) if true, the required validator is removed for disabled fields.

Example using all options:

<input type="text" id="FieldA" name="FieldA" value="{//FieldA}" 
  data-behavior="Depend" data-depend-options="{{
    'depends': 'FieldB,FieldC', 
    'condition': 'and',
    'resetOnDisable': 'false',
    'emptyOnDisable': 'true',
    'required': 'true'
}}"/>

Form Validation

QBO uses Mootool's Validators pattern for form field validation. The qbo.Validation.js implements a Validator behavior.

Hard and Soft Validators

Hard validators require the field meet the validator data conditions, regardless of business conditions. For example, one may not enter 'abc' into a money field, regardless of circumstance. Examples of hard validators include: qbo-money, qbo-int, qbo-numeric, qbo-percent, qbo-phone, qbo-ssn, qbo-zip, qbo-date.

Soft validators require the field meet the validator data conditions, but will allow a user to bypass the requirement in some situations. For example, a complex form with dozens of fields might require a user to gather the data across multiple visits, and save the form between visits. In this case, a required validator will warn the user that the field has not been filled out, but may allow the user to save the form so they can return to it later. Examples of soft validators include: required, equality-validator, range-validator-quantity, range-validator-percent, lessthan-validator, greaterthan-validator.

For a validator to be considered a “soft” validator, it must be included in the softValidators array within the Validator behavior defined in qbo.Validation.js.

It is common for an input to have both hard and soft validators. For instance, an input can be a date field, requiring a hard money validation, and it can be required, which is a soft validation.

<form data-behavior="Validator">
  <input name="MyField" class="required qbo-money"/> 
  <button type="submit" class="pause">Pause</button>
  <button type="submit" class="save">Save</button>
</form>

In this example, the Pause button will require only hard validators to pass, while the Save button will require both hard and soft validators to pass.

Justifications

Some elements in a form may have business rules attached to them. For example, an appraisal form might require comparable sales be within a few miles of the subject property, which is usually reasonable, unless the subject property is in a rural area. QBO can enable users to bypass such a business rule with a justification. The justification behavior will allow a user to bypass a validator if they enter a reason the validation cannot be met.

Canned Validators

Validator Description
required The element value must not be empty upon saving.
qbo-xml The element value must valid xml.
qbo-money The element value must be a valid currency amount.
qbo-int The element value must be a valid integer.
qbo-numeric The element value must be a valid number.
qbo-percent The element value must be a valid percent.
qbo-phone The element value must be formatted as a valid US phone number.
qbo-ssn The element value must be formatted as a valid US social security number.
qbo-zip The element value must be formatted as a valid US zip code (or zip + 4).
qbo-date The element value must be formatted as a valid date.
qbo-field The element value must be start with a letter, and be followed by any letter, number or underscore.
qbo-clean The element value will be stripped of extended characters.
⚠️ **GitHub.com Fallback** ⚠️