Skip to content

Commit d5bdbd5

Browse files
committed
Complete textarea JavaScript interface for code-input element
1 parent 3907b2c commit d5bdbd5

File tree

2 files changed

+140
-82
lines changed

2 files changed

+140
-82
lines changed

code-input.js

Lines changed: 139 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@
1515

1616
var codeInput = {
1717
/**
18-
* A list of attributes that will trigger the
18+
* A list of attributes that will trigger functionality in the
1919
* `codeInput.CodeInput.attributeChangedCallback`
2020
* when modified in a code-input element. This
2121
* does not include events, which are handled in
2222
* `codeInput.CodeInput.addEventListener` and
2323
* `codeInput.CodeInput.removeEventListener`.
24+
*
25+
* This does not include those listed in `codeInput.textareaSyncAttributes` that only synchronise with the textarea element.
2426
*/
2527
observedAttributes: [
2628
"value",
@@ -628,6 +630,7 @@ var codeInput = {
628630
let textareaAttributeNames = fallbackTextarea.getAttributeNames();
629631
for(let i = 0; i < textareaAttributeNames.length; i++) {
630632
const attr = textareaAttributeNames[i];
633+
631634
if(!this.hasAttribute(attr)) {
632635
this.setAttribute(attr, fallbackTextarea.getAttribute(attr));
633636
}
@@ -793,6 +796,11 @@ var codeInput = {
793796
return this.attributeChangedCallback(mutation.attributeName, mutation.oldValue, super.getAttribute(mutation.attributeName));
794797
}
795798
}
799+
for(let i = 0; i < codeInput.textareaSyncAttributes.length; i++) {
800+
if (mutation.attributeName == codeInput.textareaSyncAttributes[i]) {
801+
return this.attributeChangedCallback(mutation.attributeName, mutation.oldValue, super.getAttribute(mutation.attributeName));
802+
}
803+
}
796804
if (mutation.attributeName.substring(0, 5) == "aria-") {
797805
return this.attributeChangedCallback(mutation.attributeName, mutation.oldValue, super.getAttribute(mutation.attributeName));
798806
}
@@ -963,117 +971,167 @@ var codeInput = {
963971
}
964972
}
965973

974+
//-------------------------------------------
975+
//----------- Textarea interface ------------
976+
//-------------------------------------------
977+
// See https://developer.mozilla.org/en-US/docs/Web/API/HTMLTextAreaElement
978+
// Attributes defined at codeInput.textareaSyncAttributes
979+
966980
/**
967-
* Get the text contents of the code-input element.
981+
* Get the JavaScript property from the internal textarea
982+
* element, given its name and a defaultValue to return
983+
* when no textarea is present (undefined to make it throw
984+
* an error instead).
985+
*
986+
* For internal use - treat the code-input element as a
987+
* textarea for the standard properties (e.g. document.
988+
* querySelector("code-input").defaultValue).
968989
*/
969-
get value() {
990+
getTextareaProperty(name, defaultValue=undefined) {
970991
if(this.textareaElement) {
971-
// Get from editable textarea element
972-
return this.textareaElement.value;
992+
return this.textareaElement[name];
973993
} else {
974994
// Unregistered
975995
const fallbackTextarea = this.querySelector("textarea[data-code-input-fallback]");
976996
if(fallbackTextarea) {
977-
return fallbackTextarea.value;
997+
return fallbackTextarea[name];
978998
} else {
979-
return this.innerHTML;
999+
if(defaultValue === undefined) {
1000+
throw new Error("Cannot get "+name+" of an unregistered code-input element without a data-code-input-fallback textarea.");
1001+
}
1002+
return defaultValue;
9801003
}
9811004
}
9821005
}
9831006
/**
984-
* Set the text contents of the code-input element.
985-
* @param {string} val - New text contents
1007+
* Set the JavaScript property of the internal textarea
1008+
* element, given its name and value.
1009+
*
1010+
* If there is no registered or fallback textarea and errorIfCannot is
1011+
* false, return false (otherwise true); If there is no registered or
1012+
* fallback textarea and errorIfCannot is true, throw an error.
1013+
*
1014+
* For internal use - treat the code-input element as a
1015+
* textarea for the standard properties (e.g. document.
1016+
* querySelector("code-input").defaultValue).
9861017
*/
987-
set value(val) {
988-
if (val === null || val === undefined) {
989-
val = "";
990-
}
1018+
setTextareaProperty(name, value, errorIfCannot=true) {
9911019
if(this.textareaElement) {
992-
// Save in editable textarea element
993-
this.textareaElement.value = val;
994-
// Trigger highlight
995-
this.scheduleHighlight();
1020+
this.textareaElement[name] = value;
9961021
} else {
9971022
// Unregistered
9981023
const fallbackTextarea = this.querySelector("textarea[data-code-input-fallback]");
9991024
if(fallbackTextarea) {
1000-
fallbackTextarea.value = val;
1025+
fallbackTextarea[name] = value;
10011026
} else {
1002-
this.innerHTML = val;
1027+
if(!errorIfCannot) return false;
1028+
throw new Error("Cannot set "+name+" of an unregistered code-input element without a data-code-input-fallback textarea.");
10031029
}
10041030
}
1031+
return true;
10051032
}
10061033

1007-
// Synchronise blur and focus
1008-
focus(options={}) {
1009-
return this.textareaElement.focus(options);
1010-
}
1011-
blur(options={}) {
1012-
return this.textareaElement.blur(options);
1034+
get autocomplete() { return this.getAttribute("autocomplete"); }
1035+
set autocomplete(val) { return this.setAttribute("autocomplete", val); }
1036+
get cols() { return this.getTextareaProperty("cols", Number(this.getAttribute("cols"))); }
1037+
set cols(val) { this.setAttribute("cols", val); }
1038+
get defaultValue() { return this.initialValue; }
1039+
set defaultValue(val) { this.initialValue = val; }
1040+
get textContent() { return this.initialValue; }
1041+
set textContent(val) { this.initialValue = val; }
1042+
get dirName() { return this.getAttribute("dirName") || ""; }
1043+
set dirName(val) { this.setAttribute("dirname", val); }
1044+
get disabled() { return this.hasAttribute("disabled"); }
1045+
set disabled(val) {
1046+
if(val) {
1047+
this.setAttribute("disabled", true);
1048+
} else {
1049+
this.removeAttribute("disabled");
1050+
}
10131051
}
1014-
1015-
/**
1016-
* Get the placeholder of the code-input element that appears
1017-
* when no code has been entered.
1018-
*/
1019-
get placeholder() {
1020-
return this.getAttribute("placeholder");
1052+
get form() { return this.getTextareaProperty("form"); }
1053+
get labels() { return this.getTextareaProperty("labels"); }
1054+
get maxLength() {
1055+
const result = Number(this.getAttribute("maxlength"));
1056+
if(isNaN(result)) {
1057+
return -1;
1058+
}
1059+
return result;
10211060
}
1022-
/**
1023-
* Set the placeholder of the code-input element that appears
1024-
* when no code has been entered.
1025-
* @param {string} val - New placeholder
1026-
*/
1027-
set placeholder(val) {
1028-
return this.setAttribute("placeholder", val);
1061+
set maxLength(val) {
1062+
if(val == -1) {
1063+
this.removeAttribute("maxlength");
1064+
} else {
1065+
this.setAttribute("maxlength", val);
1066+
}
10291067
}
1030-
1031-
/**
1032-
* Returns a ValidityState object that represents the validity states of an element.
1033-
*
1034-
* See `HTMLTextAreaElement.validity`
1035-
*/
1036-
get validity() {
1037-
return this.textareaElement.validity;
1068+
get minLength() {
1069+
const result = Number(this.getAttribute("minlength"));
1070+
if(isNaN(result)) {
1071+
return -1;
1072+
}
1073+
return result;
10381074
}
1039-
1040-
/**
1041-
* Returns the error message that would be displayed if the user submits the form, or an empty string if no error message.
1042-
* It also triggers the standard error message, such as "this is a required field". The result is that the user sees validation
1043-
* messages without actually submitting.
1044-
*
1045-
* See `HTMLTextAreaElement.validationMessage`
1046-
*/
1047-
get validationMessage() {
1048-
return this.textareaElement.validationMessage;
1075+
set minLength(val) {
1076+
if(val == -1) {
1077+
this.removeAttribute("minlength");
1078+
} else {
1079+
this.setAttribute("minlength", val);
1080+
}
10491081
}
1050-
1051-
/**
1052-
* Sets a custom error message that is displayed when a form is submitted.
1053-
*
1054-
* See `HTMLTextAreaElement.setCustomValidity`
1055-
* @param error Sets a custom error message that is displayed when a form is submitted.
1056-
*/
1057-
setCustomValidity(error) {
1058-
return this.textareaElement.setCustomValidity(error);
1082+
get name() { return this.getAttribute("name") || ""; }
1083+
set name(val) { this.setAttribute("name", val); }
1084+
get placeholder() { return this.getAttribute("placeholder") || ""; }
1085+
set placeholder(val) { this.setAttribute("placeholder", val); }
1086+
get readOnly() { return this.hasAttribute("readonly"); }
1087+
set readOnly(val) {
1088+
if(val) {
1089+
this.setAttribute("readonly", true);
1090+
} else {
1091+
this.removeAttribute("readonly");
1092+
}
10591093
}
1060-
1061-
/**
1062-
* Returns whether a form will validate when it is submitted,
1063-
* without having to submit it.
1064-
*
1065-
* See `HTMLTextAreaElement.checkValidity`
1066-
*/
1067-
checkValidity() {
1068-
return this.textareaElement.checkValidity();
1094+
get required() { return this.hasAttribute("readonly"); }
1095+
set required(val) {
1096+
if(val) {
1097+
this.setAttribute("readonly", true);
1098+
} else {
1099+
this.removeAttribute("readonly");
1100+
}
10691101
}
1070-
1071-
/**
1072-
* See `HTMLTextAreaElement.reportValidity`
1073-
*/
1074-
reportValidity() {
1075-
return this.textareaElement.reportValidity();
1102+
get rows() { return this.getTextareaProperty("rows", Number(this.getAttribute("rows"))); }
1103+
set rows(val) { this.setAttribute("rows", val); }
1104+
get selectionDirection() { return this.getTextareaProperty("selectionDirection"); }
1105+
set selectionDirection(val) { this.setTextareaProperty("selectionDirection", val); }
1106+
get selectionEnd() { return this.getTextareaProperty("selectionEnd"); }
1107+
set selectionEnd(val) { this.setTextareaProperty("selectionEnd", val); }
1108+
get selectionStart() { return this.getTextareaProperty("selectionStart"); }
1109+
set selectionStart(val) { this.setTextareaProperty("selectionStart", val); }
1110+
get textLength() { return this.value.length; }
1111+
get type() { return "textarea"; } // Mimics textarea
1112+
get validationMessage() { return this.getTextareaProperty("validationMessage"); }
1113+
get validity() { return this.getTextareaProperty("validationMessage"); }
1114+
get value() { return this.getTextareaProperty("value", this.getAttribute("value") || this.innerHTML); }
1115+
set value(val) {
1116+
val = val || "";
1117+
if(this.setTextareaProperty("value", val, false)) {
1118+
if(this.textareaElement) this.scheduleHighlight();
1119+
} else {
1120+
this.innerHTML = val;
1121+
}
10761122
}
1123+
get willValidate() { return this.getTextareaProperty("willValidate", this.disabled || this.readOnly); }
1124+
get wrap() { return this.getAttribute("wrap") || ""; }
1125+
set wrap(val) { this.setAttribute("wrap", val); }
1126+
1127+
1128+
blur(options={}) { return this.textareaElement.blur(options); }
1129+
checkValidity() { return this.textareaElement.checkValidity(); }
1130+
focus(options={}) { return this.textareaElement.focus(options); }
1131+
reportValidity() { return this.textareaElement.reportValidity(); }
1132+
setCustomValidity(error) { this.textareaElement.setCustomValidity(error); }
1133+
setRangeText(replacement, selectionStart=this.selectionStart, selectionEnd=this.selectionEnd, selectMode="preserve") { this.getTextareaProperty("setRangeText")(replacement, selectionStart, selectionEnd, selectMode); }
1134+
setSelectionRange(selectionStart, selectionEnd, selectionDirection="none") { this.getTextareaProperty("setSelectionRange")(selectionStart, selectionEnd, selectionDirection); }
10771135

10781136
/**
10791137
* Allows plugins to store data in the scope of a single element.

docs/interface/js/_index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@ title = '`code-input` vs the `textarea` in JavaScript'
66

77
> Contributors: 2025 Oliver Geer
88
9-
Once registered, `code-input` elements support most of the JavaScript properties and events used with a `textarea` element, because they are built around them. Try swapping out your `textarea` element in your JavaScript application for a `code-input`! If it doesn't work, [submit a bug report](https://github.com/WebCoder49/code-input/issues).
9+
Once registered, `code-input` elements support the JavaScript properties and events used with a `textarea` element, because they are built around them. Try swapping out your `textarea` element in your JavaScript application for a `code-input`! If it doesn't work, [submit a bug report](https://github.com/WebCoder49/code-input/issues).
1010

1111
If you want to replace a `textarea` with a `code-input` in an application that doesn't need JavaScript, [look here](../forms). We support HTML5 forms, and progressive enhancement so JavaScript isn't needed!

0 commit comments

Comments
 (0)