jQuery UI Slider で、ハンドルの数を増減したい話

## 前書き
そもそも、[ここ](https://stackoverflow.com/questions/19348528/jquery-ui-slider-how-to-add-values)の回答を転記、加工して、動作を確認しただけです。
なぜ書き直したかといいますと、2013年の回答と、現在のバージョンが異なるから、
サンプルページが動作していなかったため、
そして、日本語での参考にできる記事がなかったため、検索性を考えて、転記しておこうということです。

ということなので、話はタイトルで分かったから、早くという人は[本家](https://stackoverflow.com/questions/19348528/jquery-ui-slider-how-to-add-values)からコピーしたほうが早いです。元のままでもちゃんと動きました。

もしくは、[本番](#本番)へどうぞ。

## 何がしたいか
[jQuery UIのSlider](https://jqueryui.com/slider/)という機能、ハンドル（値を持てる箇所）を複数指定する場合

```js
$('#slider').slider({
    min : 0,
    max : 50,
    values : [0,10,20,30,40,50]
});
```
こんな感じで、指定してあげると、6つのハンドルができます。
<img width="457" alt="11.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/194701/38c753b1-279f-dd7a-8f97-cb44729ac0f1.png">

このハンドルを、操作するには、

```js
$('#slider').slider('option',{
    'values' : [5,15,25,35,45,50]
});
```
こんな感じで、指定すれば、ハンドルを操作することができます。
<img width="443" alt="12.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/194701/cbdeda76-7187-5463-abfd-224cd233a3b3.png">

では、ハンドルの数を変化させたい場合は、

```js
// 増やす？
$('#slider').slider('option',{
    'values' : [0,15,10,20,30,40,50]
});

// 減らす？
$('#slider').slider('option',{
    'values' : [0,10,20,30,40]
});
```
**これだと、動かないです。**
エラーは起きないのですが、
`values`は初期化したとき作られた、ハンドルは操作できますが、
後からハンドルの数の変動はできない仕様になっています。

今回は、これを、jQuery UI の拡張という手を使って、sliderにmethodを追加して解決しました。
#本番
前書きが長くなりましたが、
では本番です。
## 環境
* Chrome 80.0.3987.132
* jQuery 3.3.1
* jQuery UI 1.12.1  

いずれも、2020/03/05時点で最新版

## コード
```haml
<!DOCTYPE html>
<html lang="ja-jp">
<head>
    <title></title>
    <meta charset="utf-8">
    <link rel="stylesheet" type="text/css" href="./css/jquery-ui.min.css">
    <link rel="stylesheet" type="text/css" href="./css/jquery-ui.structure.min.css">
    <style type="text/css">
        #slider {
            width: 500px;
            margin: 50px
        }
    </style>
</head>
<body>
<script src="https://code.jquery.com/jquery-3.3.1.min.js" crossorigin="anonymous"></script>
<script src="./js/jquery-ui.min.js"></script>
<div id="slider"></div>
<div><button id="add_btn">追加</button><button id="remove_btn">削除</button></div>

    <script type="text/javascript">
// ハンドルの配列
var slider_handles = [0,10,20,30,40,50];
$(function(){
    // jQuery UI slider の拡張
    $.widget('my-namespace.slider', $.ui.slider, {
        addValue: function(val) {
            this.options.values.push(val);
            this._refresh();
        },
        removeValue: function() {
            this.options.values.pop();
            this._refresh();
        }
    });
    
    // sliderの初期化
    $('#slider').slider({
        min : 0,
        max : 100,
        values : slider_handles
    });

    // ボタンのイベントリスナ
    $('#add_btn').on('click', function(event) {
        // 一番最後にあるハンドルに+10した場所に新しく作る
        $('#slider').slider('addValue',slider_handles[slider_handles.length-1]+10);
    });
    $('#remove_btn').on('click', function(event) {
        // ハンドルを1つ削除
        $('#slider').slider('removeValue');
    });
});
    </script>
</body>
</html>
```
こんな感じです。上のソースはjQuery UIだけダウンロードしてきて、そのままコピペすれば動くはずです。
### 簡単に説明
```js
    // jQuery UI slider の拡張
    $.widget('my-namespace.slider', $.ui.slider, {
        addValue: function(val) {
            this.options.values.push(val);
            this._refresh();
        },
        removeValue: function() {
            this.options.values.pop();
            this._refresh();
        }
    });
```
の部分で、sliderを拡張しています。`addValue`と、`removeValue`を追加しています。
大切なのは、`this._refresh()`だけです。
`this._refresh()`は、sliderの初期化時に、処理される部分で、一度sliderを破棄してから、作り直しています。

あとは、ボタンのイベントリスナーからそれぞれのメソッドを呼び出します。

```js
// ボタンのイベントリスナー
    $('#add_btn').on('click', function(event) {
        $('#slider').slider('addValue',slider_handles[slider_handles.length-1]+10);
    });
    $('#remove_btn').on('click', function(event) {
       $('#slider').slider('removeValue');
    });
```
追加する方のイベントリスナーでは、新しいハンドルの場所をしてしなくてはいけないので、
サンプルとしては、とりあえず、今ある一番最後のハンドルの+10の位置に追加しています。

## 注意書き
### 元のsliderをオーバーライトして使ってる話
`$.widget('my-namespace.slider', $.ui.slider, {});`の部分は、
`$.ui.slider`を継承して、`my-namespace.slider`を新しく作っています。
今回は、そのまま同じ名前の、sliderを使っているので、何も気にせず、拡張されていますが、
この方法を使うと、拡張部分をコピーし忘れてソースコード中からほかのプログラムにコピペすると、動かないので、
`$.widget('my-namespace.custom_slider', $.ui.slider, {});`のように、
新しい名前を付けて使うのが普通見たいです。ただ、その場合、`$(selector).slider();`で書いているところをすべて、
`$(slector).custom_slider();`に書き換えて使ってください。
散々コードを書いてしまった後で、追加するようなときは、今回のようにすると、そのまま使えます。

### 配列をグローバルに置いている話
ハンドルの値をしまっている配列をグローバルに置いてるのは、今回わかりやすくするために置いただけで、
`$('#slider').slider('values');`に取得してあげれば、素直に配列で返ってくるので、グローバルを汚したくない人はこちらがいいかも。

### ないのに削除したり、もう入らないのに追加できる話
`remove`では、配列の中に１つもなくても、実行できています。
`add`では、値がはみ出ても追加できています。
実際に使うときはアラート出すなりして、それぞれ工夫してください。

## 余談
### 要は`this._refresh()`
`this._refresh();`だけで動くんだったら、refreshメソッドだけ拡張すればいい気もする。
今回私は、１行ずつの追加削除でよかったけれど、複数行扱うときは、そのほうがいい気がする。
### `array.pop()`だけでは不満なら
頭から消したかったり、指定して削除したかったり、することもあるかも、と思った。今のところない。
実装が必要になったら、更新するかもしれない。

### 本家が実装してくれればいいのに
お願いします。

## 最後に
サンプルとして投稿しているコードは実際に使ったものを抽象化しているものです。
動作は保証しませんが、問題があれば、教えていただければ修正するかもしれません。

この投稿のコード権利を主張しませんので、適当にコピペして使って大丈夫です。

質問とか、適当こいてんじゃないよみたいなことがあれば、コメントいただければ。