連番を含むコードの作成をちょっとだけ楽にするツールを作る。

#はじめに
今回は、趣味としてちびちびスキルアップしてきたプログラミングで、何か作ってみようと考えた末に作った、自分用のツールです。
やはり形になるものを1つ作ると、考案から完成までの流れが理解しやすくなるなぁと思いました。

これまで、バッチファイルから始めて、HTML/CSS/JavaScript/Pythonを触ってきましたが、今回はHTML/CSS/JavaScriptを使いました。とりあえず、作ったものと参考にしたURLとかを備忘録的に書いておこうかと思います。

##きっかけ
* 趣味としてかじっていたプログラミングを使って何か作りたかった
* 連番が出てくるコードはたくさんあるが、for文やEmmetのような便利なツールを使えない/使いたくない状況で、いちいちコピペして数字を打つのが面倒だと感じた場面が最近あった

#目標とするもの
例えば、

```example.css
body input[num="1"] > span#index_1
body input[num="2"] > span#index_2
body input[num="3"] > span#index_3
body input[num="4"] > span#index_4
body input[num="5"] > span#index_5
```

こういうのを作るのを楽にしたい。

##プラン立て・HTML/CSSで骨組み作り

###プランを立てる大切さ
完成形のイメージを持って開発を行うことはとても大切。今回はHTML/CSS/JavaScriptを使用したが、しっかりとしたプランを立てておくことで、ID名やクラス名、属性名を分かりやすいものにし、その後のメンテナンスもしやすくなるようにできる。

###今回のプラン

**[共通の文字列]{連番}[共通の文字列]{連番}・・・**のような構造になっているので、それにあわせて設計する。

**[文字列]{連番}[文字列]**という形を基本として、必要な分だけ**[文字列]{連番}**の組み合わせを追加できるようにする。

###骨組みの作り上げ

完成体の体が見えてきたため、HTML/CSSを書き上げる。基本的にすべてのパーツをJavaScriptでの処理に使用するので、ID名などは分かりやすいものをつけることを心がけた。

参考 : [CSS クラス名リスト](https://github.com/manabuyasuda/coding-guidelines/blob/master/css/css-naming-list.md)
ID/クラス名の名付け方がとても参考になる。 

また、フリーのアイコン素材を配布しているサイトはたくさんあるので、とりあえず探せば見つかる。(今回はHTMLファイルに埋め込んだ)

|![scr.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/305299/4d78cdd3-3ba7-8909-0bbc-ff02d1575639.png)
|:-:|

リップルエフェクト(クリックしたら波紋が出るあれ)の導入も行ったので、それについてもいつかまとめたい。

<details><summary>index.html</summary><div>

```index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>連番サポート</title>
    <link rel="stylesheet" href="main.css">
    <link rel="icon" href="./img/icon16.png" sizes="16x16" type="image/png">
    <link rel="icon" href="./img/icon32.png" sizes="32x32" type="image/png">
    <link rel="icon" href="./img/icon48.png" sizes="48x48" type="image/png">
    <link rel="icon" href="./img/icon64.png" sizes="64x64" type="image/png">
</head>
<body>
    <div id="content">
        <div id="primary">
            <textarea name="String_0" id="String_0"></textarea>
            <div id="Number_1"><input type="number" name="Number_1_start" value="1">&#61;&gt;<input type="number" name="Number_1_end" value="1"></div>
            <textarea name="String_1" id="String_1"></textarea>
            <div>
                <svg id="plus" x="0px" y="0px" viewBox="0 0 512 512" tabindex="0">
                <g>
                    <path class="st0" d="M359.244,224.004h-59.988c-6.217,0-11.258-5.043-11.258-11.258v-59.992c0-6.215-5.039-11.254-11.256-11.254
                        h-41.486c-6.217,0-11.258,5.039-11.258,11.254v59.992c0,6.215-5.039,11.258-11.256,11.258h-59.988
                        c-6.219,0-11.258,5.039-11.258,11.258v41.484c0,6.215,5.039,11.258,11.258,11.258h59.988c6.217,0,11.256,5.039,11.256,11.258
                        v59.984c0,6.219,5.041,11.258,11.258,11.258h41.486c6.217,0,11.256-5.039,11.256-11.258v-59.984
                        c0-6.219,5.041-11.258,11.258-11.258h59.988c6.217,0,11.258-5.043,11.258-11.258v-41.484
                        C370.502,229.043,365.461,224.004,359.244,224.004z"></path>
                    <path class="st0" d="M256,0C114.613,0,0,114.617,0,256c0,141.387,114.613,256,256,256c141.383,0,256-114.613,256-256
                        C512,114.617,397.383,0,256,0z M256,448c-105.871,0-192-86.129-192-192c0-105.867,86.129-192,192-192c105.867,0,192,86.133,192,192
                        C448,361.871,361.867,448,256,448z"></path>
                </g>
                </svg>
                <svg id="minus" x="0px" y="0px" viewBox="0 0 512 512" class="disabled" tabindex="0">
                    <g>
                        <path class="st0" d="M359.77,224.004H152.228c-5.928,0-10.732,4.804-10.732,10.73v42.535c0,5.926,4.805,10.734,10.732,10.734
                            H359.77c5.928,0,10.732-4.809,10.732-10.734v-42.535C370.502,228.808,365.697,224.004,359.77,224.004z"></path>
                        <path class="st0" d="M256,0C114.613,0,0,114.617,0,256c0,141.387,114.613,256,256,256c141.383,0,256-114.613,256-256
                            C512,114.617,397.383,0,256,0z M256,448c-105.871,0-192-86.129-192-192c0-105.867,86.129-192,192-192c105.867,0,192,86.133,192,192
                            C448,361.871,361.867,448,256,448z"></path>
                    </g>
                </svg>
            </div>
        </div>
        
        <div id="secondary">
            <div class="submit_form"><button id="apply" class="submit">実行</button><button id="reset" class="submit">リセット</button></div>
            <dl>
                <dt>結果</dt>
                <dd><textarea name="result" id="result"></textarea></dd>
                <dt>結果をクリップボードにコピー</dt>
                <dd><button id="clip">コピー</button></dd>
            </dl>

        </div>
    </div>
</body>
<script src="main.js"></script>
</html>

```

</div></details>



<details><summary>main.css</summary><div>

```main.css

@import url('https://fonts.googleapis.com/css?family=Source+Code+Pro&display=swap');

body{
    background-color: #222222;
    color: white;
    margin: 20px 0 0 20px;
    display: flex;
    justify-content: center;
}

textarea, input[type="number"]{
    background-color: #333333;
    border: 1px solid #666;
    color: white;
    margin: 15px 0;
}

textarea{
    padding: 10px;
    width: 20em;
    height: 5em;
    display: block;
    font-family: 'Source Code Pro', monospace;
}

input[type="number"]{
    width: 7em;
    padding: 3px;
    margin: 0 15px;
}

textarea:hover, input[type="number"]:hover{
    border: 1px solid #ffa6008e;
    outline: none;
}

textarea:focus, input[type="number"]:focus{
    border: 1px solid orange;
    outline: none;
}

svg{
    fill:white;
    width: 20px;
    height: 20px;
    cursor: pointer;
}

svg:hover{
    fill: #ffa6008e;
}

#plus{
    margin: 1em 1em 0 6.5em;
}

#minus{
    margin: 1em 6.5em 0 1em;
}

#minus.disabled, #minus.disabled:hover{
    fill: gray;
    cursor: inherit;
}

.submit_form{
    margin: 0 0 0 30px;
}

.submit{
    color: white;
    width: 80px;
    height: 80px;
    background-color: transparent;
    border: 1px solid #666;
    border-radius: 50%;
    outline: none;
    margin: 20px 0 30px 20px;
    display: inline-block;
    position: relative;
    z-index: 1;
    overflow: hidden;
}

.submit:hover{
    border: 1px solid #ffa6008e;
}

.submit:focus{
    border: 1px solid orange;
}

#secondary{
    margin: 40px 0 0 0;
}

dl{
    margin-top: 40px;
}

dd{
    margin-left: 0;
}

#result{
    width: 20em;
    height: 15em;
}

@media screen and (min-width: 768px) {

    body {
        display: block;
    }

    #content{
        display: flex;
    }

    #secondary{
        margin: 0 0 0 80px;
    }

    #result{
        width: 40em;
        height: 20em;
    }
}


.ripple{
    position: absolute;
    width: 0;
    height: 0;
    border: none;
    border-radius: 50%;
    background-color: #ffa600e1;
    z-index: 5;
    pointer-events: none;

    animation-name: fadeOut;
    animation-duration: .5s;
    animation-fill-mode:none;
}

@keyframes fadeOut {
    0% {
        opacity:1;
        width: 0px;
        height: 0px;
        border: none;
    }

    40% {
        opacity: .3;
    }

    100% {
        opacity:0;
        width: 100px;
        height: 100px;
        transform:translateX(-50px) translateY(-50px);
    }
}

#clip{
    background-color: transparent;
    color: white;
    border: 1px solid #666;
    border-radius: 5px;
    outline: none;
    width: 64px;
    height: 24px;
    margin: 10px 0 0 15px;
    position: relative;
    z-index: 1;
    overflow: hidden;
}

#clip:hover{
    border: 1px solid #ffa6008e;
}

#clip:focus{
    border: 1px solid orange;
}

```

</div></details>

##JavaScriptでメイン処理を書く
###アイコン周りのプラン
+アイコンで[文字列]{連番}のブロックを一つ追加
-アイコンで削除 (ただし初期状態のみこれ以上削除できないよう、クリックできないようにしている)

HTML要素の追加には、.appendChild()等を使っても良いのだが、今回は.insertAdjacentHTML()を使用した。パフォーマンス面で優秀らしい。([innerHTML より insertAdjacentHTML を使う](https://qiita.com/amamamaou/items/624c22adec32515e863b)より)今回は特にパフォーマンスが要求される場面ではないが、今後はよく使用することになりそう。

```main_前半.js


let element_count = 1;

    document.querySelector('svg#plus').addEventListener('click', function () {
        if (element_count === 1) {
            document.getElementById('minus').classList.remove('disabled');
        }
        element_count+=1;
        let adding_element = '<span class="block_content" id="Number_' + element_count + '"><input type="number" name="Number_' + element_count + '_start" value="1">&#61;&gt;<input type="number" name="Number_' + element_count + '_end" value="1"></span><textarea name="String_' + element_count + '" id="String_' + element_count + '" class="block_content"></textarea>'
        document.querySelector('#String_' + (element_count - 1)).insertAdjacentHTML('afterend', adding_element);
    })

    document.querySelector('svg#minus').addEventListener('click', function () {
        if (element_count !== 1) {
            document.querySelector('span#Number_' + element_count).remove();
            document.querySelector('#String_' + element_count).remove();
            if (element_count === 2) {
                document.getElementById('minus').classList.add('disabled');
            }
            element_count-=1;
        }
    })
```

###メイン処理のプラン
まず、例えば1-10の連番を振る際に、すべての欄を1-10にセットして・・・とするのは面倒なので、指定された数値範囲が最も大きいものに合わせて連番を振るようにした。
つまり、下のように、1カ所のみ1-10の範囲指定をすれば、それに合わせて連番の範囲が自動的に判断され、あとは左側の数字に合わせてそれぞれ連番を振っていけるようにした。

|![max.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/305299/03d1288e-e532-3b87-f053-e5579d51bab6.png)
|:-:|

こんな感じでコーディング。。。

```main.js

window.addEventListener('load', function () {
    
    let element_count = 1;

    document.querySelector('svg#plus').addEventListener('click', function () {
        if (element_count === 1) {
            document.getElementById('minus').classList.remove('disabled');
        }
        element_count+=1;
        let adding_element = '<span class="block_content" id="Number_' + element_count + '"><input type="number" name="Number_' + element_count + '_start" value="1">&#61;&gt;<input type="number" name="Number_' + element_count + '_end" value="1"></span><textarea name="String_' + element_count + '" id="String_' + element_count + '" class="block_content"></textarea>'
        document.querySelector('#String_' + (element_count - 1)).insertAdjacentHTML('afterend', adding_element);
    })

    document.querySelector('svg#minus').addEventListener('click', function () {
        if (element_count !== 1) {
            document.querySelector('span#Number_' + element_count).remove();
            document.querySelector('#String_' + element_count).remove();
            if (element_count === 2) {
                document.getElementById('minus').classList.add('disabled');
            }
            element_count-=1;
        }
    })
    
    document.getElementById('apply').addEventListener('click', (ev) => {
        document.getElementById('apply').insertAdjacentHTML('afterbegin', '<span class="ripple" style="left: ' + ev.offsetX + 'px; top: ' + ev.offsetY + 'px;"></span>');

        let width_loop = [];
        for (let i = 1; i <= element_count; i++) {
            width_loop.push(Math.abs((document.querySelector('input[name="Number_' + i  + '_start"]').value - document.querySelector('input[name="Number_' + i + '_end"]').value)));
        }
        let loopNumber = Math.max.apply(null, width_loop);

        setResult(loopNumber);
    })

    document.getElementById('reset').addEventListener('click', (ev) => {
        document.getElementById('reset').insertAdjacentHTML('afterbegin', '<span class="ripple" style="left: ' + ev.offsetX + 'px; top: ' + ev.offsetY + 'px;"></span>');

        let allTextarea = document.querySelectorAll('#primary textarea');

        for (textarea of allTextarea) {
            textarea.value = '';
        }
    })

    function setResult(loopNumber) {
        let result = [];
        for (let n = 0; n <= loopNumber; n++) {
            let result_string = '';
            for (let i = 0; i <= element_count; i++) {
                if (i !== element_count) {
                    result_string = result_string + document.querySelector('textarea[name="String_' + i + '"]').value + (n + Math.min(document.querySelector('input[name="Number_' + (i + 1)  + '_start"]').value, document.querySelector('input[name="Number_' + (i + 1)  + '_end"]').value)).toString();
                } else {
                    result_string = result_string + document.querySelector('textarea[name="String_' + i + '"]').value;
                    result.push(result_string);
                }
            }
        }
        
        document.getElementById('result').innerHTML = result.join('\n');
    }

    document.getElementById('clip').addEventListener('click', (ev) => {
        document.getElementById('clip').insertAdjacentHTML('afterbegin', '<span class="ripple" style="left: ' + ev.offsetX + 'px; top: ' + ev.offsetY + 'px;"></span>');
        document.getElementById('result').select();
        document.execCommand("copy");
    
    })

})

```

#完成
そんな感じでできたのがこれです。
[連番サポート (on GitHub)](https://numlocker-japan.github.io/Consecutive-Num-Tool/)

|![res.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/305299/2086ecd9-088a-d8fa-3093-19f6cff6bffd.png)
|:-:|