A JavaScript library for creating interactive convivial decision flows.
The Convivial Decision Flow library allows you to create dynamic, interactive decision flows on your web pages. Users can navigate through a series of steps based on their choices, with the ability to store progress and display summaries of their selections.
- Interactive Steps: Guide users through a series of steps with clickable answers.
- Form Handling: Collect user input through forms and navigate based on their responses.
- Storage Options: Save user progress in local or session storage to maintain state across sessions.
- Dynamic Filtering: Show or hide content based on user input using custom filters.
- Summary Display: Automatically generate and display a summary of user choices at the end.
- Back and Restart Navigation: Allow users to navigate backward and restart the decision flow.
- Custom Function Execution: Define and execute custom functions securely from storage.
The library now supports both local and session storage for saving user progress. You can choose the type of storage by specifying it when initializing the ConvivialDecisionFlow
instance.
You can now define and store custom functions in the selected storage. These functions can be executed securely using the data-df-show
attribute. This feature helps in extending the functionality of the decision flow dynamically.
The library supports handling form submissions within the decision flow. Forms can collect user input and navigate to the next step based on the form's action attribute.
You can use data attributes to dynamically show or hide content based on user input. The supported filter criteria include:
- Variable Comparison: Compare stored variables against specified values.
- Example:
data-df-filter="var_age_gte_18"
(Shows content if age is greater than or equal to 18)
- Example:
- Step History: Show content based on previously visited steps.
- Example:
data-df-filter="visited_step-id"
(Shows content if a specific step was visited)
- Example:
Automatically generate summaries of user choices and display them in a designated section. The summary includes:
- Summary of Choices: A list of all the user's choices throughout the decision flow.
- History: Display the steps the user has taken.
- Submission Data: Show collected form data.
At the end of the decision flow, you can include divs to show a summary of user choices, the history of steps taken, and submission data. This can be configured in the HTML structure:
<div class="step" id="summary">
<h3>Summary</h3>
<div class="convivial-decision-flow__history"></div>
<div class="convivial-decision-flow__submission"></div>
</div>
You can define complex conditions in your decision flow using data-df-filter
attributes. The following examples illustrate how to use AND (+
) and OR (,
):
-
Logical AND: All conditions must be true.
<div data-df-filter="var_age_gte_18+var_nationality_eq_japanese"> Content for Japanese users aged 18 or older. </div>
-
Logical OR: At least one condition must be true.
<div data-df-filter="var_age_gte_18,var_nationality_eq_japanese"> Content for users aged 18 or older or Japanese users. </div>
-
Combination of AND and OR:
<div data-df-filter="!var_nationality_eq_japanese+var_age_gte_18,var_nationality_eq_japanese+var_age_gte_20"> Content for users who are either non-Japanese aged 18 or older, or Japanese aged 20 or older. </div>
To ensure the safety and security of executing custom functions in the Convivial Decision Flow library, we have implemented several measures to prevent potential injection attacks and unauthorized code execution. Here's a detailed explanation of these enhancements and why our approach is safer than using eval
.
We ensure that any input used in the functions is sanitized to prevent injection attacks. This includes validating function names to ensure they are valid JavaScript identifiers.
Function names are validated to match a specific pattern that ensures they are safe and valid. This prevents the execution of malicious code through crafted function names.
Functions are executed within a secure context to limit potential exploits. We avoid using eval
, which can execute arbitrary code and is a significant security risk.
Using eval
to execute dynamic code can lead to severe security vulnerabilities, as it allows execution of arbitrary code, which can be exploited by attackers. Our approach avoids these risks by:
- Validating Function Names: Ensuring that function names are valid JavaScript identifiers prevents the execution of unintended code.
- Executing in a Controlled Context: By defining and executing functions within a controlled context, we limit the scope of what can be executed.
- Avoiding Arbitrary Code Execution: Unlike
eval
, which can execute any string as JavaScript code, our method only allows predefined and validated functions to be executed.
We validate function names to ensure they are valid identifiers:
defineFunction(type, name, fn) {
if (typeof fn !== 'function') {
throw new Error('Provided argument is not a function');
}
if (!this.functions[type]) {
this.functions[type] = {};
}
// Ensure the function name is a valid identifier
const validName = /^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(name);
if (!validName) {
throw new Error('Invalid function name');
}
this.functions[type][name] = fn;
console.log(`Defined function "${name}" under type "${type}"`);
}
We validate that the function name is safe to use before executing:
executeFunction(type, name, el, args = []) {
if (!this.functions[type] || !this.functions[type][name]) {
throw new Error(`Function "${name}" not found in ${type}`);
}
// Validate that the function name is safe to use
const validName = /^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(name);
if (!validName) {
throw new Error('Invalid function name');
}
try {
return this.functions[type][name](this, el, ...args);
} catch (e) {
console.error(`Error executing function "${name}":`, e);
throw e;
}
}
We validate function names before adding event listeners:
_initializeFunctionCalls() {
document.querySelectorAll(`#${this.config.id} [data-df-show]`).forEach((element) => {
const functionName = element.getAttribute('data-df-show');
if (functionName) {
// Validate the function name
const validName = /^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(functionName);
if (validName) {
console.log(`Executing show function: ${functionName}`);
this.executeFunction('show', functionName, element);
} else {
console.warn(`Invalid function name: ${functionName}`);
}
}
});
document.querySelectorAll(`#${this.config.id} [data-df-show]`).forEach((element) => {
const functionName = element.getAttribute('data-df-show');
if (functionName) {
// Validate the function name
const validName = /^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(functionName);
if (validName) {
console.log(`Adding event listener for content function: ${functionName}`);
// Use a data attribute to track if the event listener has already been added
if (!element.hasAttribute('data-listener-added')) {
element.addEventListener('click', () => {
console.log(`Executing content function: ${functionName}`);
this.executeFunction('content', functionName, element);
});
// Mark this element as having the listener added
element.setAttribute('data-listener-added', 'true');
}
} else {
console.warn(`Invalid function name: ${functionName}`);
}
}
});
}
By implementing these security measures, we ensure that the Convivial Decision Flow library provides a secure environment for defining and executing custom functions, significantly reducing the risk of security vulnerabilities.
To use the Convivial Decision Flow library, you need to include both the JavaScript and CSS files in your HTML:
<link rel="stylesheet" type="text/css" href="path/to/style.min.css">
<script type="text/javascript" src="path/to/convivial_decision_flow.min.js" defer></script>
Create your decision flow using a series of nested div
elements. Each div
with the class step
represents a step in the decision flow. Use data-df-filter
attributes to conditionally display content.
<div class="convivial-decision-flow" id="example-flow">
<div class="step" id="step-1">
<h3>Welcome</h3>
<form class="df-form" action="#step-2">
<label for="age">Enter your age:</label>
<input type="number" name="age" required>
<input type="submit" value="Next">
</form>
</div>
<div class="step" id="step-2">
<h3>Choose your preference</h3>
<ul>
<li><a class="step__answer" href="#step-3" data-answer-path="option1">Option 1</a></li>
<li><a class="step__answer" href="#step-4" data-answer-path="option2">Option 2</a></li>
</ul>
</div>
<div class="step" id="step-3">
<h3>Option 1</h3>
<p>You selected Option 1.</p>
</div>
<div class="step" id="step-4">
<h3>Option 2</h3>
<p>You selected Option 2.</p>
</div>
<div class="step" id="summary">
<h3>Summary</h3>
<div data-df-show="history"></div>
<div data-df-show="submission"></div>
</div>
<div class="convivial-decision-flow__footer">
<button class="btn btn-secondary step__button step__button--back">Back</button>
<button class="btn btn-secondary step__button step__button--restart">Restart</button>
</div>
</div>
The library automatically initializes all decision flows on the page when the DOM content is loaded:
<script>
document.addEventListener('DOMContentLoaded', function () {
document.querySelectorAll('.convivial-decision-flow').forEach(function (flow) {
if (flow.id) {
new ConvivialDecisionFlow(localStorage, flow.id, flow); // Modify as needed to use 'sessionStorage' or 'localStorage'
} else {
console.warn('Convivial decision flow does not have an ID.');
}
});
});
</script>
You can define and store custom functions in the functions
property map and execute them securely. Here's how:
- Define a function in the JavaScript:
document.addEventListener('DOMContentLoaded', function () {
const dt = new ConvivialDecisionFlow(localStorage, 'example-flow', document.getElementById('example-flow'));
dt.functions.content['updateTextFunction'] = function (context, el) {
document.getElementById('custom-text').textContent = 'Updated text';
};
});
- Execute the function using the
data-df-show
attribute:
<button data-df-show="updateTextFunction">Update Text</button>
To compress the convivial_decision_flow.js
file, follow these steps:
- Install the
uglify-js
package:
npm install -g uglify-js
- Use the following command to compress the file:
uglifyjs convivial_decision_flow.js -o convivial_decision_flow.min.js
To create a release, update the version number in your package.json
file and use the following command:
npm version [patch|minor|major]
For more details, refer to the npm documentation.
Here's an example of how you can use the decision flow in a web page:
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Convivial Decision Flow Example</title>
<link rel="stylesheet" type="text/css" href="style.min.css">
<script type="text/javascript" src="convivial_decision_flow.min.js" defer></script>
</head>
<body>
<h1>Example Convivial Decision Flow</h1>
<div class="convivial-decision-flow" id="example-flow">
<!-- Steps here -->
</div>
</body>
</html>
By following the above guide, you can effectively utilize the Convivial Decision Flow library to create interactive decision-making experiences on your website.