Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <div id="branching-form">
      <h1>Weee branching form</h1>

      <!-- Level 0 form field: person_age -->
      <div class="field">
        <label>How old are you?</label>
        <input type="text" name="person_age" placeholder="Age" />

        <!-- Level 1 form fields: children of person_age -->
        <div class="field hidden--no-hide" data-parent-branch="person_age" data-show-on-value="3..7">
          <p class="info"><span class="level">Level 1</span> Person's age is <b>between 3 & 7</b></p>
          <label>What's your favorite candy?</b></label>
          <input type="text" name="favorite_candy" placeholder="Favorite candy" />

          <!-- Level 2 form fields: children of favorite_candy -->
          <div class="field hidden---" data-parent-branch="favorite_candy" data-show-on-value=" StarBurst,skittles">
             <p class="info"><span class="level">Level 2</span> Person's favorite candy is <b>starburst or skittles</b></p>
            <label>What flavor?</b></label>
            <input type="text" name="what_flavor_starburst" placeholder="What flavor?" />
          </div>
          <div class="field hidden" data-parent-branch="favorite_candy" data-show-on-value="chocolate">
             <p class="info"><span class="level">Level 2</span> Person's favorite candy is <b>chocolate</b></p>
            <label>Which chocolate bar?</b></label>
            <input type="text" name="which_chocolate_bar" placeholder="Which chocolate bar?" />
          </div>
        </div>

        <!-- Level 1 form fields: children of person_age -->
        <div class="field hidden" data-parent-branch="person_age" data-show-on-value="!5,!7">
           <p class="info"><span class="level">Level 1</span> Person's age <b>does not equal 5 or 7</b></p>
          <label>Did you start kindergarden this year?</label>
          <input type="hidden" name="started_kindergarden_this_year" />
          <span class="custom-radio"> YES</span>
          <span class="custom-radio"> NO</span>

          <!-- Level 2 form fields: children of started_kindergarden_this_year -->
          <div class="field hidden" data-parent-branch="started_kindergarden_this_year" data-show-on-value="yes">
             <p class="info"><span class="level">Level 2</span> Person <b>did</b> start kindergarden this year</p>
            <label>What kindergarden do you go to?</b></label>
            <input type="text" name="what_kindergarden" />
          </div>
        </div>

        <!-- Level 1 form fields: children of person_age -->
        <div class="field hidden" data-parent-branch="person_age" data-show-on-value="gte-21">
          <p class="info"><span class="level">Level 1</span>Person's age is <b>at least 21</b></p>
          <label>Did you graduate college?</label>
          <input type="radio" name="graduated_college" value="1">Yes &nbsp;
          <input type="radio" name="graduated_college" value="2">No &nbsp;
          <input type="radio" name="graduated_college" value="3">Maybe

          <!-- Level 2 form fields: children of started_kindergarden_this_year -->
          <div class="field hidden" data-parent-branch="graduated_college" data-show-on-value="1">
             <p class="info"><span class="level">Level 2</span> Person <b>did</b> graduate college</p>
            <label>What college did you graduate from?</b></label>
            <input type="text" name="graduated_from" />
          </div>

          <div class="field hidden" data-parent-branch="graduated_college" data-show-on-value="2,3">
             <p class="info"><span class="level">Level 2</span> Person <b>did not</b> graduate college</p>
            <label>What college would you like to attend?</b></label>
            <input type="text" name="wants_to_graduate_from" />
          </div>
        </div>
      </div>

      <!-- Level 0 form field: person_income -->
      <div class="field">
        <label>What is your annual income?</label>
        <select name="person_income">
          <option value="under30">$0 - $30,000</option>
          <option value="between30and60">$30,000 - $60,000</option>
          <option value="between60and100">$60,000 - $100,000</option>
          <option value="morethan100">More than $100,000</option>
        </select>

        <!-- Level 1 form fields: children of person_income -->
        <div class="field hidden" data-parent-branch="person_income" data-show-on-value="morethan100">
          <p class="info"><span class="level">Level 1</span> Person earns <b>more than $100,000</b></p>
          <label>How does it feel to be rich?</label>
          <input type="text" name="being_rich" />
        </div>
      </div>
    </div>

    <div id="fake-submit-button">Submit</div>
    <div id="fake-post-data"></div>

<p>This demo using form forker plugin <a target="blank" href="https://github.com/brittanystoroz/form-forker">https://github.com/brittanystoroz/form-forker</a></p>
              
            
!

CSS

              
                html {
  background: #e1e1e1;
}
body {
  font: 200 16px/18px'helvetica', arial, sans-serif;
  max-width: 600px;
  margin: 50px auto;
  color: #000;
  padding: 50px 50px;
  border: 1px solid #f2f2f2;
  background: #f5f5f5;
  border-radius: 5px;
}
h1 {
  font: 100 26px/30px'helvetica', arial, sans-serif;
  font-weight: 100;
  margin: 0 0 20px 0;
  color: #999;
  padding: 0px;
}
#branching-form div.field p.info {
  font-size: 11px;
  background: rgba(0, 0, 0, 0.3);
  padding: 2px 10px;
  color: #333;
  text-align: right;
  margin: 0 0 5px 0;
}
#branching-form div.field > div.field > p.info {
  background: rgba(0, 0, 0, 0.2);
}
#branching-form div.field > div.field > div.field > p.info {
  background: rgba(0, 0, 0, 0.1);
}
#branching-form div.field p.info span.level {
  float: left;
}
#test {
  display: inline;
}
#branching-form div.field {
  padding: 15px 0px 15px 15px;
  height: auto;
  transition: padding 0.2s ease 0.0s;
}
#branching-form div.field label {
  display: block;
  padding: 5px 0px;
}
#branching-form div.field input {
  width: 99%;
  height: 18px;
}
#branching-form div.field input[type="radio"] {
  display: inline-block;
  margin: 10px 5px;
  width: auto;
  vertical-align: middle;
}
#branching-form div.field.hidden {
  overflow: hidden;
  height: 0px;
  padding: 0px 0px 0px 15px;
  transition: padding 0.2s ease 0.0s;
}
#branching-form div.field span.custom-radio {
  border: 1px solid #bfbfbf;
  border-radius: 5px;
  display: inline-block;
  margin: 5px 0 10px 0px;
  font: bold 10px sans-serif;
  color: #717171;
  background-color: #ffffff;
  padding: 4px 9px;
  cursor: pointer;
}
#branching-form div.field span.custom-radio.radio-selected {
  background-color: #cbcbcb;
}
#fake-submit-button {
  background-color: #6288a5;
  border: 1px solid rgb(83, 118, 145);
  border-radius: 3px;
  font: 600 13px/20px'helvetica', arial, sans-serif;
  text-transform: uppercase;
  padding: 5px 15px;
  max-width: 100px;
  text-align: center;
  margin: 0 auto;
  color: #fff;
}
#fake-submit-button: hover {
  background-color: #326891;
  border-color: #325c8a;
  cursor: pointer;
}
#fake-post-data {
  margin: 40px auto;
  max-width: 600px;
  font: 400 12px/16px'courier', serif;
  word-wrap: break-word;
}
#fake-post-data h2 {
  font: 600 10px/16px'helvetica', arial, sans-serif;
  color: #666;
  text-transform: uppercase;
  background-color: #eee;
  padding: 5px 10px 3px 10px;
}
              
            
!

JS

              
                /*
 * Form Forker - jQuery / Zepto forkable form plugin
 *
 * Copyright (c) 2014 Brittany Storoz
 *
 * Licensed under the MIT license:
 *   http://www.opensource.org/licenses/mit-license.php
 *
 * Project home:
 *   http://www.brittanystoroz.github.io/form-forking
 *
 * Version: 0.0.0
 *
 */

;
(function($, window, document, undefined) {

  "use strict";

  $.fn.forkable = function(opts) {

    var BranchingForm = function(form, options) {
      var opts = options || {};

      this.$domForm = form;
      this.opts = opts;

      this.init();
    };

    BranchingForm.prototype = {
      testChangeEvent: function(e) {
        return(e);
      },
      getForks: function() {
        var self = this,
            forkNames = [],
            forkInputs = [],
            childrenFormFields = self.$domForm.find('div[data-parent-branch]'); // TO DO: don't limit this to div elems

        $.each(childrenFormFields, function(index, childFormField) {
          var forkName = $(childFormField).data("parent-branch");
          if (forkNames.indexOf(forkName) === -1) {
            forkNames.push(forkName);
            var fork = self.$domForm.find('input[name="' + forkName + '"], select[name="' + forkName + '"]');
            forkInputs.push(fork);
          }
        });
        self.forks = forkInputs;
      },
      init: function() {
        var self = this;
        self.getForks();
        self.$domForm.addClass('forkable');

        /* If custom evaluation methods have been defined, make them available in the branching form */
        if (self.opts.branchingMethods) {
          $.each(self.opts.branchingMethods, function(key, val) {
            if (typeof self[key] !== "undefined") {
              console.warn("Note: You are overriding one of Form Forker's default branching methods with \"" + key + "\". If this was not your intention, try renaming your custom branching method.");
            }
            self[key] = val;
          });
        }

        /* Set up event handlers to monitor the values of our forks */
        $.each(self.forks, function(index, fork) {
          var branchMethod = $(fork).data('branching-fn'); // determine the type of evaluation we need to do on the input value
          if ($(fork).prop('type') === 'text') {
            $(fork).keyup(function() {
              $(fork).trigger('change');
            });
          }

          $(fork).change(function(e) {
            self.testChangeEvent(e.currentTarget);
            var childBranches = self.$domForm.find('div[data-parent-branch=' + $(fork).prop('name') + ']'),
              userEnteredValue = $(fork).val();

            if (!branchMethod && isNaN(parseInt(userEnteredValue))) {
              self.stringEval(childBranches, userEnteredValue);
            } else if (!branchMethod) {
              self.integerEval(childBranches, parseInt(userEnteredValue));
            } else {
              self[branchMethod](childBranches, userEnteredValue);
            }
          });
        });
      },

      /* Gather any values from inputs that are currently displayed to submit */
      postData: function() {
        var unhiddenFields = this.domForm.find('div:not(".hidden")'),
          inputFieldsToSend = unhiddenFields.find('> input, > textarea, > select'),
          postData = {};

        $.each(inputFieldsToSend, function(index, input) {
          var postVal;
          ($(input).prop('type') === 'radio') ? postVal = $('input:checked').val() : postVal = $(input).val(); // if input is a radio button, only send the value of the checked radio

          if (postVal) { // if the input value isn't blank, add a key to the postData object
            postData[$(input).prop('name')] = postVal;
          }
        });

        return postData;
      },


      util: {

        /* Fake radio buttons so we can give them custom styling; will probaby need to support custom select menus, checkboxes, etc. in the future */
        handleCustomRadios: function(e) {
          var $elem = $(e.currentTarget), // radios span that was clicked on
            $associatedInput = $elem.parent().find('> input'), // the input whose value needs to be toggled based on our span selection
            selectedRadioVal = $.trim($elem.html()); // the innerHTML of our selected span

          $associatedInput.val(selectedRadioVal); // set the hidden input value equal to the selected span
          $associatedInput.trigger('change'); // manually trigger the change even on our input so we can evaluate it's value
          $elem.addClass('radio-selected').siblings().removeClass('radio-selected');
        },


        /* Remove leading/trailing whitespace & convert to lowercase to ensure no false negatives when comparing strings */
        cleanString: function(dirtyString) {
          return dirtyString.toLowerCase().replace(/^\s+|\s+$/g, '');
        },


        /*
         * Map string-based operators to their arithmetic equivalent and return the result of the evaluated expression
         * @param {string} comparisonOperator The string-based comparison to convert to an arithmetic operator // possible values: gte, gt, lte, lt, eq, noteq
         * @param {integer} userEnteredValue The current integer value a user has entered
         * @param {integer} integerToCompareAgainst The integer we will compare the userEnteredValue to as specified by the child branch's data-show-on-value attribute
         */
        convertToArithmeticComparison: function(comparisonOperator, userEnteredValue, integerToCompareAgainst) {
          switch (comparisonOperator) {
            case 'gte':
              return (userEnteredValue >= integerToCompareAgainst);
            case 'gt':
              return (userEnteredValue > integerToCompareAgainst);
            case 'lte':
              return (userEnteredValue <= integerToCompareAgainst);
            case 'lt':
              return (userEnteredValue < integerToCompareAgainst);
            case 'eq':
              return (userEnteredValue === integerToCompareAgainst);
            case 'noteq':
              return (userEnteredValue !== integerToCompareAgainst);
          }
        },


        /*
         * Parse the data-show-on-value attribute of child branches for integer evaluations.
         * @param {integer} userEnteredValue The current integer value a user has entered into an input with dependent child branches
         * @param {string} conditionsToBeMet The conditions to be met for showing a child branch, as specified by the child branch's
                         data-show-on-value attribute // possible values: gte-{int}, gt-{int}, lte-{int}, lt-{int}, eq-{int}, noteq-{int}
         * @param {string} logicalOperator The logical operator to use when evaluating multiple conditions // possible values: _and_, _or_
         */
        parseNumericConditions: function(userEnteredValue, conditionsToBeMet, logicalOperator) {
          var self = this,
            userInput = parseInt(userEnteredValue),
            valuePassesConditions;

          /* Loop through the conditions that must be met to show a particular child branch */
          $.each(conditionsToBeMet, function(index, condition) {
            var conditionToEvaluate = condition.split('-'),
              comparisonOperator = conditionToEvaluate[0], // gte, gt, lte, lt, eq, noteq
              integerToCompareAgainst = parseInt(conditionToEvaluate[1]), // integer we will compare the userEnteredValue to
              conditionResult = self.convertToArithmeticComparison(comparisonOperator, userInput, integerToCompareAgainst);

            /* If there are multiple conditions but the logical operator is an ||, we can return true after the first condition that passes. */
            /* If there is no logical operator (only 1 condition is specified), or it is an &&, we must loop through all conditions and only return true if all pass. */
            if (logicalOperator && logicalOperator === '_or_' && conditionResult === true) {
              valuePassesConditions = true;
              return false;
            } else if (logicalOperator && logicalOperator === '_and_' && conditionResult === false) {
              valuePassesConditions = false;
              return false;
            } else {
              valuePassesConditions = conditionResult;
            }

          });

          return valuePassesConditions;
        },
      },


      /*
       * Method for comparing integer values with arithmetic operators. This code is method is CRAZY.
       * @param {object} opts Options containing references to the child branches and the user-entered value to be evaluated
       */
      integerEval: function(childBranches, userEnteredValue) {
        var self = this,
          userInput = parseInt(userEnteredValue), // convert input value from string to integer
          logicalOperator,
          numericOperations,
          conditionsForShowingChildBranch;

        /* for each of the possible child branches, find the condition that must be met in order for them to be shown */
        $.each(childBranches, function(index, childBranch) {
          var $childBranch = $(childBranch),
            showOnValueAttribute = $(childBranch).data('show-on-value'),
            conditionType = typeof showOnValueAttribute,
            childBranchShouldBeShown;

          if (conditionType === 'string') {
            if (showOnValueAttribute.indexOf(',') !== -1) {
              conditionType = 'integerArray';
            } else if (showOnValueAttribute.indexOf('..') !== -1) {
              conditionType = 'inclusiveRange';
            } else if (showOnValueAttribute.match(/\_and\_|\_or\_/g) !== null) {
              conditionType = 'logicalExpression';
            }
          }

          switch (conditionType) {
            case 'number': // data-show-on-value="1"
              numericOperations = ['eq-' + showOnValueAttribute];
              break;

            case 'string': // data-show-on-value="gte-8"
              if (showOnValueAttribute.charAt(0) === "!") {
                numericOperations = ["noteq-" + showOnValueAttribute.slice(1)]
              }
              else {
                numericOperations = [showOnValueAttribute];
              }
              break;

            case 'inclusiveRange': // data-show-on-value="3..7"
              conditionsForShowingChildBranch = showOnValueAttribute.split('..');
              logicalOperator = "_and_"; // _and_, _or_
              numericOperations = ["gte-" + conditionsForShowingChildBranch[0], "lte-" + conditionsForShowingChildBranch[1]]; // gte-{int}, lte-{int}
              break;

            case 'integerArray': // data-show-on-value="1,2,3,4,5"
              conditionsForShowingChildBranch = showOnValueAttribute.split(',');
              numericOperations = [];
              $.each(conditionsForShowingChildBranch, function(index, condition) {
                if (condition.charAt(0) === "!") {
                  numericOperations.push('noteq-' + condition.slice(1));
                  logicalOperator = '_and_';
                }
                else {
                  numericOperations.push('eq-' + condition);
                  logicalOperator = '_or_';
                }
              });
              break;

            case 'logicalExpression': // data-show-on-value="gte-3_and_lte-7"
              conditionsForShowingChildBranch = showOnValueAttribute.split(/(\_and\_|\_or\_)/g);
              logicalOperator = conditionsForShowingChildBranch[1]; // _and_, _or_
              numericOperations = [conditionsForShowingChildBranch[0], conditionsForShowingChildBranch[2]]; // gte-{int}, gt-{int}, lte-{int}, lt-{int}, eq-{int}, noteq-{int}
          }


          childBranchShouldBeShown = self.util.parseNumericConditions(userInput, numericOperations, logicalOperator);
          (childBranchShouldBeShown) ? $childBranch.removeClass('hidden').find('> div.field').removeClass('hidden') : $childBranch.addClass('hidden');
          $childBranch.find('div.field').addClass('hidden');

        });
      },


      /*
       * Compare string values. Will also work for boolean values by comparing their string equivalents, though this could get buggy.
       * @param {object} opts Options containing references to the child branches and the user-entered value to be evaluated
       */
      stringEval: function(childBranches, userEnteredValue) {
        var self = this,
          userInput = self.util.cleanString(userEnteredValue),
          childBranchShouldBeShown;

        $.each(childBranches, function(index, childBranch) {
          var $childBranch = $(childBranch),
            conditionsForShowingChildBranch = $childBranch.data('show-on-value').toString().split(','); // grab the conditions that must be true in order to show this child branch
          conditionsForShowingChildBranch = $.map(conditionsForShowingChildBranch, function(condition) {
            return self.util.cleanString(condition);
          });
          childBranchShouldBeShown = (conditionsForShowingChildBranch.indexOf(userInput) !== -1);
          (childBranchShouldBeShown) ? $childBranch.removeClass('hidden').find('> div.field').removeClass('hidden') : $childBranch.addClass('hidden');
          $childBranch.find('div.field').addClass('hidden');
        });
      }
    }; // end BranchingForm.prototype

    /* Initialize the new forkable form */
    var userBranchingForm = new BranchingForm($(this), opts);
    return userBranchingForm;

  }; // end $.fn.forkable

})(window.jQuery || window.Zepto, window, document);



///TESTING

(function () {
  $('#branching-form').forkable({
    branchingMethods: {
      "customEval": function(childBranches, userEnteredValue) {
        console.log("CUSTOM This: ", this);
        console.log("CB: ", childBranches);
        console.log("User Val: ", userEnteredValue)
      }
    }
  });
  $('#branching-form').find('input, select, textarea').eq(0).trigger( 'change');




  // /* Attach click event handler for custom radios */
  // $('#branching-form').find('div span.custom-radio').click(function(e) {
  //   userFeedbackForm.util.handleCustomRadios(e);
  // });

  // /* Stub submit button to display postData on form submit */
  // $('#fake-submit-button').click(function(e) {
  //   var stringifiedPostData = userFeedbackForm.postData();
  //   stringifiedPostData = JSON.stringify(stringifiedPostData);
  //   var radioVal = $(userFeedbackForm).find('input[type=radio').val();
  //   $('#fake-post-data').html('<h2>Post Data on Submit:</h2>' + stringifiedPostData);
  // });

})();




              
            
!
999px

Console