Visualforceページでの戻るボタンは、History APIで対策!ブラウザの履歴情報を操作する。
こんにちは!
Visualforceページの開発では、reRenderでページの一部を動的に書き換える方法を取ることが多いと思います。 コンポーネントのIdを指定するだけなので、手軽で便利ですよね。 ただ、便利なreRenderにも落とし穴があります。 今回は以下の流れで、現象とその対策を紹介していきたいと思います。
- ・reRenderを使用したVisualforceページは多い
- ・reRenderがもたらすユーザビリティの低下
- ・History APIを使った対策
- ・実装例:History API・Javascript
- ・実装例:Visualforce・Apex
- ・まとめ・サンプルコード
reRenderを使用したVisualforceページは多い
下記は、reRenderを利用した典型的なVisualforce・Apexの例です。
<apex:page id=“SamplePage” controller="SamplePageController">
<apex:form>
<apex:pageBlock title="SamplePage">
<apex:pageBlockButtons>
<apex:commandButton value="Click" action="{!doClick}" reRender="SampleSection"/>
</apex:pageBlockButtons>
<apex:pageBlockSection id="SampleSection">
<apex:outputText value="{!Counter}"/>
</apex:pageBlockSection>
</apex:pageBlock>
</apex:form>
</apex:page>
public class SamplePageController {
public Integer Counter {get; set;}
public SamplePageController () {
this.Counter = 0;
}
public void doClick () {
this.Counter += 1;
}
}
SamplePageには、押されるとCounterを+1するClickボタンが存在しています。 また、ClickボタンのreRenderに、Counterを表示しているコンポーネントを指定しています。 そのため、Clickボタンが押されると、指定の領域だけ書き換えられます。 SamplePageはごく一般的なVisualforceページですが、一方である違和感を生むことになります。
reRenderがもたらすユーザビリティの低下
さて、実際にユーザがSamplePageを利用する場合、以下の手順が想定されます。
- 1.Salesforceにログインして、ホーム画面が表示される。
- 2.SamplePageに遷移する。
- 3.Counterが0であることを確認する。
- 4.Clickボタンを押して、Counterが1になることを確認する。
一連の操作を行った時に、ふと、 「Counterを0に戻したい」と思った場合。 当然、ブラウザの戻るボタンを押すと、ホーム画面まで戻ってしまいます。 ホーム画面からSamplePageに遷移してreRenderで書き換えていたので、 単純に戻るボタンを押しても元に戻らないことは、開発者であればすぐに理解できるかと思います。 しかし、ユーザの「0に戻したい」という意図からは外れた動作となります。
SamplePageは単純な例ですが、検索画面で同様の実装を行うとこうなります。
- 1.Saleaforceにログインして、ホーム画面が表示される。
- 2.検索画面に遷移する。
- 3.検索を実行して、検索結果を表示する。
- 4.検索結果から1件選択し、詳細を表示する。
この場合も、 「検索結果に戻したい」と思って戻るボタンを押すと、ホーム画面まで戻ってしまうので非常に不便です。
History APIを使った対策
今回は、この問題に対して、HTML5のHistory APIを利用した解決策について紹介します。 History APIは、ブラウザの履歴情報をスクリプトで操作するために以前から用意されていましたが、 HTML5からは、任意の履歴の追加などいくつかの新しい機能が追加されています。 つまり、履歴情報を操作して戻るボタンの挙動を制御することができるようになっています。
実装例:History API・Javascript
SamplePageの例に戻って、問題を確認します。 SamplePageでは、 Clickボタンを押した際にページ遷移が行われていないため、戻るボタンが機能していないことが問題でした。 そこで、Clickボタンが押された時に呼び出す、履歴を追加する関数を用意しておきます。
function pushDoClickState() {
history.pushState(null,null,'/result');
}
履歴の追加は、history.pushState()で行います。 今回は、URLのみ指定しておきます。 URLは相対パスでも絶対パスでも指定可能。URLを変えたくない場合はURLも未指定にします。 これで、Clickボタンが押された時に履歴を残すようになり、 Clickボタンの後に戻るボタンを押しても、ホームまで戻ることは無くなりました。
実装例:Visualforce・Apex
履歴の対応を行ったことで、戻るボタンでホーム画面まで戻ってしまうことは無くなりましたが、 書き換えていたCounterはカウントアップされたままです。 そこで、戻るボタンのイベントを監視し、イベントが発生した時の処理を作成します。
window.addEventListener('popstate', function (e) {
actFuncHistoryBack();
},false);
戻るボタンのイベントは、popstateです。 更に、呼び出す処理として、<apex:actionFunction>とApexのメソッドを用意しましょう。
<apex:actionFunction name="actFuncHistoryBack" action="{!doHistoryBack}" reRender="SampleSection" />
public void doHistoryBack() {
this.Counter -= 1;
}
これで、戻るボタンが押された時にCounterを1減らすという処理が作成できました。
まとめ・サンプルコード
このように、History APIを利用した実装を行うことで、 Clickボタンを押すと履歴を残しながらCounterを1増やし、 戻るボタンを押すと1つ前の履歴に戻りながらCounterを1減らす。 という処理を作成することができました。 戻る際の処理を変えることで、検索画面などでの再検索なども対応可能になります。 これによりユーザの想定通りの戻るボタンの挙動が実現でき、 よりユーザビリティの高いVisualforceページを作り上げることが可能です。 実際に運用が始まってから「戻るボタンが効かない!」という声が上がる前に、配慮しておきたいですね。 最後に、動作確認用のVisualforceページとApexクラスを載せておきます。
<apex:page id="SamplePage" controller="SamplePageController">
<apex:form >
<apex:pageBlock title="SamplePage">
<apex:pageBlockButtons >
<apex:commandButton value="Click" action="{!doClick}" reRender="SampleSection" onComplete="pushDoClickState();" />
</apex:pageBlockButtons>
<apex:pageBlockSection id="SampleSection">
<apex:outputText value="{!Counter}"/>
</apex:pageBlockSection>
</apex:pageBlock>
<apex:actionFunction name="actFuncHistoryBack" action="{!doHistoryBack}" reRender="SampleSection" />
</apex:form>
<script>
window.addEventListener('popstate', function (e) {
actFuncHistoryBack();
},false);
function pushDoClickState() {
// 履歴追加
history.pushState(null,null,'/result');
}
</script>
</apex:page>
public class SamplePageController {
public Integer Counter {get; set;}
public SamplePageController () {
this.Counter = 0;
}
public void doClick () {
this.Counter += 1;
}
public void doHistoryBack() {
this.Counter -= 1;
}
}