1.0.6 • Published 8 years ago

schmapit v1.0.6

Weekly downloads
4
License
ISC
Repository
github
Last release
8 years ago

Schmapit client-side HTML template sequencing library

Why does this exist?

This library will display chunks of HTML, which usually take the form of a question and an answer. When the user answers a question, the library consults the behavior that you've defined for that question, and then displays the next HTML chunk to the user, based on their answer.

Decision trees are not decision trees in the real world.

Many decision tree libraries exist for the simple use case of a binary decision tree, some implementing an ID3 algorithm which is clever. But:

  • What if your decision tree is not binary?
  • What if part of your decision tree needs to run in a loop, to collect a certain number of multiple pieces of data?
  • What if certain parts of your decision tree are embedded within others?
  • What if you have certain conditions which cause you to move backwards in the decision tree sequence?

This is why the entire decision tree visualization is useless for real-world decision flows for humans. In the real world, your actual processes are often times complicated, full of caveats and special scenarios, and the only way to deal with these is to write code.

Design philosophy

Mission: make it as easy as possible to write vanilla JavaScript to define an HTML sequence.

Face it: you will need to write custom code for your logic anyway, so the library you use should be minimal and easy to understand. Rather than try to wrap your project around an unrealistic abstraction, why not use simple HTML fragments and event handlers?

How is this used?

  1. The developer creates a variety of HTML templates that he/she would like to sequence
  • The templates contain HTML elements that function as "inputs" -- signals that the user is ready to move on to the next step. These inputs need special markup.
  • These examples assume Underscore template format, although any templating library that compiles into functions should work.
  1. The developer creates a variety of HTML templates that function as wrappers, or "themes" for other templates. These themes contain a special <%= templateContent %> variable, along with a step-wrapper class so Schmapit can find them in the DOM.
  2. The templates are precompiled and placed into their own file. This file makes the precompiled templates available under a "namespace".
  3. In the application, the developer creates an object, which defines one property (or "node") for each template:
  • The property names match the filenames of the templates, minus the extension
  • Each property has two properties: theme, which is the wrapper template that this template will be loaded inside of, and behavior, which contains all of the logic that runs when a user activates an input.
  1. In the application, the developer instantiates a Schmapit object with the template namespace, tells it the initial node to start on, and attaches the object containing all of the node definitions
  2. The application runs. When a user activates an input, its event handler (behavior) runs, which records the user's action, determines which template to show next, and appends the template using jQuery.

Example

The example app will ask the user if they want a phone or a computer. Based on what they select, it will then ask them for either a phone number or a computer model number. The last step will give them a message, which is formed using data from their answers.

Note that there are many ways to use a given library in a client-side script. The following example assumed that you are downloading files manually, and including them in the head of the HTML document in the traditional way.

question.html

<div class="question-container step-wrapper" data-step="<%= stepIndex %>">
  <%= templateContent %>
</div>

deviceQuestion.html

<h3>Which type of device do you want to purchase?</h3>
<span class="btn-group">
  <button type="button" class="node-input continue" data-step="<%= stepIndex %>" value="phone">A phone</button>
  <button type="button" class="node-input" data-step="<%= stepIndex %>" value="computer">A computer</button>
</span>

phoneQuestion.html

<h3>What type of phone do you want to purchase?</h3>
<span class="btn-group">
  <button type="button" class="node-input continue" data-step="<%= stepIndex %>" value="smart">Smartphone</button>
  <button type="button" class="node-input" data-step="<%= stepIndex %>" value="basic">Basic phone</button>
</span>

computerQuestion.html

<h3>What operating system would you like your computer to have?</h3>
<span class="btn-group">
  <button type="button" class="node-input continue" data-step="<%= stepIndex %>" value="mac">Apple OS X</button>
  <button type="button" class="node-input" data-step="<%= stepIndex %>" value="windows">Microsoft Windows</button>
  <button type="button" class="node-input" data-step="<%= stepIndex %>" value="linux">Linux</button>
</span>

instruction.html

<div class="instruction-container step-wrapper" data-step="<%= stepIndex %>">
  <%= templateContent %>
</div>

contactInstruction.html

<h3>The product you selected will cost <%= cost %>.</h3>
<span class="btn-group">
  <button type="button" class="node-input continue" data-step="<%= stepIndex %>">Continue</button>
</span>

finished.html

<div class="finished-container step-wrapper" data-step="<%= stepIndex %>">
  <%= templateContent %>
</div>

finishedMessage.html

<h2>Good luck with your new <%= type %> <%= device %>!</h2>

index.html

<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Device Decision Tree</title>
    <script src="templates.js"></script>
    <script src="index.js"></script>
  </head>
</html>
<body>
  <!-- Schmapit will update an element with class "progress-bar" after every step -->
  <progress class="progress-bar">progress</progress>
  <h1>This is our decision tree.</h1>
  <!-- This DOM id is where Schmapit will do its thing, hard-coded at the moment -->
  <div id="initial-step"></div>
</body>

app.js (using an npm/browserify environment)

var schmapit = require('schmapit');
var $ = require('jquery');

var app = new schmapit({ name: 'deviceQuestion' }, schmapitExample);

var nodes = {
  deviceQuestion: {
    theme: "question",
    behavior: function(step, context) {
      $('.node-input', step).on('click', function(event) {
        var answer = $(event.target).attr('value');
        switch (answer) {
          case 'phone':
            var nextStep = { name: 'phoneQuestion' };
            break;
          case 'computer':
            var nextStep = { name: 'computerQuestion' };
            break;
        }
        app.update(this, { label: "Device wanted", value: answer }, 2, nextStep);
      });
    }
  },
  phoneQuestion: {
    theme: "question",
    behavior: function(step, context) {
      $('.node-input', step).on('click', function(event) {
        var answer = $(event.target).attr('value');
        var nextStep = {
          name: 'contactInstruction',
          placeholders: { cost: "$300" }
        }
        app.update(this, { label: "Type of phone", value: answer }, 1, nextStep);
      });
    }
  },
  computerQuestion: {
    theme: "question",
    behavior: function(step, context) {
      $('.node-input', step).on('click', function(event) {
        var answer = $(event.target).attr('value');
        var nextStep = {
          name: 'contactInstruction',
          placeholders: { cost: "$600" }
        }
        app.update(this, { label: "Type of computer", value: answer }, 1, nextStep);
      });
    }
  },
  contactInstruction: {
    theme: "question",
    behavior: function(step, context) {
      $('.node-input', step).on('click', function(event) {
        // Here, we demonstrate pulling answers out of previously-answered questions to make decisions.
        var device = app.stepResults.deviceQuestion[0].value;

        switch (device) {
          case 'phone':
            var type = app.stepResults.phoneQuestion[0].value;
            break;
          case 'computer':
            var type = app.stepResults.computerQuestion[0].value;
            break;
        }
        var nextStep = {
          name: 'finishedMessage',
          placeholders: {
            type: type,
            device: device
          }
        }
        app.update(this, {}, 0, nextStep);
      });
    }
  },
  finishedMessage: {
    theme: "finished",
    behavior:  function(step, context) {}
  },
}

app.nodes = nodes;

$(document).ready(function() {
  app.reset();
});

Limitations

Progress bars are incredibly difficult to do right; by default, the Schmapit library never allows the progress bar to go backwards.

In the simple example app, that might seem counterintuitive. But, consider a very complicated app, with many paths linking to each other at many different points: some steps run in loops, and some run multiple times... how would the Schmapit library know the future path the user might take?

For now, supply a "dumb" integer value when you call schmapit.update(), and the progress bar will never go below that value once the node is instanced into a step and delivered to the page.

1.0.6

8 years ago

1.0.5

8 years ago

1.0.4

8 years ago

1.0.3

8 years ago

1.0.2

8 years ago

1.0.1

8 years ago

1.0.0

8 years ago