- エーオーシステム コーポレートサイト
https://www.aosystem.co.jp/ - エーオーシステム プロダクトサイト
https://ao-system.net/ - レンタルサーバー
- バーチャル展示会
- ウェブカタログサービス
- 3Dグラフィック
- Android アプリ
- iOS (iPhone,iPad) アプリ
- Flutter開発
- プログラミング記録QuickAnswer
- 無料画像素材
- スカイボックス 3D SKY BOX
このページのQRコード
・textareaのサイズ変更にヘッダ部分が追従。
→ MutationObserver()でstyleの変更を監視。
・Closed な Shadow DOM なのにstyle設定可能。
→ Light DOM の style を Shadow DOM にコピー。
・複数の Light DOM に個別に適用。
→ Shadow DOM でクラスのインスタンス生成。
使用結果
[HTML]
<style>
textarea-sort.one {
width: 300px;
height: 100px;
border: solid 1px #4ad;
}
textarea-sort.two {
width: 400px;
height: 500px;
border-style: dashed;
border-width: 0px 2px 2px 2px;
border-color: #d4a;
border-radius: 10px;
background-color: #fee;
padding: 5px 10px;
color: #333;
}
</style>
<textarea-sort class="one"></textarea-sort>
<textarea-sort class="two">Lion
Elephant
Giraffe
Tiger
01
1
001
Panda
Dolphin
Eagle
Eagle
Koala
Kangaroo
Cheetah
Dolphin
Koala</textarea-sort>
<script defer src="./js/ce_textarea_sort.js"></script>
/**
* ソート、コピー機能付きtextarea
*
* @author ao-system, Inc.
* @date 2024-03-03
*
* e.g.
* <textarea-sort></textarea-sort>
* <script defer src="ce_textarea_sort.js"></script>
*
*/
(() => {
'use strict';
class TextareaSort {
#constProperties = [
{ prop: 'svgPath',
value: '<path d="M2.77,10.45l-1.5,1.5V5.17c0-2.49,2.02-4.51,4.51-4.51h6.78l-1.5,1.5H5.78c-1.66,0-3.01,1.35-3.01,3.01V10.45z"/><path class="icon" d="M12.18,15.34H5.84c-1.3,0-2.35-1.05-2.35-2.35V5.15c0-1.3,1.05-2.35,2.35-2.35h6.34c1.3,0,2.35,1.05,2.35,2.35v7.84C14.53,14.29,13.48,15.34,12.18,15.34z M5.84,4.3c-0.47,0-0.85,0.38-0.85,0.85v7.84c0,0.47,0.38,0.85,0.85,0.85h6.34c0.47,0,0.85-0.38,0.85-0.85V5.15c0-0.47-0.38-0.85-0.85-0.85H5.84z"/>',
},
{ prop: 'style',
value: `
:host > div { /*wrapper*/
--color-bar-fore: #fff;
width: 100%;
position: relative;
> div { /*bar*/
width: 100%;
padding: 2px 5px;
box-sizing: border-box;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
column-gap: 5px;
> div { /*text button*/
font-size: 14px;
color: var(--color-bar-fore);
cursor: pointer;
user-select: none;
white-space: nowrap;
opacity: 0.6;
&:hover {
opacity: 1;
}
}
> span { /*count,sign*/
font-size: 14px;
color: var(--color-bar-fore);
white-space: nowrap;
}
> svg { /*copy*/
fill: var(--color-bar-fore);
cursor: pointer;
opacity: 0.6;
&:hover {
opacity: 1;
}
}
}
> textarea {
box-sizing: border-box;
}
}
`,
},
{ prop: 'styleElm',
value: document.createElement('style'),
},
{ prop: 'wrapElm',
value: document.createElement('div'),
},
{ prop: 'barElm',
value: document.createElement('div'),
},
{ prop: 'countElm',
value: document.createElement('span'),
},
{ prop: 'textareaElm',
value: document.createElement('textarea'),
},
];
constructor() {
//class内でwritable:falseの定数を作成する方法
this.#constProperties.forEach((obj) => Object.defineProperty(this, obj.prop, {value:obj.value, writable:false}));
}
#dataCount() {
const count = this.textareaElm.value.split('\n').filter(line => line.trim() !== '').length;
this.countElm.textContent = 'count:' + count;
}
#dataSort() {
const ary = this.textareaElm.value.split('\n').filter(line => line.trim() !== '');
const ary2 = ary.sort();
this.textareaElm.value = ary2.join('\n');
}
#dataReverse() {
const ary = this.textareaElm.value.split('\n').filter(line => line.trim() !== '');
const ary2 = ary.reverse();
this.textareaElm.value = ary2.join('\n');
}
#dataUnique() {
const ary = this.textareaElm.value.split('\n').filter(line => line.trim() !== '');
const ary2 = [...new Set(ary)];
this.textareaElm.value = ary2.join('\n');
this.#dataCount();
}
#clipboardCopy() {
return new Promise((resolve, reject) => {
try {
navigator.clipboard.writeText(this.textareaElm.value).then(() => {
resolve('Copied!');
}, () => {
resolve('Could not copy');
});
} catch(e) {
resolve('HTTPS required');
}
});
}
//styleを作成
#styleElement() {
this.styleElm.textContent = this.style;
}
//barを作成
#barElement() {
this.barElm.appendChild(this.countElm);
//
const sortElm = document.createElement('div');
sortElm.textContent = 'sort';
sortElm.addEventListener('click', () => {this.#dataSort();});
this.barElm.appendChild(sortElm);
//
const reverseElm = document.createElement('div');
reverseElm.textContent = 'reverse';
reverseElm.addEventListener('click', () => {this.#dataReverse();});
this.barElm.appendChild(reverseElm);
//
const uniqueElm = document.createElement('div');
uniqueElm.textContent = 'unique';
uniqueElm.addEventListener('click', () => {this.#dataUnique();});
this.barElm.appendChild(uniqueElm);
//
const signElm = document.createElement('span');
this.barElm.appendChild(signElm);
//
const svgElm = document.createElementNS('http://www.w3.org/2000/svg','svg');
svgElm.setAttribute('width',16);
svgElm.setAttribute('height',16);
svgElm.innerHTML = this.svgPath;
svgElm.addEventListener('click', async () => {
signElm.textContent = await this.#clipboardCopy();
setTimeout(() => { signElm.textContent = ''; },3000);
});
this.barElm.appendChild(svgElm);
this.wrapElm.appendChild(this.barElm);
}
//textareaを作成
#textareaElement(elementRef) {
this.textareaElm.value = elementRef.innerHTML;
this.textareaElm.addEventListener('input',() => {this.#dataCount();});
this.wrapElm.appendChild(this.textareaElm);
}
//shadowDOMにappend
#attachShadow(elementRef,styleElm,wrapElm) {
const shadowClosed = elementRef.attachShadow({mode:'closed'});
shadowClosed.innerHTML = '';
shadowClosed.appendChild(this.styleElm);
shadowClosed.appendChild(this.wrapElm);
}
//textareaのスタイル変更を監視
#observeTextarea() {
const observer = new MutationObserver((mutationsList, observer) => {
for(const mutation of mutationsList) {
if (mutation.type === 'attributes' && mutation.attributeName === 'style') {
//textareaのwidthが変化したらwrapperのwidthに適用
this.wrapElm.style.width = this.textareaElm.style.width;
}
}
});
observer.observe(this.textareaElm, { attributes:true, attributeFilter:['style'] });
}
//textarea-sortのスタイルを取得してshadowDOM内のエレメントに適用する
#applyStyle(elementRef) {
const computedStyle = window.getComputedStyle(elementRef);
for (let i = 0; i < computedStyle.length; i++) {
const propertyName = computedStyle[i];
const propertyValue = computedStyle.getPropertyValue(propertyName);
if (['box-sizing','resize','white-space-collapse'].includes(propertyName) == false) { //これらを除く
this.textareaElm.style.setProperty(propertyName, propertyValue);
}
}
//textarea-sortのborderTopColorをshadow内barのbackgroundColorに適用
this.barElm.style.backgroundColor = computedStyle.borderTopColor;
//textarea-sortのborderTopLeftRadius,borderTopRightRadiusをbarのborderTopLeftRadius,borderTopRightRadiusに適用
this.barElm.style.setProperty('border-top-left-radius',computedStyle.borderTopLeftRadius);
this.barElm.style.setProperty('border-top-right-radius',computedStyle.borderTopRightRadius);
this.textareaElm.style.borderTopLeftRadius = 0;
this.textareaElm.style.borderTopRightRadius = 0;
//textareaのwidthをwrapperのwidthに適用
this.wrapElm.style.width = this.textareaElm.clientWidth + 'px';
//textarea-sortのスタイルを削除。<textarea-sort/>を非表示にする為
elementRef.style.border = 'none';
elementRef.style.padding = 0;
}
render(elementRef) {
this.#styleElement();
this.#barElement();
this.#textareaElement(elementRef);
this.#attachShadow(elementRef);
this.#observeTextarea();
this.#applyStyle(elementRef);
this.#dataCount();
}
}
customElements.define('textarea-sort',
class extends HTMLElement {
constructor() {
super();
(new TextareaSort()).render(this);
}
}
);
})();
このページのQRコード
便利ウェブサイト
便利 Android アプリ
便利 iOS(iPhone,iPad) アプリ