Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 60 additions & 29 deletions code-input.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,25 +130,35 @@ var codeInput = {


codeInput.usedTemplates[templateName] = template;
// Add waiting code-input elements wanting this template from queue
if (templateName in codeInput.templateNotYetRegisteredQueue) {
for (let i in codeInput.templateNotYetRegisteredQueue[templateName]) {
const elem = codeInput.templateNotYetRegisteredQueue[templateName][i];
const activateQueuedElements = function (queueName) {
if (!(queueName in codeInput.templateNotYetRegisteredQueue)) return;

for (let i in codeInput.templateNotYetRegisteredQueue[queueName]) {
const elem = codeInput.templateNotYetRegisteredQueue[queueName][i];
const requestedTemplateName = elem.getAttribute("template") || codeInput.defaultTemplate;
if (requestedTemplateName != templateName) continue;

elem.templateObject = template;
elem.setup();
if (elem.textareaElement == null) {
elem.setup();
} else {
elem.textareaElement.removeAttribute("data-code-input-fallback");
elem.classList.add("code-input_loaded");
if (template.preElementStyled) elem.classList.add("code-input_pre-element-styled");
else elem.classList.remove("code-input_pre-element-styled");
elem.scheduleHighlight();
}
}
}
delete codeInput.templateNotYetRegisteredQueue[queueName];
};

// Add waiting code-input elements wanting this template from queue
activateQueuedElements(templateName);

if (codeInput.defaultTemplate == undefined) {
codeInput.defaultTemplate = templateName;
// Add elements with default template from queue
if (undefined in codeInput.templateNotYetRegisteredQueue) {
for (let i in codeInput.templateNotYetRegisteredQueue[undefined]) {
const elem = codeInput.templateNotYetRegisteredQueue[undefined][i];
elem.templateObject = template;
elem.setup();
}
}
activateQueuedElements(undefined);
}
},

Expand Down Expand Up @@ -478,6 +488,8 @@ var codeInput = {
* @param {Array} args - the arguments to pass into the event callback in the template after the code-input element. Normally left empty
*/
pluginEvt(eventName, args) {
if (this.templateObject == undefined) return;

for (let i in this.templateObject.plugins) {
let plugin = this.templateObject.plugins[i];
if (eventName in plugin) {
Expand Down Expand Up @@ -531,6 +543,11 @@ var codeInput = {

// Update code
resultElement.innerHTML = this.escapeHtml(value);
if (this.templateObject == undefined) {
this.syncSize();
return;
}

this.pluginEvt("beforeHighlight");

// Syntax Highlight
Expand All @@ -543,7 +560,7 @@ var codeInput = {
}

getStyledHighlightingElement() {
if(this.templateObject.preElementStyled) {
if(this.templateObject != undefined && this.templateObject.preElementStyled) {
return this.preElement;
} else {
return this.codeElement;
Expand Down Expand Up @@ -660,24 +677,22 @@ var codeInput = {

/**
* Get the template object this code-input element is using.
* @returns {Object} - Template object
* @param {string} [templateName] - Optional template name to use instead of the current attribute/default.
* @returns {Object|undefined} - Template object, or undefined while waiting for it to be registered.
*/
getTemplate() {
let templateName;
if (this.getAttribute("template") == undefined) {
// Default
templateName = codeInput.defaultTemplate;
} else {
templateName = this.getAttribute("template");
}
getTemplate(templateName) {
if (templateName == undefined) templateName = this.getAttribute("template") || codeInput.defaultTemplate;

if (templateName in codeInput.usedTemplates) {
return codeInput.usedTemplates[templateName];
} else {
// Doesn't exist - add to queue
if (!(templateName in codeInput.templateNotYetRegisteredQueue)) {
codeInput.templateNotYetRegisteredQueue[templateName] = [];
}
codeInput.templateNotYetRegisteredQueue[templateName].push(this);
if (!codeInput.templateNotYetRegisteredQueue[templateName].includes(this)) {
codeInput.templateNotYetRegisteredQueue[templateName].push(this);
}
return undefined;
}
}
Expand Down Expand Up @@ -966,13 +981,13 @@ var codeInput = {
// Children not yet present - wait until they are
window.addEventListener("DOMContentLoaded", () => {
const fallbackTextarea = this.querySelector("textarea[data-code-input-fallback]");
if(fallbackTextarea) {
if(fallbackTextarea && this.textareaElement == null) {
this.setupTextareaSyncEvents(fallbackTextarea);
}
})
} else {
const fallbackTextarea = this.querySelector("textarea[data-code-input-fallback]");
if(fallbackTextarea) {
if(fallbackTextarea && this.textareaElement == null) {
this.setupTextareaSyncEvents(fallbackTextarea);
}
}
Expand Down Expand Up @@ -1022,9 +1037,25 @@ var codeInput = {
this.value = newValue;
break;
case "template":
this.templateObject = codeInput.usedTemplates[newValue || codeInput.defaultTemplate];
if (this.templateObject.preElementStyled) this.classList.add("code-input_pre-element-styled");
else this.classList.remove("code-input_pre-element-styled");
const previousTemplateName = oldValue || codeInput.defaultTemplate;
if (previousTemplateName in codeInput.templateNotYetRegisteredQueue) {
codeInput.templateNotYetRegisteredQueue[previousTemplateName] = codeInput.templateNotYetRegisteredQueue[previousTemplateName].filter((elem) => elem != this);
if (codeInput.templateNotYetRegisteredQueue[previousTemplateName].length == 0) {
delete codeInput.templateNotYetRegisteredQueue[previousTemplateName];
}
}

this.templateObject = this.getTemplate(newValue || codeInput.defaultTemplate);
if (this.templateObject == undefined) {
this.classList.remove("code-input_pre-element-styled");
this.classList.remove("code-input_loaded");
this.textareaElement.setAttribute("data-code-input-fallback", "");
} else {
this.textareaElement.removeAttribute("data-code-input-fallback");
this.classList.add("code-input_loaded");
if (this.templateObject.preElementStyled) this.classList.add("code-input_pre-element-styled");
else this.classList.remove("code-input_pre-element-styled");
}
// Syntax Highlight
this.scheduleHighlight();

Expand Down
45 changes: 45 additions & 0 deletions tests/tester.js
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,51 @@ console.log("I've got another line!", 2 < 3, "should be true.");
await waitAsync(50);
assertEqual("Core", "Programmatically-created element rendered value", programmaticCodeInput.preElement.textContent, "Hello, World!\n");

const switchableCodeInput = document.createElement("code-input");
switchableCodeInput.setAttribute("template", "code-editor");
switchableCodeInput.textContent = "before";
document.body.append(switchableCodeInput);
await waitAsync(50);

const staleTemplateName = "test-unregistered-stale";
const recoveredTemplateName = "test-unregistered-recovered";
switchableCodeInput.setAttribute("template", staleTemplateName);
switchableCodeInput.value = "after";
await waitAsync(50);
assertEqual("Core", "Unregistered template has no active template object", switchableCodeInput.templateObject, undefined);
assertEqual("Core", "Unregistered template preserves editable plain-text rendering", switchableCodeInput.codeElement.textContent, "after\n");
assertEqual("Core", "Unregistered template returns to fallback display", switchableCodeInput.classList.contains("code-input_loaded"), false);
assertEqual("Core", "Unregistered template keeps its textarea editable", switchableCodeInput.textareaElement.hasAttribute("data-code-input-fallback"), true);

switchableCodeInput.setAttribute("template", recoveredTemplateName);
await waitAsync(50);
assertEqual("Core", "Stale unregistered template is removed from the waiting queue", staleTemplateName in codeInput.templateNotYetRegisteredQueue, false);
const staleTemplate = new codeInput.Template((codeElement) => {
codeElement.classList.add("test-stale-template-highlighted");
});
codeInput.registerTemplate(staleTemplateName, staleTemplate);
await waitAsync(50);
assertEqual("Core", "Stale queued template does not replace current missing template", switchableCodeInput.templateObject, undefined);
assertEqual("Core", "Stale queued template does not highlight the element", switchableCodeInput.codeElement.classList.contains("test-stale-template-highlighted"), false);

const recoveredTemplate = new codeInput.Template((codeElement) => {
codeElement.classList.add("test-recovered-template-highlighted");
}, true, true, false, [new codeInput.plugins.Indent()]);
codeInput.registerTemplate(recoveredTemplateName, recoveredTemplate);
await waitAsync(50);
assertEqual("Core", "Registered missing template becomes active", switchableCodeInput.templateObject, recoveredTemplate);
assertEqual("Core", "Registered missing template resumes highlighting", switchableCodeInput.codeElement.classList.contains("test-recovered-template-highlighted"), true);
assertEqual("Core", "Registered missing template preserves the edited value", switchableCodeInput.value, "after");
assertEqual("Core", "Registered missing template is removed from the waiting queue", recoveredTemplateName in codeInput.templateNotYetRegisteredQueue, false);
assertEqual("Core", "Registered missing template restores the loaded display", switchableCodeInput.classList.contains("code-input_loaded"), true);
assertEqual("Core", "Registered missing template removes the fallback marker", switchableCodeInput.textareaElement.hasAttribute("data-code-input-fallback"), false);

switchableCodeInput.setAttribute("template", "code-editor");
await waitAsync(50);
assertEqual("Core", "Existing registered template can be selected after recovery", switchableCodeInput.templateObject, codeInput.usedTemplates["code-editor"]);
assertEqual("Core", "Existing registered template keeps the loaded display", switchableCodeInput.classList.contains("code-input_loaded"), true);
switchableCodeInput.remove();

// Event Listener Tests

let numTimesInputCalled = {"listener": 0, "idl": 0, "content": 0};
Expand Down