プリザンターでサイトの移動を抑止する方法

## はじめに
プリザンターをつかっていて「このサイト、階層が変わってる！だれが動かしたの？」ってなること、ありませんか？
サイトの管理権限をユーザーに持たせていると、サイトの階層移動や順番の入れ替えができるのですが、ドラッグ＆ドロップだけで出来てしまうので、ちょっとした誤動作で意図せず移動させてしまう**事故**が発生してしまいます。
今回は、これを防ぐためのスクリプトを紹介します。

## 移動を防ぐ方法
移動はjQuery UIのSortableで実現されています。そのため、該当の要素に対して`sortable`オプションの有効・無効を切り替えることで、移動をさせる・させないを設定することができます。APIのオプションは[こちら](https://api.jqueryui.com/sortable/#option-disabled)です。

## 実際に実装してみた
いろいろと実装の方法はあるのですが、今回は該当の要素の上にポイントが乗った時に、有効・無効の判断をおこなって、切り替えるようにしてみました。

要件としては

* 最初は無効で、必要な場合のみ有効に切り替える
* 有効・無効はメニューで切り替えることが出来るようにする  

の2点を想定します。


まずは移動が有効なのか無効なのかを判断するために、Body要素の中に、現在の状態を保持するフラグを作ります。

```js:スクリプト
//同じ要素を複数回追加しないように、要素の有無をチェックする
if(!$('#SiteSortable').length){
    $('body').append('<input id="SiteSortable" name="SiteSortable" type="hidden" value="0">');
}
```
このBodyの中にHiddenなInputを設置して、フラグや値を保持するというのはプリザンターでは、一般的な方法なので覚えておくと便利です。`$p.UserId`などのスクリプトで使える関数も、実はこのInput群からデータを取っているというものは多いです。

次に、このフラグを反転させて、有効・無効を切り替える術を用意します。これも実装方法はいくらかあるのですが、今回はナビゲーションメニュー内に切替のメニューを用意することにします。

```json:拡張ナビゲーションメニュー
{
	"TargetId": "SettingsMenu",
	"Action": "Append",
	"NavigationMenus": [
		{
			"ContainerId": "ExtendSettingsContainer",
			"MenuId": "ExtendSettingsMenu",
			"Name": "拡張管理",
			"Icon": "ui-icon ui-icon-gear",
			"ChildMenus": [
				{
					"MenuId": "SettingsMenu_DisableSorter",
					"Name": "移動を有効にする",
					"Icon": "ui-icon ui-icon-radio-off",
					"Url": "javascript:$p.naviMenuExtendDisableSorter();",
					"ReferenceTypes": ["Sites"],
					"Actions": ["index"]
				}
			]
		}
	]
}
```
このような感じで、表示されているページがサイトの場合のみに表示されるメニューを追加します。メニューからはJavaScriptの関数をキックします。
```js:スクリプト
$p.naviMenuExtendDisableSorter = function(){
    if($('#SiteSortable').val() == 0){
        $('#SiteSortable').val(1);
        $('#SettingsMenu_DisableSorter a').text('移動を無効にする');
        $p.closeSideMenu();
    }else{
        $('#SiteSortable').val(0);
        $('#SettingsMenu_DisableSorter a').text('移動を有効にする');
        $p.closeSideMenu();
    }
}
```
こちらがキックされる関数です。最初に追加したBobyのInput要素のValueを見て、そのフラグを反転させて、文言を差し替えています。

次は、実際に移動させる要素側の処理を追加します。
```js:スクリプト
$('.nav-sites.sortable').on('mouseenter pointerenter touchstart',function(){
    $(this).sortable($('#SiteSortable').val() == 0 ? 'disable' : 'enable');
});
```
移動することが出来る要素の上にポインタなどが乗ったときに、Body内のInputを見て、移動が有効・無効の判断をおこなって、それをスイッチさせています。

![レコーディング-2024-11-23-135237.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3555395/b5171ae6-914a-ab10-c3ad-c80d2f35c7d3.gif)
これで実装が完了です。

## ソースコードまとめ
これを最後にまとめて、[拡張スクリプト](https://pleasanter.org/ja/manual/extended-script)・[拡張ナビゲーションメニュー](https://pleasanter.org/ja/manual/extended-server-script)として設置します。
```js:DisableSortAndMove.js
if(!$('#SiteSortable').length){
    $('body').append('<input id="SiteSortable" name="SiteSortable" type="hidden" value="0">');
}

$p.naviMenuExtendDisableSorter = function(){
    if($('#SiteSortable').val() == 0){
        $('#SiteSortable').val(1);
        $('#SettingsMenu_DisableSorter a').text('ソートを無効にする');
        $p.closeSideMenu();
    }else{
        $('#SiteSortable').val(0);
        $('#SettingsMenu_DisableSorter a').text('ソートを有効にする');
        $p.closeSideMenu();
    }
}

$('.nav-sites.sortable').on('mouseenter pointerenter touchstart',function(){
    $(this).sortable($('#SiteSortable').val() == 0 ? 'disable' : 'enable');
});

```

```json:ExtendSettingsMenu.json
{
	"TargetId": "SettingsMenu",
	"Action": "Append",
	"NavigationMenus": [
		{
			"ContainerId": "ExtendSettingsContainer",
			"MenuId": "ExtendSettingsMenu",
			"Name": "拡張管理",
			"Icon": "ui-icon ui-icon-gear",
			"ChildMenus": [
				{
					"MenuId": "SettingsMenu_DisableSorter",
					"Name": "ソートを有効にする",
					"Icon": "ui-icon ui-icon-radio-off",
					"Url": "javascript:$p.naviMenuExtendDisableSorter();",
					"ReferenceTypes": ["Sites"],
					"Actions": ["index"]
				}
			]
		}
	]
}
```

## まとめ
サイトの移動を禁止させる仕組みが出来ました。
UI回りの実装はjQuery UIを使っているということ、使い回しをする値はBodyの中にInputをHiddenで入れておくというプリザンターの小技を覚えておくと、いろいろと応用を利かせることが出来ます。