ihp-1.4.0: data/static/vendor/ihp-ssc.js
var debugMode = document.currentScript.dataset.debugMode || false;
window.callServerAction = function (action, payload) {
var component = findActiveComponent(event.target || document.activeElement);
var message = JSON.stringify({ action, ...payload });
component.connection.send(message)
}
if (window.Turbolinks) {
document.addEventListener('turbolinks:load', initalizeAllSSCComponents);
} else {
initalizeAllSSCComponents();
}
function initalizeAllSSCComponents() {
for (var component of document.querySelectorAll('.ihp-ssc')) {
if (debugMode) console.log('init', component)
initalizeSSC(component);
}
}
function initalizeSSC(component) {
var socketProtocol = location.protocol === 'https:' ? 'wss' : 'ws';
var socketHost = socketProtocol + "://" + window.location.hostname + ":" + document.location.port;
component.connection = new WebSocket(socketHost + component.dataset.path);
component.connection.onopen = function (event) {
if (debugMode) console.log('Connected');
};
component.connection.onclose = function (event) {
component.innerText = 'This component crashed :(';
};
component.connection.onmessage = function (event) {
var payload = JSON.parse(event.data);
evaluatePatch(component, payload);
if (debugMode) console.log(payload);
//var newElement = document.createElement('div');
//newElement.innerHTML = event.data;
//morphdom(component, newElement, { childrenOnly: true });
};
}
// Unescapes a HTML-encoded string.
function htmlDecode(input) {
var doc = new DOMParser().parseFromString(input, "text/html");
return doc.documentElement.textContent;
}
function evaluatePatch(component, nodeOperations) {
for (var nodeOperation of nodeOperations) {
nodeOperation.domNode = resolvePathToNode(component, nodeOperation.path);
}
for (var nodeOperation of nodeOperations) {
if (debugMode) console.log(nodeOperation);
evaluateNodeOperation(component, nodeOperation);
}
}
function evaluateNodeOperation(component, nodeOperation) {
switch (nodeOperation.type) {
case 'CreateNode': return createNode(component, nodeOperation);
case 'UpdateTextContent': return updateTextContent(component, nodeOperation);
case 'UpdateNode': return updateNode(component, nodeOperation);
case 'DeleteNode': return deleteNode(component, nodeOperation);
case 'ReplaceNode': return replaceNode(component, nodeOperation);
default:
if (debugMode) console.log('Unhandled type', nodeOperation);
}
}
function createNode(component, nodeOperation) {
var newElement = htmlStringToDomNode(nodeOperation.html);
var domNode = nodeOperation.domNode;
if (domNode.nodeType == Node.TEXT_NODE) {
domNode.replaceWith(newElement);
} else {
domNode.appendChild(newElement);
}
}
function updateTextContent(component, nodeOperation) {
var decodedContent = htmlDecode(nodeOperation.textContent)
if (nodeOperation.domNode.textContent === decodedContent) {
return;
}
nodeOperation.domNode.textContent = decodedContent;
}
function updateNode(component, nodeOperation) {
for (var attributeOperation of nodeOperation.attributeOperations) {
evaluateAttributeOperation(nodeOperation.domNode, attributeOperation);
}
}
function deleteNode(component, nodeOperation) {
nodeOperation.domNode.remove();
}
function replaceNode(component, nodeOperation) {
nodeOperation.domNode.outerHTML = nodeOperation.newNodeHtml;
if (debugMode) console.log('A#######', nodeOperation.domNode);
if (nodeOperation.domNode.type == Node.ELEMENT_NODE) {
for (var component of nodeOperation.domNode.querySelectorAll('.ihp-ssc')) {
initalizeSSC(component);
}
}
}
const inputTagnames = ["INPUT", "TEXTAREA"];
function evaluateAttributeOperation(domNode, op) {
// HtmlDiff passes attribute values as raw values — that is, if the attribute value is a single quote mark it'll be
// represented in HTML as """ and therefore we'll get """. We have to unescape those values before using
// 'setAttribute' or 'x.value = y'.
if (op.type === "UpdateAttribute" && op.attributeName == 'value' && inputTagnames.includes(domNode.tagName)) {
domNode.value = htmlDecode(op.attributeValue);
} else if (op.type === 'UpdateAttribute' || op.type === 'AddAttribute') {
domNode.setAttribute(op.attributeName, htmlDecode(op.attributeValue));
} else if (op.type === 'DeleteAttribute') {
domNode.removeAttribute(op.attributeName);
} else {
throw new Error('Invalid op.type');
}
}
function resolvePathToNode(component, path) {
var current = component;
for (var i = path.length; i--; i > 0) {
var currentPathElement = path[i];
if (current.childNodes[currentPathElement])
current = current.childNodes[currentPathElement];
}
return current;
}
/* Returns the nearest element with a class `.ihp-ssc` */
var findActiveComponent = function (element) {
if (element.classList.contains('ihp-ssc')) {
return element;
}
if (!element) {
throw new Error('Could not find nearest .ihp-ssc element');
}
return findActiveComponent(element.parentNode);
};
function htmlStringToDomNode(string) {
var template = document.createElement('template');
template.innerHTML = string;
return template.content;
}