Files
tinypin/client/reef-databind.js
2021-01-29 14:23:37 -06:00

306 lines
8.7 KiB
JavaScript

// this currently will bind all fields with 'data-bind' attributes
// to the 'store'.
app.addSetter('databind.onInput', (data, bindPath, value) => {
// console.log(`binding ${bindPath} to ${value}`);
put(data, bindPath, value);
/*!
* Add items to an object at a specific path
* (c) 2018 Chris Ferdinandi, MIT License, https://gomakethings.com
* @param {Object} obj The object
* @param {String|Array} path The path to assign the value to
* @param {*} val The value to assign
*/
function put(obj, path, val) {
/**
* If the path is a string, convert it to an array
* @param {String|Array} path The path
* @return {Array} The path array
*/
var stringToPath = function (path) {
// If the path isn't a string, return it
if (typeof path !== 'string') return path;
// Create new array
var output = [];
// Split to an array with dot notation
path.split('.').forEach(function (item, index) {
// Split to an array with bracket notation
item.split(/\[([^}]+)\]/g).forEach(function (key) {
// Push to the new array
if (key.length > 0) {
output.push(key);
}
});
});
return output;
};
// Convert the path to an array if not already
path = stringToPath(path);
// Cache the path length and current spot in the object
var length = path.length;
var current = obj;
// Loop through the path
path.forEach(function (key, index) {
// Check if the assigned key shoul be an array
var isArray = key.slice(-2) === '[]';
// If so, get the true key name by removing the trailing []
key = isArray ? key.slice(0, -2) : key;
// If the key should be an array and isn't, create an array
if (isArray && Object.prototype.toString.call(current[key]) !== '[object Array]') {
current[key] = [];
}
// If this is the last item in the loop, assign the value
if (index === length - 1) {
// If it's an array, push the value
// Otherwise, assign it
if (isArray) {
current[key].push(val);
} else {
current[key] = val;
}
}
// Otherwise, update the current place in the object
else {
// If the key doesn't exist, create it
if (!current[key]) {
current[key] = {};
}
// Update the current place in the object
current = current[key];
}
});
};
});
Reef.databind = function(reef){
let el = document.querySelector(reef.elem);
let store = reef.store;
if ( !store ){
console.error("Databind only works when using a store.");
return;
}
// bind all elements on the page that have a data-bind item
const bindData = debounce(() => {
let elems = el.querySelectorAll("[data-bind]");
for ( let i = 0; i < elems.length; ++i ){
let elem = elems[i];
let bindName = elem.getAttribute("data-bind");
if ( bindName ){
let val = get(store.data, bindName, "");
// dom values are strings, convert back so we can compare safely
if ( typeof(val) == 'array' ){
let strs = [];
for ( let i = 0; i < val.length; ++i ){
strs.push(val[i].toString());
}
val = strs;
} else {
val = val.toString();
}
// multiple selects need special handling
if ( elem.tagName == "SELECT" && elem.matches("[multiple]") ){
let options = elem.querySelectorAll("option");
for ( let i = 0; i < options.length; ++i ){
if ( val.indexOf(options[i].value) > -1 ){
options[i].selected = true;
}
}
} else if ( elem.type == "radio" || elem.type == "checkbox" ){
if ( elem.value == val.toString() ){
elem.checked = true;
} else {
elem.checked = false;
}
} else {
elem.value = val;
}
}
}
});
el.addEventListener('input', (evt) => {
let target = evt.target;
let bindPath = target.getAttribute("data-bind");
if (bindPath) {
let val = target.value;
if ( target.type == "checkbox" ){
if ( ! target.checked ){
val = null;
}
}
// multiple selects need special handling
if ( target.tagName == 'SELECT' && target.matches("[multiple]") ){
val = [];
let options = target.querySelectorAll("option");
for ( let i = 0; i < options.length; ++i ){
if ( options[i].selected ){
val.push(parseString(options[i].value));
}
}
}
// for checkbox, radio, and select fields - map numeric and boolean values to
// actual types instead of strings
else if ( target.type == 'checkbox' || target.type == 'radio' ){
val = parseString(val);
}
store.do('databind.onInput', bindPath, val);
//put(store.data, bindPath, val);
}
});
el.addEventListener('render', (evt) => {
bindData();
});
// convert booleans and numbers
function parseString(str){
if ( str == "true" ){
return true;
} else if ( str == "false" ){
return false;
} else {
return parseFloat(str) || str;
}
}
/**
* Debounce functions for better performance
* (c) 2018 Chris Ferdinandi, MIT License, https://gomakethings.com
* @param {Function} fn The function to debounce
*/
function debounce(fn) {
// Setup a timer
var timeout;
// Return a function to run debounced
return function () {
// Setup the arguments
var context = this;
var args = arguments;
// If there's a timer, cancel it
if (timeout) {
window.cancelAnimationFrame(timeout);
}
// Setup the new requestAnimationFrame()
timeout = window.requestAnimationFrame(function () {
fn.apply(context, args);
});
}
};
/*!
* Get an object value from a specific path
* (c) 2018 Chris Ferdinandi, MIT License, https://gomakethings.com
* @param {Object} obj The object
* @param {String|Array} path The path
* @param {*} def A default value to return [optional]
* @return {*} The value
*/
function get(obj, path, def) {
/**
* If the path is a string, convert it to an array
* @param {String|Array} path The path
* @return {Array} The path array
*/
var stringToPath = function (path) {
// If the path isn't a string, return it
if (typeof path !== 'string') return path;
// Create new array
var output = [];
// Split to an array with dot notation
path.split('.').forEach(function (item) {
// Split to an array with bracket notation
item.split(/\[([^}]+)\]/g).forEach(function (key) {
// Push to the new array
if (key.length > 0) {
output.push(key);
}
});
});
return output;
};
// Get the path as an array
path = stringToPath(path);
// Cache the current object
var current = obj;
// For each item in the path, dig into the object
for (var i = 0; i < path.length; i++) {
// If the item isn't found, return the default (or null)
if (!current[path[i]]) return def;
// Otherwise, update the current value
current = current[path[i]];
}
return current;
};
}