| @@ -2,21 +2,18 @@ | |||||
| * Detail: 仿照 DOMTokenList 功能实现的自定义 CustomTokenList | * Detail: 仿照 DOMTokenList 功能实现的自定义 CustomTokenList | ||||
| * Reference: https://gist.github.com/dimitarnestorov/48b69a918288e9098db1aab904a2722a | * Reference: https://gist.github.com/dimitarnestorov/48b69a918288e9098db1aab904a2722a | ||||
| * Use: | * Use: | ||||
| * let span = document.querySelector("span"); | |||||
| * let customTokenList = new CustomTokenList(span, "custom-token"); | |||||
| * span.customToken = customTokenList; | |||||
| * span.customToken.add("token1","token2") | |||||
| * console.log(span.getAttribute("custom-token")); | |||||
| * //=> token1 token2 | |||||
| * span.setAttribute("custom-token", "token-a token-b token-c"); | |||||
| * console.log(span.customToken.value); | |||||
| * //=> token-a token-b token-c | |||||
| * span.customToken.add("token1","token2") | |||||
| * console.log(span.getAttribute("custom-token")); | |||||
| * //=> token1 token2 | |||||
| let span = document.querySelector("span"); | |||||
| let _ctl = new CustomTokenList(span, "custom-token"); | |||||
| span.customTokenList = _ctl; | |||||
| span.customTokenList.add("token1","token2"); | |||||
| console.log(span.getAttribute("custom-token")); | |||||
| // token1 token2 | |||||
| await span.setAttribute("custom-token", "token-a token-b token-c"); | |||||
| console.log(span.customTokenList.value); | |||||
| // token-a token-b token-c | |||||
| */ | */ | ||||
| class CustomTokenList { | |||||
| tokens = new Set(); | |||||
| class CustomTokenList extends Array { | |||||
| #node = null; | #node = null; | ||||
| #attributeName = null; | #attributeName = null; | ||||
| #refreshAttribute = true; | #refreshAttribute = true; | ||||
| @@ -39,11 +36,12 @@ class CustomTokenList { | |||||
| } | } | ||||
| } | } | ||||
| constructor(node, attributeName){ //传入 HTMLElement 和需要绑定的 参数名称 | constructor(node, attributeName){ //传入 HTMLElement 和需要绑定的 参数名称 | ||||
| super(); | |||||
| if (Object.getPrototypeOf(node).constructor == Attr) { | if (Object.getPrototypeOf(node).constructor == Attr) { | ||||
| this.#attribute = node; | this.#attribute = node; | ||||
| } else if (node instanceof HTMLElement) { | } else if (node instanceof HTMLElement) { | ||||
| this.#node = node; | this.#node = node; | ||||
| this.#attributeName = attributeName.toString().toLowerCase(); | |||||
| this.#attributeName = attributeName.toString().toLowerCase(); //attributeName 只支持小写 | |||||
| this.#observerOptions.attributeFilter = [this.#attributeName]; | this.#observerOptions.attributeFilter = [this.#attributeName]; | ||||
| } else { | } else { | ||||
| throw new TypeError(`${CustomTokenList.name}.constructor: Argument 1 is not an Attr or HTMLElement.\n参数 1 不是 Attr 或 HTMLElement。`); | throw new TypeError(`${CustomTokenList.name}.constructor: Argument 1 is not an Attr or HTMLElement.\n参数 1 不是 Attr 或 HTMLElement。`); | ||||
| @@ -53,9 +51,8 @@ class CustomTokenList { | |||||
| this.#observer = new MutationObserver(function(mutationList) { | this.#observer = new MutationObserver(function(mutationList) { | ||||
| for (const mutation of mutationList) { | for (const mutation of mutationList) { | ||||
| if (mutation.type == 'attributes' && mutation.attributeName == _this.#attributeName) { | if (mutation.type == 'attributes' && mutation.attributeName == _this.#attributeName) { | ||||
| console.log('单个变化',mutation); | |||||
| _this.#attribute = _this.#node.getAttributeNode(_this.#attributeName); | _this.#attribute = _this.#node.getAttributeNode(_this.#attributeName); | ||||
| _this.tokens.clear(); | |||||
| _this.length = 0; | |||||
| if (_this.#attribute) { | if (_this.#attribute) { | ||||
| _this.#refreshAttribute = false; //外部属性变化时,添加内容不再循环进行属性的更新 | _this.#refreshAttribute = false; //外部属性变化时,添加内容不再循环进行属性的更新 | ||||
| _this.add(..._this.#attribute.nodeValue.split(/\s+/g)); | _this.add(..._this.#attribute.nodeValue.split(/\s+/g)); | ||||
| @@ -78,14 +75,16 @@ class CustomTokenList { | |||||
| if (tokens.some(token=>/\s/.test(token))) | if (tokens.some(token=>/\s/.test(token))) | ||||
| throw CustomTokenList.#InvalidCharacterError('add'); | throw CustomTokenList.#InvalidCharacterError('add'); | ||||
| tokens.forEach(token=>this.tokens.add(token)); | |||||
| tokens.forEach(token => { | |||||
| if (!this.includes(token)) this.push(token); | |||||
| }); | |||||
| if (this.#refreshAttribute) { | if (this.#refreshAttribute) { | ||||
| this.#observer.disconnect(); //解除绑定 | this.#observer.disconnect(); //解除绑定 | ||||
| if (!this.#attribute) { | if (!this.#attribute) { | ||||
| this.#node.setAttributeNode(document.createAttribute(this.#attributeName)); | this.#node.setAttributeNode(document.createAttribute(this.#attributeName)); | ||||
| } | } | ||||
| this.#attribute.value = [...this.tokens].join(' '); | |||||
| this.#attribute.value = this.join(' '); | |||||
| this.#observer.observe(this.#node, this.#observerOptions); //恢复绑定 | this.#observer.observe(this.#node, this.#observerOptions); //恢复绑定 | ||||
| } | } | ||||
| return; | return; | ||||
| @@ -98,18 +97,21 @@ class CustomTokenList { | |||||
| if (tokens.some(token=>/\s/.test(token))) | if (tokens.some(token=>/\s/.test(token))) | ||||
| throw CustomTokenList.#InvalidCharacterError('remove'); | throw CustomTokenList.#InvalidCharacterError('remove'); | ||||
| tokens.forEach(token=>this.tokens.delete(token)); | |||||
| tokens.forEach(token => { | |||||
| const index = this.indexOf(token); | |||||
| if (index>=0) this.splice(index,1); | |||||
| }); | |||||
| if (this.#refreshAttribute) { | if (this.#refreshAttribute) { | ||||
| this.#observer.disconnect(); //解除绑定 | this.#observer.disconnect(); //解除绑定 | ||||
| this.#attribute.value = [...this.tokens].join(' '); | |||||
| this.#attribute.value = this.join(' '); | |||||
| this.#observer.observe(this.#node, this.#observerOptions); //恢复绑定 | this.#observer.observe(this.#node, this.#observerOptions); //恢复绑定 | ||||
| } | } | ||||
| return; | return; | ||||
| } | } | ||||
| contains(token){ | contains(token){ | ||||
| return this.tokens.has(token.toString()); | |||||
| return this.includes(token.toString()); | |||||
| } | } | ||||
| toggle(token, force){ | toggle(token, force){ | ||||
| @@ -135,37 +137,23 @@ class CustomTokenList { | |||||
| } | } | ||||
| forEach(callback, thisArg) { | |||||
| [...this.tokens].forEach( | |||||
| (value, index, array)=> | |||||
| callback.call(thisArg ?? this, value, index, array) | |||||
| ); | |||||
| } | |||||
| entries() { | |||||
| return [...this.tokens].entries(); | |||||
| } | |||||
| get value() { | get value() { | ||||
| return [...this.tokens].join(' '); | |||||
| } | |||||
| values() { | |||||
| return this.tokens.values(); | |||||
| } | |||||
| keys() { | |||||
| return new Array(this.tokens).keys(); | |||||
| return this.join(' '); | |||||
| } | } | ||||
| replace(oldToken, newToken){ | replace(oldToken, newToken){ | ||||
| oldToken = oldToken.toString(); | |||||
| newToken = newToken.toString(); | |||||
| if (/\s/.test(oldToken) || /\s/.test(newToken)) | if (/\s/.test(oldToken) || /\s/.test(newToken)) | ||||
| throw CustomTokenList.#InvalidCharacterError('replace'); | throw CustomTokenList.#InvalidCharacterError('replace'); | ||||
| if (this.contains(oldToken)) { | |||||
| this.#refreshAttribute = false; //减少一次属性的更新 | |||||
| this.remove(oldToken); | |||||
| this.#refreshAttribute = true; | |||||
| this.add(newToken); | |||||
| const index = this.indexOf(oldToken); | |||||
| if (index>=0) { | |||||
| this.splice(index,1, newToken); | |||||
| if (this.#refreshAttribute) { | |||||
| this.#observer.disconnect(); //解除绑定 | |||||
| this.#attribute.value = this.join(' '); | |||||
| this.#observer.observe(this.#node, this.#observerOptions); //恢复绑定 | |||||
| } | |||||
| return true; | return true; | ||||
| } else { | } else { | ||||
| return false; | return false; | ||||
| @@ -173,11 +161,7 @@ class CustomTokenList { | |||||
| } | } | ||||
| item(index) { | item(index) { | ||||
| return [...this.tokens][index]; | |||||
| } | |||||
| valueOf() { | |||||
| return this.tokens; | |||||
| return this[index]; | |||||
| } | } | ||||
| }; | }; | ||||