Fix swapping of table elements (jQuery is smart)

Fix including input values (checkboxes, etc.) (jQuery is smart)
Add bulk update demo (needs docs)
This commit is contained in:
carson 2020-05-10 05:50:54 -07:00
parent 2c75621526
commit 2cf6023eb7
10 changed files with 321 additions and 54 deletions

91
dist/kutty.js vendored
View File

@ -66,12 +66,46 @@ var kutty = kutty || (function () {
while (elt = elt && parentElt(elt)); while (elt = elt && parentElt(elt));
} }
function makeFragment(resp) { function getStartTag(str) {
// var range = getDocument().createRange(); var tagMatcher = /<([a-z][^\/\0>\x20\t\r\n\f]*)/i
// return range.createContextualFragment(resp); var match = tagMatcher.exec( str );
if (match) {
return match[1].toLowerCase();
} else {
return "";
}
}
function parseHTML(resp, depth) {
var parser = new DOMParser(); var parser = new DOMParser();
var responseDoc = parser.parseFromString(resp, "text/html"); var responseDoc = parser.parseFromString(resp, "text/html");
return responseDoc.body; var responseNode = responseDoc.body;
while (depth > 0) {
depth--;
responseNode = responseNode.firstChild;
}
return responseNode;
}
function makeFragment(resp) {
var startTag = getStartTag(resp);
switch (startTag) {
case "thead":
case "tbody":
case "tfoot":
case "colgroup":
case "caption":
return parseHTML("<table>" + resp + "</table>", 1);
case "col":
return parseHTML("<table><colgroup>" + resp + "</colgroup></table>", 2);
case "tr":
return parseHTML("<table><tbody>" + resp + "</tbody></table>", 2);
case "td":
case "th":
return parseHTML("<table><tbody><tr>" + resp + "</tr></tbody></table>", 3);
default:
return parseHTML(resp, 0);
}
} }
function isType(o, type) { function isType(o, type) {
@ -122,6 +156,11 @@ var kutty = kutty || (function () {
return trigger.split(/\s+/); return trigger.split(/\s+/);
} }
function addRule(rule) {
var sheet = getDocument().styleSheets[0];
sheet.insertRule(rule, sheet.cssRules.length);
}
//==================================================================== //====================================================================
// Node processing // Node processing
//==================================================================== //====================================================================
@ -754,24 +793,39 @@ var kutty = kutty || (function () {
return false; return false;
} }
function shouldInclude(elt) {
if(elt.name === "" || elt.name == null || elt.disabled) {
return false;
}
// ignore "submitter" types (see jQuery src/serialize.js)
if (elt.type === "button" || elt.type === "submit" || elt.tagName === "image" || elt.tagName === "reset" || elt.tagName === "file" ) {
return false;
}
if (elt.type === "checkbox" || elt.type === "radio" ) {
return elt.checked;
}
}
function processInputValue(processed, values, elt) { function processInputValue(processed, values, elt) {
if (elt == null || haveSeenNode(processed, elt)) { if (elt == null || haveSeenNode(processed, elt)) {
return; return;
} else { } else {
processed.push(elt); processed.push(elt);
} }
var name = getRawAttribute(elt,"name"); if (shouldInclude(elt)) {
var value = elt.value; var name = getRawAttribute(elt,"name");
if (name && value) { var value = elt.value;
var current = values[name]; if (name && value) {
if(current) { var current = values[name];
if (Array.isArray(current)) { if(current) {
current.push(value); if (Array.isArray(current)) {
current.push(value);
} else {
values[name] = [current, value];
}
} else { } else {
values[name] = [current, value]; values[name] = value;
} }
} else {
values[name] = value;
} }
} }
if (matches(elt, 'form')) { if (matches(elt, 'form')) {
@ -789,7 +843,7 @@ var kutty = kutty || (function () {
processInputValue(processed, values, elt); processInputValue(processed, values, elt);
// include any explicit includes // include any explicit includes
var includes = getAttributeValue(elt, "kt-include"); var includes = getClosestAttributeValue(elt, "kt-include");
if (includes) { if (includes) {
var nodes = getDocument().querySelectorAll(includes); var nodes = getDocument().querySelectorAll(includes);
forEach(nodes, function(node) { forEach(nodes, function(node) {
@ -1056,10 +1110,9 @@ var kutty = kutty || (function () {
} }
// insert kutty-indicator css rules // insert kutty-indicator css rules
var sheet = getDocument().styleSheets[0]; addRule(".kutty-indicator{opacity:0;transition: opacity 200ms ease-in;}");
sheet.insertRule(".kutty-indicator{opacity:0;transition: opacity 500ms ease-in;}\n" + addRule(".kutty-request .kutty-indicator{opacity:1}");
".kutty-request .kutty-indicator{opacity:1}\n" + addRule(".kutty-request.kutty-indicator{opacity:1}");
".kutty-request.kutty-indicator{opacity:1}\n", sheet.cssRules.length);
// initialize the document // initialize the document
ready(function () { ready(function () {

2
dist/kutty.min.js vendored

File diff suppressed because one or more lines are too long

BIN
dist/kutty.min.js.gz vendored

Binary file not shown.

View File

@ -66,12 +66,46 @@ var kutty = kutty || (function () {
while (elt = elt && parentElt(elt)); while (elt = elt && parentElt(elt));
} }
function makeFragment(resp) { function getStartTag(str) {
// var range = getDocument().createRange(); var tagMatcher = /<([a-z][^\/\0>\x20\t\r\n\f]*)/i
// return range.createContextualFragment(resp); var match = tagMatcher.exec( str );
if (match) {
return match[1].toLowerCase();
} else {
return "";
}
}
function parseHTML(resp, depth) {
var parser = new DOMParser(); var parser = new DOMParser();
var responseDoc = parser.parseFromString(resp, "text/html"); var responseDoc = parser.parseFromString(resp, "text/html");
return responseDoc.body; var responseNode = responseDoc.body;
while (depth > 0) {
depth--;
responseNode = responseNode.firstChild;
}
return responseNode;
}
function makeFragment(resp) {
var startTag = getStartTag(resp);
switch (startTag) {
case "thead":
case "tbody":
case "tfoot":
case "colgroup":
case "caption":
return parseHTML("<table>" + resp + "</table>", 1);
case "col":
return parseHTML("<table><colgroup>" + resp + "</colgroup></table>", 2);
case "tr":
return parseHTML("<table><tbody>" + resp + "</tbody></table>", 2);
case "td":
case "th":
return parseHTML("<table><tbody><tr>" + resp + "</tr></tbody></table>", 3);
default:
return parseHTML(resp, 0);
}
} }
function isType(o, type) { function isType(o, type) {
@ -759,24 +793,39 @@ var kutty = kutty || (function () {
return false; return false;
} }
function shouldInclude(elt) {
if(elt.name === "" || elt.name == null || elt.disabled) {
return false;
}
// ignore "submitter" types (see jQuery src/serialize.js)
if (elt.type === "button" || elt.type === "submit" || elt.tagName === "image" || elt.tagName === "reset" || elt.tagName === "file" ) {
return false;
}
if (elt.type === "checkbox" || elt.type === "radio" ) {
return elt.checked;
}
}
function processInputValue(processed, values, elt) { function processInputValue(processed, values, elt) {
if (elt == null || haveSeenNode(processed, elt)) { if (elt == null || haveSeenNode(processed, elt)) {
return; return;
} else { } else {
processed.push(elt); processed.push(elt);
} }
var name = getRawAttribute(elt,"name"); if (shouldInclude(elt)) {
var value = elt.value; var name = getRawAttribute(elt,"name");
if (name && value) { var value = elt.value;
var current = values[name]; if (name && value) {
if(current) { var current = values[name];
if (Array.isArray(current)) { if(current) {
current.push(value); if (Array.isArray(current)) {
current.push(value);
} else {
values[name] = [current, value];
}
} else { } else {
values[name] = [current, value]; values[name] = value;
} }
} else {
values[name] = value;
} }
} }
if (matches(elt, 'form')) { if (matches(elt, 'form')) {
@ -794,7 +843,7 @@ var kutty = kutty || (function () {
processInputValue(processed, values, elt); processInputValue(processed, values, elt);
// include any explicit includes // include any explicit includes
var includes = getAttributeValue(elt, "kt-include"); var includes = getClosestAttributeValue(elt, "kt-include");
if (includes) { if (includes) {
var nodes = getDocument().querySelectorAll(includes); var nodes = getDocument().querySelectorAll(includes);
forEach(nodes, function(node) { forEach(nodes, function(node) {

View File

@ -6,13 +6,14 @@
right: 0; right: 0;
left: 0; left: 0;
height: 50px; height: 50px;
width: 100vw;
background-color: whitesmoke; background-color: whitesmoke;
border-top: 2px solid gray; border-top: 2px solid gray;
overflow: scroll; overflow: scroll;
margin: 0px; margin: 0px;
} }
#demo-server-info.show { #demo-server-info.show {
max-height: 35vh; max-height: 45vh;
height: 500px; height: 500px;
} }
#demo-activity { #demo-activity {

View File

@ -12,3 +12,4 @@ You can copy and paste them and then adjust them for your needs.
| Pattern | Description | | Pattern | Description |
|-----------|-------------| |-----------|-------------|
| [Click To Edit](/examples/click-to-edit) | Demonstrates inline editing of a data object | [Click To Edit](/examples/click-to-edit) | Demonstrates inline editing of a data object
| [Bulk Update](/examples/bulk-update) | Demonstrates bulk updating of multiple rows of data

115
www/examples/bulk-update.md Normal file
View File

@ -0,0 +1,115 @@
---
layout: demo_layout.njk
---
## Bulk Update
The click to edit pattern provides a way to offer inline editing of all or part of a record without a page refresh.
* The form issues a `PUT` back to `/contacts/1`, following the usual REST-ful pattern.
<style scoped="">
.kutty-settling tr.deactivate td {
background: lightcoral;
}
.kutty-settling tr.activate td {
background: darkseagreen;
}
tr td {
transition: all 1.2s;
}
</style>`
{% include demo_ui.html.liquid %}
<script>
//=========================================================================
// Fake Server Side Code
//=========================================================================
// data
var dataStore = function(){
var data = [
{ name: "Joe Smith", email: "joe@smith.org", status: "Active" },
{ name: "Angie MacDowell", email: "angie@macdowell.org", status: "Active" },
{ name: "Fuqua Tarkenton", email: "fuqua@tarkenton.org", status: "Active" },
{ name: "Kim Yee", email: "kim@yee.org", status: "Inactive" }
];
return {
findContactById : function(id) {
return data[id];
},
allContacts : function() {
return data;
}
}
}()
function getIds(params) {
if(params['ids']) {
if(Array.isArray(params['ids'])) {
return params['ids'].map(x => parseInt(x))
} else {
return [parseInt(params['ids'])];
}
} else {
return [];
}
}
// routes
init("/demo", function(request){
return displayUI(dataStore.allContacts());
});
onPut("/activate", function(request, params){
var ids = getIds(params);
for (var i = 0; i < ids.length; i++) {
dataStore.findContactById(ids[i])['status'] = 'Active';
}
return displayTable(ids, dataStore.allContacts(), 'activate');
});
onPut("/deactivate", function (req, params) {
var ids = getIds(params);
for (var i = 0; i < ids.length; i++) {
dataStore.findContactById(ids[i])['status'] = 'Inactive';
}
return displayTable(ids, dataStore.allContacts(), 'deactivate');
});
// templates
function displayUI(contacts) {
return `<div kt-include="#checked-contacts" kt-target="#tbody">
<a class="btn" kt-put="/activate">Activate</a>
<a class="btn" kt-put="/deactivate">Deactivate</a>
</div>
<form id="checked-contacts">
<table>
<thead>
<tr>
<th></th>
<th>Name</th>
<th>Email</th>
<th>Status</th>
</tr>
</thead>
<tbody id="tbody">
${displayTable([], contacts, "")}
</tbody>
</table>
</form>`
}
function displayTable(ids, contacts, action) {
var txt = "";
for (var i = 0; i < contacts.length; i++) {
var c = contacts[i];
txt += `\n<tr class="${ids.includes(i) ? action : ""}">
<td><input type='checkbox' name='ids' value='${i}'></td><td>${c.name}</td><td>${c.email}</td><td>${c.status}</td>
</tr>`
}
return txt;
}
</script>

View File

@ -2,7 +2,7 @@
layout: demo_layout.njk layout: demo_layout.njk
--- ---
## Kutty Pattern: Click To Edit ## Click To Edit
The click to edit pattern provides a way to offer inline editing of all or part of a record without a page refresh. The click to edit pattern provides a way to offer inline editing of all or part of a record without a page refresh.

View File

@ -26,7 +26,7 @@ function parseParams(str) {
var k = decode(e[1]); var k = decode(e[1]);
var v = decode(e[2]); var v = decode(e[2]);
if (params[k] !== undefined) { if (params[k] !== undefined) {
if (!$.isArray(params[k])) { if (!Array.isArray(params[k])) {
params[k] = [params[k]]; params[k] = [params[k]];
} }
params[k].push(v); params[k].push(v);
@ -103,7 +103,6 @@ function showTimelineEntry(id) {
var children = document.getElementById("demo-timeline").children; var children = document.getElementById("demo-timeline").children;
for (var i = 0; i < children.length; i++) { for (var i = 0; i < children.length; i++) {
var child = children[i]; var child = children[i];
console.log(child.id);
if (child.id == id + "-link" ) { if (child.id == id + "-link" ) {
child.classList.add('active'); child.classList.add('active');
} else { } else {

View File

@ -66,12 +66,46 @@ var kutty = kutty || (function () {
while (elt = elt && parentElt(elt)); while (elt = elt && parentElt(elt));
} }
function makeFragment(resp) { function getStartTag(str) {
// var range = getDocument().createRange(); var tagMatcher = /<([a-z][^\/\0>\x20\t\r\n\f]*)/i
// return range.createContextualFragment(resp); var match = tagMatcher.exec( str );
if (match) {
return match[1].toLowerCase();
} else {
return "";
}
}
function parseHTML(resp, depth) {
var parser = new DOMParser(); var parser = new DOMParser();
var responseDoc = parser.parseFromString(resp, "text/html"); var responseDoc = parser.parseFromString(resp, "text/html");
return responseDoc.body; var responseNode = responseDoc.body;
while (depth > 0) {
depth--;
responseNode = responseNode.firstChild;
}
return responseNode;
}
function makeFragment(resp) {
var startTag = getStartTag(resp);
switch (startTag) {
case "thead":
case "tbody":
case "tfoot":
case "colgroup":
case "caption":
return parseHTML("<table>" + resp + "</table>", 1);
case "col":
return parseHTML("<table><colgroup>" + resp + "</colgroup></table>", 2);
case "tr":
return parseHTML("<table><tbody>" + resp + "</tbody></table>", 2);
case "td":
case "th":
return parseHTML("<table><tbody><tr>" + resp + "</tr></tbody></table>", 3);
default:
return parseHTML(resp, 0);
}
} }
function isType(o, type) { function isType(o, type) {
@ -759,24 +793,39 @@ var kutty = kutty || (function () {
return false; return false;
} }
function shouldInclude(elt) {
if(elt.name === "" || elt.name == null || elt.disabled) {
return false;
}
// ignore "submitter" types (see jQuery src/serialize.js)
if (elt.type === "button" || elt.type === "submit" || elt.tagName === "image" || elt.tagName === "reset" || elt.tagName === "file" ) {
return false;
}
if (elt.type === "checkbox" || elt.type === "radio" ) {
return elt.checked;
}
}
function processInputValue(processed, values, elt) { function processInputValue(processed, values, elt) {
if (elt == null || haveSeenNode(processed, elt)) { if (elt == null || haveSeenNode(processed, elt)) {
return; return;
} else { } else {
processed.push(elt); processed.push(elt);
} }
var name = getRawAttribute(elt,"name"); if (shouldInclude(elt)) {
var value = elt.value; var name = getRawAttribute(elt,"name");
if (name && value) { var value = elt.value;
var current = values[name]; if (name && value) {
if(current) { var current = values[name];
if (Array.isArray(current)) { if(current) {
current.push(value); if (Array.isArray(current)) {
current.push(value);
} else {
values[name] = [current, value];
}
} else { } else {
values[name] = [current, value]; values[name] = value;
} }
} else {
values[name] = value;
} }
} }
if (matches(elt, 'form')) { if (matches(elt, 'form')) {
@ -794,7 +843,7 @@ var kutty = kutty || (function () {
processInputValue(processed, values, elt); processInputValue(processed, values, elt);
// include any explicit includes // include any explicit includes
var includes = getAttributeValue(elt, "kt-include"); var includes = getClosestAttributeValue(elt, "kt-include");
if (includes) { if (includes) {
var nodes = getDocument().querySelectorAll(includes); var nodes = getDocument().querySelectorAll(includes);
forEach(nodes, function(node) { forEach(nodes, function(node) {