ブックマークバーに登録した後にメンテナンスしやすいブックマークレットを作るコツ

今日もまたブックマークレット

#はじめに
ブラウザのブックマークにjavascriptのコードの保存して実行可能とするブックマークレットは、お手軽ではあるもの、利用者のブラウザにばらまいてしまうので、スクリプトのバージョンアップが大変です。組織で働いていると部署ごとや人ごとにスクリプトの内容を一部変えてもらったりするのですが、1行になった長いスクリプトの一部を変更してもらうことは困難に近いです。
経験的に改編しやすい作り方が分かってきたのでメモとして残します。

#人によってスクリプトの変数を変えてほしいとき
Closure Compilreを使ったり改行空白処理をして一直線のコードに変えたとき、コードの中の方に修正したい変数が埋まっていると修正するのが大変です。間違えてコードの一部も消してしまったりします。

こんなときは、一番外側の引数に値を設定するようにしておきます。

```javascript
javascript:(
    function(name){
        alert(name +'さんこんにちわ');
    }
)(
    'kanaxx'
)

```

closure compilerを通すとこう変わります

```
javascript:(function(a){alert(a+"\u3055\u3093\u3053\u3093\u306b\u3061\u308f")})("kanaxx");
```

変更したい部分が必ず一番後ろに現れます。ブックマークそのものを編集しなければいけないとしても、多少は楽にできます。外部サービスのAPIのキーとか個人単位に変えて欲しいときはこんなパターンがいいです。


##変数が少ない場合
実行時に変更する変数が少ない場合には、```prompt```関数で入力を促すのも一つです。コードを修正せずに動作を変えることができます。```name```はブックマーク上に定義した値からとるようになっているので、指定すれば```prompt```のデフォルトを設定した状態で表示します。変更がない場合はENTERを押すだけでよいです。変更したいときだけ変えればよいです。
変数が多いと、ENTERを何度も押すことになるのでおススメはしないです。2、3個ですかね。

```javascript

javascript:(
    function(name){
        name = prompt('please input your name', name);
        alert(name +'さんこんにちわ');
    }
)(
    'kanaxx'
)
```

```javascript
javascript:(function(a){a||(a=prompt("please input your name"));alert(a+"\u3055\u3093\u3053\u3093\u306b\u3061\u308f")})("kanaxx");
```

##ENTERを押すのも面倒なんだけど
この辺はもうお好みですね。

```javascript
javascript:(
    function(name){
        var defaultname = 'NANASHI';
        if( !name ){
            //選択の余地がない場合
            name = defaultname;
            //選択の余地を残す場合
            name = prompt('please input your name', defaultname);
        }
        alert(name +'さんこんにちわ');
    }
)(
    'kanaxx'
)


```

楽になったといえ、ブラウザのブックマーク編集は面倒だし、変更戻し忘れとかあるので、このパータンで作る必要があるときは、2～3種類をブックマークを作るようにしています。デフォルト値設定済みのブックマークと、デフォルト値無しのものを置いておき、実行時に使い分けるようにしています。

たぶん、5人以内の開発者で使っているうちは、このレベルでよいです。


#プログラムを外部ファイルとして取り込む
開発者以外にスクリプトを提供し始めると、PCの操作が苦手な人たちが出てきます。実行時変数の修正の難易度が上がり、バージョンアップの作業が面倒になるので、自作したjavascriptファイルを取り込む方式にしておきます。利用者が多い場合には、この方法のほうが良いです。

ブラウザの設定するブックマークレットのコードは、こう変わります。一番最後の引数をjavascriptを置いたURLに変えるだけです。（第一の原則はちゃんと使ってます）

```javascript
javascript:(function(url){
    s=document.createElement('script');
    s.src=url;
    document.body.appendChild(s);}
)(
    'https://www.expamle.com'
)
```

:warning: ただし、インターネットに繋がっていない環境だとスクリプトが動かなくなります。社内ネットワークやVPN配下のURLを指定することも可能ですが、そのマシン（URL）に到達可能な状態でないと動作しなくなります。

##jsファイルを置く
外部ファイル形式で取り込むのでファイルをインターネット上に置かないといけません。今はいろんなクラウドサービスが充実しているので、置き場所には困らないと思います。

今回はお手軽な```github pages```と```cloudinary```に以下のファイルを置いてみました。herokuでもfirebaseでもお好なところにおいてください。

```javascript:sample4.js
javascript:(
    function(name){
        var defaultname = 'NANASHI';
        if( !name ){
            //選択の余地がない場合
            // name = defaultname;
            //選択の余地を残す場合
            name = prompt('please input your name', defaultname);
        }
        alert(name +'さんこんにちわ');
    }
)(
    'kanaxx'
)
```

「名前が指定されていればその名前を表示、名前が指定されてなければプロンプト＋デフォルト値を出し、入力された名前を表示する」だけです。

###github pages
ファイル置き場
https://kanaxx.github.io/qiita0321/sample4.js

ブラウザ側のコード

```javascript
javascript:(function(url){s=document.createElement('script');s.src=url;document.body.appendChild(s);})('https://kanaxx.github.io/qiita0321/sample4.js'))
```

###cloudinary
ファイル置き場
https://res.cloudinary.com/kanaxx/raw/upload/v1553134492/bookmarklet/sample4.js

ブラウザ側のコード

```javascript
javascript:(function(url){s=document.createElement('script');s.src=url;document.body.appendChild(s);})('https://res.cloudinary.com/kanaxx/raw/upload/v1553134492/bookmarklet/sample4.js'))
```

実行すると分かりますが、プロンプトが出ません。```sample4.js```に書いてある内容が、無名関数を定義して、変数(kanaxx)を渡して即時実行になっているのでこうなりますね。
ブラウザのブックマークに登録するコードをそのままjsファイルとして外部化すると、ブラウザに登録する時に個々に変えてもらおうとしていた部分が変えられなくなります。（そりゃそうだ）。利用者ユーザごとに変えたい部分がない場合には、これでも問題はありません。


##外部ファイル化＋変数化をする
先ほどの方法で具合が悪い場合、jsファイルとブックマークレットのコードを変更します。

**jsファイル**

```javascript:sample4_2.js
function showYourName(name){
    var defaultname = 'NANASHI';
    if( !name ){
        //選択の余地がない場合
        // name = defaultname;
        //選択の余地を残す場合
        name = prompt('please input your name', defaultname);
    }
    alert(name +'さんこんにちわ');
}

```

変更点は、無名関数をやめて普通の名前付きの関数にしてあることと、関数の実行部がないこと（普通のjsファイルになりました）

**ブラウザ側のコード**

```javascript

javascript:(function(url,n){
    s=document.createElement('script');
    s.src=url;
    s.onload=function(){showYourName(n);};
    document.body.appendChild(s);
}
)(
    'https://res.cloudinary.com/kanaxx/raw/upload/v1553135897/bookmarklet/sample4_2.js'
    ,'aaa'
)
```

jsファイルに定義した関数(```showYourName```)をscriptのonloadで呼び出すようにしたことと、一番外側の変数(```n='aaa'```)をonloadの関数に渡すように変えています。これで、スクリプト本体をブラウザに埋め込むことなく、ユーザ側のブラウザの設定で多少の動作変更をすることができます。

###github pages
https://kanaxx.github.io/qiita0321/sample4_2.js

ブックマークレットのコード

```javascript
javascript:(function(url,n){s=document.createElement('script');s.src=url;s.onload=function(){showYourName(n);};document.body.appendChild(s);})('https://kanaxx.github.io/qiita0321/sample4_2.js','aaa')
```

第二引数が'aaa'があるので、onloadで関数が呼び出され、そのままalertが出て終わります。

###cloudinary
https://res.cloudinary.com/kanaxx/raw/upload/v1553135897/bookmarklet/sample4_2.js

```javascript
javascript:(function(url,n){s=document.createElement('script');s.src=url;s.onload=function(){showYourName(n);};document.body.appendChild(s);})('https://res.cloudinary.com/kanaxx/raw/upload/v1553135897/bookmarklet/sample4_2.js')
```

こっち側は第二引数がないのでプロンプトが出ます。
ブラウザ側のコードに、エントリポイントの関数名を書くことになりますが、関数名がころころ変わることもないでしょうし、まぁ問題ないでしょう。


#変数を外部サービスに預ける
スクリプト内部の変数にもスクリプトの呼び出し時の変数にも依存しない方法とするなら、変数そのものを外部に出すのがよいです。最近は`fetch()`が使えるようになったので、ブックマークレットからHTTPのアクセスがやりやすくなりました。

外部に出す方法はいくつかありますが、よく使うものをいくつか書いておきます。基本的にはHTTPでアクセスできれば、静的ファイルでも、自作のPHPの返り値でも、SaaS系のREST APIでも何とでもなると思います。

##jsonファイルを置く
HTTPできる空間にjsonファイルを置いてブックマークレットから取りに行きます。何らかの手段で取りに行くjsonを動的にする必要はあります。呼び出し変数で指定するのがスマートですね。
全員分のjsonファイルを置いておくのは面倒なので、PHPか何かでjsonを返すのがよいでしょう。jsonがインターネット側から丸見えになる可能性はあるので、見えてもいいファイルにするとか、イントラ内に置くとかはお任せします。

jsonファイル
https://kanaxx.github.io/qiita0321/kanaxx.json

```json:kanaxx.json
{
"name":"kanaxx", 
"namejp":"かなっくす", 
"site":"https://qiita.com/kanaxx/", 
"apiKey":"api no key"
}
```

スクリプトファイル

```javascript
javascript:(
    function(name){
        var defaultname = 'NANASHI';
        if( !name ){
            name = prompt('please input your name', defaultname);
        }
        fetch('https://kanaxx.github.io/qiita0321/'+name+'.json')
        .then((response)=>{return response.json()})
        .then( (user)=>{
            console.log(user);
            alert(user.namejp +'さんこんにちわ');
        });
    }
)(
    'kanaxx'
)
```

ブックマークに設定したあとに、各自で自分の名前を変えてもらいます。


##firestoreに設定値を置いておく
[この辺に書いた](https://qiita.com/kanaxx/items/ffe5fca8bc3a3ce3e48f)

コンソールにログインして値を変更するとスクリプト側は変更せずに動作が変わりますね。誰がコンソールをいじるんだという話もありますけど。

##micro CMSに頼る
この辺に書いた。
https://qiita.com/kanaxx/items/f38142d2574d236574a9

データを編集するUIがあってエンジニア以外もデータメンテナンスができ、ノープログラムでJSON取得もできるので、便利。

##実行時に選択させてしまおう
ブックマークに登録するときに、いろいろ変更させるなんてスマートじゃない。ブックマークレット実行時には選択肢だけ作って、選んでもらおう。

https://kanaxx.github.io/qiita0321/userlist.json

```json:userlist.json
[
    {"name":"user1", "namejp":"ゆーざいち", "apiKey":"api no key1"}
    ,
    {"name":"user2", "namejp":"ゆーざに", "apiKey":"api no key2"}
    ,
    {"name":"user3", "namejp":"ゆーざ３", "apiKey":"api no key3"}
    ,
    {"name":"NANASHI", "namejp":"ななしさん", "apiKey":"api no key0"}
]
```

```javascript:sample6.js
javascript:(
    function(name){
        var defaultname = 'NANASHI';
        if( !name ){
            name = prompt('please input your name', defaultname);
        }
        
        fetch('https://kanaxx.github.io/qiita0321/userlist.json')
        .then((response)=>{return response.json()})
        .then( (users)=>{
            console.log(users);
            users = users.reverse();
            div = document.createElement("div");
            formdom = 
            '<div style="margin:30px">'+
            'ユーザ選択 <select id="newmyselect"><option></option></select>'+
            '<button id="newmybutton">実行</button>'+
            '</div>';

            div.innerHTML = formdom;
            document.body.appendChild(div);

            myselect = document.getElementById('newmyselect');

            document.getElementById("newmybutton").addEventListener("click", function() {
                showName();
            }, false);

            for(i=0; i<users.length; i++){
                var opt = document.createElement("option");
                opt.value = users[i].name;
                var str = document.createTextNode(users[i].namejp);
                opt.appendChild(str);
                if( name == opt.value){
                    opt['selected']=true;
                }
                myselect.insertBefore(opt, myselect.options[0]);
            }
        });

        function showName(){
            myselect = document.getElementById('newmyselect');
            name = myselect.options[myselect.selectedIndex].value;
            namejp = myselect.options[myselect.selectedIndex].text;
            alert(namejp + '(' + name + ')さん、こんにちわ');
        }
    }
)(
    'user1'
)
```

 
**ブラウザ側のコード**

```javascript
javascript:(function(d,j,s){s=d.createElement('script');s.src=j;d.body.appendChild(s);})(document,'https://kanaxx.github.io/qiita0321/sample6.js')
```

ブックマークレット実行後の画面
![image.png](https://qiita-image-store.s3.amazonaws.com/0/168315/7626783d-80f0-73ab-cc1f-3df7a1de38f6.png)

jQuery使ったほうが簡単ですが、脱jQueryしてみました。

#まとめ
スクリプトの中心部分を編集させるくらいなら、大事な変数は一番後ろにしましょう。
最近はfetchしてリスト作ってボタン押すタイプがお気に入りです。