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