/**
* jQuery Interdependencies library
*
* http://miohtama.github.com/jquery-interdependencies/
*
* Copyright 2012-2013 Mikko Ohtamaa, others
*/
/*global console, window*/
(function($) {
"use strict";
/**
* Microsoft safe helper to spit out our little diagnostics information
*
* @ignore
*/
function log(msg) {
if(window.console && window.console.log) {
console.log(msg);
}
}
/**
* jQuery.find() workaround for IE7
*
* If your selector is an pure tag id (#foo) IE7 finds nothing
* if you do jQuery.find() in a specific jQuery context.
*
* This workaround makes a (false) assumptions
* ids are always unique across the page.
*
* @ignore
*
* @param {jQuery} context jQuery context where we look child elements
* @param {String} selector selector as a string
* @return {jQuery} context.find() result
*/
function safeFind(context, selector) {
if(selector[0] == "#") {
// Pseudo-check that this is a simple id selector
// and not a complex jQuery selector
if(selector.indexOf(" ") < 0) {
return $(selector);
}
}
return context.find(selector);
}
/**
* Sample configuration object which can be passed to {@link jQuery.deps#enable}
*
* @class Configuration
*/
var configExample = {
/**
* @cfg show Callback function show(elem) for showing elements
* @type {Function}
*/
show : null,
/**
* @cfg hide Callback function hide(elem) for hiding elements
* @type {Function}
*/
hide : null,
/**
* @cfg log Write console.log() output of rule applying
* @type {Boolean}
*/
log : false,
/**
* @cfg checkTargets When ruleset is enabled, check that all controllers and controls referred by ruleset exist on the page.
*
* @default true
*
* @type {Boolean}
*/
checkTargets : true
};
/**
* Define one field inter-dependency rule.
*
* When condition is true then this input and all
* its children rules' inputs are visible.
*
* Possible condition strings:
*
* * **==** Widget value must be equal to given value
*
* * **any** Widget value must be any of the values in the given value array
*
* * **non-any** Widget value must not be any of the values in the given value array
*
* * **!=** Widget value must not be qual to given value
*
* * **()** Call value as a function(context, controller, ourWidgetValue) and if it's true then the condition is true
*
* * **null** This input does not have any sub-conditions
*
*
*
*/
function Rule(controller, condition, value) {
this.init(controller, condition, value);
}
$.extend(Rule.prototype, {
/**
* @method constructor
*
* @param {String} controller jQuery expression to match the `` source
*
* @param {String} condition What input value must be that {@link Rule the rule takes effective}.
*
* @param value Matching value of **controller** when widgets become visible
*
*/
init : function(controller, condition, value) {
this.controller = controller;
this.condition = condition;
this.value = value;
// Child rules
this.rules = [];
// Controls shown/hidden by this rule
this.controls = [];
},
/**
* Evaluation engine
*
* @param {String} condition Any of given conditions in Rule class description
* @param {Object} val1 The base value we compare against
* @param {Object} val2 Something we got out of input
* @return {Boolean} true or false
*/
evalCondition : function(context, control, condition, val1, val2) {
/**
*
* Codestar Framework
* Added new condition for Codestar Framework
*
* @since 1.0.0
* @version 1.0.0
*
*/
if(condition == "==" || condition == "OR") {
return this.checkBoolean(val1) == this.checkBoolean(val2);
} else if(condition == "!=") {
return this.checkBoolean(val1) != this.checkBoolean(val2);
} else if(condition == ">=") {
return Number(val2) >= Number(val1);
} else if(condition == "<=") {
return Number(val2) <= Number(val1);
} else if(condition == ">") {
return Number(val2) > Number(val1);
} else if(condition == "<") {
return Number(val2) < Number(val1);
} else if(condition == "()") {
return window[val1](context, control, val2); // FIXED: function method
} else if(condition == "any") {
return $.inArray(val2, val1.split(',')) > -1;
} else if(condition == "not-any") {
return $.inArray(val2, val1.split(',')) == -1;
} else {
throw new Error("Unknown condition:" + condition);
}
},
/**
*
* Codestar Framework
* Added Boolean value type checker
*
* @since 1.0.0
* @version 1.0.0
*
*/
checkBoolean: function(value) {
switch(value) {
case true:
case 'true':
case 1:
case '1':
//case 'on':
//case 'yes':
value = true;
break;
case false:
case 'false':
case 0:
case '0':
//case 'off':
//case 'no':
value = false;
break;
}
return value;
},
/**
* Evaluate the condition of this rule in given jQuery context.
*
* The widget value is extracted using getControlValue()
*
* @param {jQuery} context The jQuery selection in which this rule is evaluated.
*
*/
checkCondition : function(context, cfg) {
// We do not have condition set, we are always true
if(!this.condition) {
return true;
}
var control = context.find(this.controller);
if(control.size() === 0 && cfg.log) {
log("Evaling condition: Could not find controller input " + this.controller);
}
var val = this.getControlValue(context, control);
if(cfg.log && val === undefined) {
log("Evaling condition: Could not exctract value from input " + this.controller);
}
if(val === undefined) {
return false;
}
val = this.normalizeValue(control, this.value, val);
return this.evalCondition(context, control, this.condition, this.value, val);
},
/**
* Make sure that what we read from input field is comparable against Javascript primitives
*
*/
normalizeValue : function(control, baseValue, val) {
if(typeof baseValue == "number") {
// Make sure we compare numbers against numbers
return parseFloat(val);
}
return val;
},
/**
* Read value from a diffent HTML controls.
*
* Handle, text, checkbox, radio, select.
*
*/
getControlValue : function(context, control) {
/**
*
* Codestar Framework
* Added multiple checkbox value control
*
* @since 1.0.0
* @version 1.0.0
*
*/
if( ( control.attr("type") == "radio" || control.attr("type") == "checkbox" ) && control.size() > 1 ) {
return control.filter(":checked").val();
}
// Handle individual checkboxes & radio
if ( control.attr("type") == "checkbox" || control.attr("type") == "radio" ) {
return control.is(":checked");
}
return control.val();
},
/**
* Create a sub-rule.
*
* Example:
*
* var masterSwitch = ruleset.createRule("#mechanicalThrombectomyDevice")
* var twoAttempts = masterSwitch.createRule("#numberOfAttempts", "==", 2);
*
* @return Rule instance
*/
createRule : function(controller, condition, value) {
var rule = new Rule(controller, condition, value);
this.rules.push(rule);
return rule;
},
/**
* Include a control in this rule.
*
* @param {String} input jQuery expression to match the input within ruleset context
*/
include : function(input) {
if(!input) {
throw new Error("Must give an input selector");
}
this.controls.push(input);
},
/**
* Apply this rule to all controls in the given context
*
* @param {jQuery} context jQuery selection within we operate
* @param {Object} cfg {@link Configuration} object or undefined
* @param {Object} enforced Recursive rule enforcer: undefined to evaluate condition, true show always, false hide always
*
*/
applyRule : function(context, cfg, enforced) {
var result;
if(enforced === undefined) {
result = this.checkCondition(context, cfg);
} else {
result = enforced;
}
if(cfg.log) {
log("Applying rule on " + this.controller + "==" + this.value + " enforced:" + enforced + " result:" + result);
}
if(cfg.log && !this.controls.length) {
log("Zero length controls slipped through");
}
// Get show/hide callback functions
var show = cfg.show || function(control) {
control.show();
};
var hide = cfg.hide || function(control) {
control.hide();
};
// Resolve controls from ids to jQuery selections
// we are controlling in this context
var controls = $.map(this.controls, function(elem, idx) {
var control = context.find(elem);
if(cfg.log && control.size() === 0) {
log("Could not find element:" + elem);
}
return control;
});
if(result) {
$(controls).each(function() {
// Some friendly debug info
if(cfg.log && $(this).size() === 0) {
log("Control selection is empty when showing");
log(this);
}
show(this);
});
// Evaluate all child rules
$(this.rules).each(function() {
if(this.condition !== "OR"){
this.applyRule(context, cfg);
}
});
} else {
$(controls).each(function() {
// Some friendly debug info
if(cfg.log && $(this).size() === 0) {
log("Control selection is empty when hiding:");
log(this);
}
hide(this);
});
// Supress all child rules
$(this.rules).each(function() {
if(this.condition !== "OR"){
this.applyRule(context, cfg, false);
} else {
this.applyRule(context, cfg);
}
});
}
}
});
/**
* A class which manages interdependenice rules.
*/
function Ruleset() {
// Hold a tree of rules
this.rules = [];
}
$.extend(Ruleset.prototype, {
/**
* Add a new rule into this ruletset.
*
* See {@link Rule} about the contstruction parameters.
* @return {Rule}
*/
createRule : function(controller, condition, value) {
var rule = new Rule(controller, condition, value);
this.rules.push(rule);
return rule;
},
/**
* Apply these rules on an element.
*
* @param {jQuery} context Selection we are dealing with
*
* @param cfg {@link Configuration} object or undefined.
*/
applyRules: function(context, cfg) {
var i;
cfg = cfg || {};
if(cfg.log) {
log("Starting evaluation ruleset of " + this.rules.length + " rules");
}
for(i=0; i