ver2.1.1.4でのエントリー編集の不具合
ver2.1.1.4をご利用の場合
2015/03/17以前にダウンロードしたver2.1.1.4パッケージで 管理画面側でエントリーを編集すると公開日時と掲載期限が入力できない問題が確認されています。 これによりエントリーを保存した日が掲載期限に設定されエントリーがその日に掲載期限になってしまう問題があります。
対応
以下のパッチを適応して下さい。
2015/03/17以前にダウンロードしたver2.1.1.4パッケージで 管理画面側でエントリーを編集すると公開日時と掲載期限が入力できない問題が確認されています。 これによりエントリーを保存した日が掲載期限に設定されエントリーがその日に掲載期限になってしまう問題があります。
以下のパッチを適応して下さい。
2013年7月19日(金)〜20日(土)に師崎荘にて行われましたa-blog cms の合宿で、発表しましたdatepickerのカレンダーに、スケジュールモジュールで登録した内容を反映する方法を紹介します。
お問い合わせフォームなどの入力項目で、チェックボックスの「その他」にチェックをした場合は、その他の内容を必須項目にして、その他にチェックを入れた場合は内容も入れてください。とするケースがあります。 そのやり方を2例紹介します。
htmx は標準で browser history API をサポートしており、属性に hx-push-url="true" を追加することで、これを簡単に利用できます。a-blog cms をバックエンドで使用する際の注意点をいくつか紹介します。
※(例)と書かれている部分は実際には動作しません。
<a>タグでの GETリクエストに対しては、htmx のドキュメントに従い、hx-get属性と hx-push-url属性を追加することを推奨します。
<a href="entry-1.html" hx-get="entry-1.html" hx-push-url="true">記事詳細</a>
POSTリクエストのケースでは、例えば a-blog cms でカスタムフィールド station を選択するための <select> を含むフォームを使用し、結果を表示するテンプレートとして include/htmx/result.html を指定する方法があります。
<form hx-post="" hx-push-url="true" hx-trigger="submit" hx-target="#search-result" hx-ext="ajax-header"> <select name="station"> <option value="Tokyo">Tokyo</option> <option value="Osaka">Osaka</option> <option value="Nagoya">Nagoya</option> </select> <input type="hidden" name="field[]" value="station"> <input type="hidden" name="tpl" value="include/htmx/result.html"> <input type="submit" name="ACMS_POST_2GET" value="検索" > </form> <div id="search-result">この部分に結果が表示されます</div>
a-blog cms の仕様として、<form> の POST 時 name="ACMS_POST_2GET" が送られてくると、検索条件( station = Nagoya )から URL を組み立て、その後リダイレクトされ GET に変換され、以下のような URL でアクセスした結果を取得できます。
https://example.com/htmx/result.html/field/station/Nagoya/
ここで問題が発生します。 上記の URL がブラウザに表示され履歴にも登録されますが、この URL にアクセスすると Ajax で追加される部分的な HTML になってしまいます。この検索結果で履歴に登録されて欲しい URLは、テンプレートファイル include/htmx/result.html のパスが無い以下の URL であって欲しいのです。
https://example.com/field/station/Nagoya/
ここまでで a-blog cms POSTリクエスト後に URL を組み立てる ACMS_POST_2GET の処理が、htmx の hx-push-url と相性が悪い状況を解決する方法を考えてみました。
以下に示す JavaScriptコードを追加することで、hx-push-url 属性の動作を維持しつつ、希望する URL形式を browser history API に正しく渡すことができるようになります。
<script> addEventListener('htmx:beforeHistoryUpdate', function (event) { const proposedUrl = event.detail.history.path; const customUrl = proposedUrl.replace(/\/include\/htmx\/.*\.html/, ''); event.detail.history.path = customUrl; }); </script>
処理としては htmx で History を更新する前の path を取得し、/include/htmx/ 〜 .html 部分を削除するという処理を追加しています。
一般的に a-blog cms の部分的なファイルは include ディレクトリに整理して保存するというルールが定着していますので、htmx で読み込むテンプレートファイルは、その include ディレクトリ内に htmx を用意し、その htmx ディレクトリ内で管理というルールでテーマを構築というルールにする事で上記の JavaScript が有効的に動作させることができます。
現在提供中の標準テーマの一覧ページでは、横に並ぶコンテンツの数(2列、3列、4列)は、テンプレートの CSS クラスの設定によって決まる仕様になっています。 この記事では、タイトルの通り、運営側でもこの設定を変更できるようにする方法について考えてみます。
実際にどのような問題が起こるのかを UTSUWA のテーマ「事業紹介」を例に説明していきます。
当初、打ち合わせ時には「事業紹介」については増えないと聞いており、コンテンツとしては 2つなので、col-6 と Class を設定し、横に2枚並ぶようにテンプレートに書かれていました。
これがサイトを運営している途中で、新しい事業が増えて 3つになる事になりました。3つ目のコンテンツは下に表示され隣は空白となります。
同じページ内の「ブログ」コンテンツは横に4枚並べて表示していますし、横に3枚並べた方がいいと思う人も多い事でしょう。
CMS で運用している中で、よくある話ではないかと思います。 詳細ページについては、ある程度レイアウト調整もできるのですが、沢山の情報を管理可能な CMS では一覧ページについて自由度が低くなる傾向があります。
対策としては、その制御を行っているのはテンプレート上に書かれている 1つの Class という事になるので、それを管理画面側から変更できるようにする事になります。
実は Ver. 2.5.1(2015年8月) から、モジュールの設定に ループ内クラス という設定が追加されています。これを活用する事で、管理画面上から変更できるようになります。 テンプレートに直接書かれている Class を {entry:loop.class} という変数に置き換えるだけです。
次の問題は、この記述内容が運用担当者に理解できるのかというところになります。
acms-col-6 acms-col-sm-4 acms-col-md-3
標準テーマの一覧ページでは、コンテンツの横幅を指定するために acms-col-* というクラスを使用します。 このクラスの数字は 12分割のグリッドシステム に基づいており、1行を12としたときに、各コンテンツが何列分の幅を占めるかを決定します。
つまり、画面のサイズによって自動的に 2列 → 3列 → 4列 とレイアウトが変化します。
クラス名 | デフォルトのブレイクポイント |
---|---|
acms-col- | 全て |
acms-col-sm- | 480px以上〜 |
acms-col-md- | 768px以上〜 |
acms-col-lg- | 1024px以上〜 |
acms-col-xl- | 1440px以上〜 |
この設定では、制作者には理解できたとしても、サイトを運用している担当者には難しいと感じるのではないでしょうか。
モジュールフィールドに、各画面の幅の時のカラム数のカスタムフィールドを作る事で実装が可能になります。
カスタム設定で設定した SELECT( col-base , col-sm , col-md )を元に、表示設定の「ループ内クラス」{entry:loop.class} を自動で書くことができる JavaScript を用意しました。こうすることでテンプレートは標準的な記述のままで済ますことができ、独自に設定したい時にも対応が可能となります。
<select name="col-base" class="acms-admin-form-width-mini"> <option value="acms-col-12" {col-base:selected#acms-col-12}>1カラム</option> <option value="acms-col-6" {col-base:selected#acms-col-6}>2カラム</option> <option value="acms-col-4" {col-base:selected#acms-col-4}>3カラム</option> <option value="acms-col-3" {col-base:selected#acms-col-3}>4カラム</option> </select> <input type="hidden" name="field[]" value="col-base" /> <select name="col-sm" class="acms-admin-form-width-mini"> <option value="" {col-sm:selected#}>未設定 (480px以上)</option> <option value="acms-col-sm-12" {col-sm:selected#acms-col-sm-12}>1カラム</option> <option value="acms-col-sm-6" {col-sm:selected#acms-col-sm-6}>2カラム</option> <option value="acms-col-sm-4" {col-sm:selected#acms-col-sm-4}>3カラム</option> <option value="acms-col-sm-3" {col-sm:selected#acms-col-sm-3}>4カラム</option> </select> <input type="hidden" name="field[]" value="col-sm" /> <select name="col-md" class="acms-admin-form-width-mini"> <option value="" {col-md:selected#}>未設定 (768px以上)</option> <option value="acms-col-md-12" {col-md:selected#acms-col-md-12}>1カラム</option> <option value="acms-col-md-6" {col-md:selected#acms-col-md-6}>2カラム</option> <option value="acms-col-md-4" {col-md:selected#acms-col-md-4}>3カラム</option> <option value="acms-col-md-3" {col-md:selected#acms-col-md-3}>4カラム</option> </select> <input type="hidden" name="field[]" value="col-md" /> <span id="col-etc"></span>
最初に INPUT name="entry_summary_loop_class" の値からカスタム設定の SELECT を調整します。その後、SELECT を修正すると INPUT のテキストを書き換え、INPUT 側をダイレクトに修正すると SELECT 側を書き換えるようにしています。
SELECT の OPTION に無い acms-col-2 などが INPUT に書かれた際には、自動的に SELECT に追加し、acms-col- , acms-col-sm- , acms-col-md- でない Class を書いた時には <span id="col-etc"></span> に表示するようにして追加で Class を書いていることも分かるようにしておきました。
<script> ACMS.addListener("acmsReady", function() { const loopClass = document.querySelector('input[name="entry_summary_loop_class"]'); const colBase = document.querySelector('select[name="col-base"]'); const colSm = document.querySelector('select[name="col-sm"]'); const colMd = document.querySelector('select[name="col-md"]'); const colEtc = document.getElementById("col-etc"); function updateLoopClass() { const values = [colBase.value, colSm.value, colMd.value].filter(value => value !== ""); loopClass.value = values.join(" "); } function ensureOptionExists(select, value) { if (!value || select.querySelector(`option[value="${value}"]`)) return; const existingValues = new Set(Array.from(select.options).map(opt => opt.value)); if (!existingValues.has(value)) { const option = document.createElement("option"); option.value = value; option.textContent = value; option.setAttribute("data-custom", "true"); select.appendChild(option); } } function updateSelectValues() { const values = loopClass.value.split(" ").map(val => val.trim()).filter(val => val !== ""); let baseValue = "", smValue = "", mdValue = ""; let extraValues = []; values.forEach(val => { if (/^acms-col-sm-\d+$/.test(val)) { smValue = val; } else if (/^acms-col-md-\d+$/.test(val)) { mdValue = val; } else if (/^acms-col-\d+$/.test(val)) { baseValue = val; } else { extraValues.push(val); } }); ensureOptionExists(colBase, baseValue); ensureOptionExists(colSm, smValue); ensureOptionExists(colMd, mdValue); colBase.value = baseValue; colSm.value = smValue; colMd.value = mdValue; colEtc.textContent = extraValues.length > 0 ? extraValues.join(", ") : ""; } colBase.addEventListener("change", updateLoopClass); colSm.addEventListener("change", updateLoopClass); colMd.addEventListener("change", updateLoopClass); loopClass.addEventListener("input", updateSelectValues); updateSelectValues(); }); </script>
通常の管理画面のモジュールID の設定でテストを行っていた際には
document.addEventListener("DOMContentLoaded", function () { ... });
のように記述して動作していましたが、モジュールIDの管理画面は表の表示画面の「編集」リンクからモーダル表示が可能です。その際には既に DOMコンテンツがロード済みという事になり、ここで書かれた JavaScript を動作させることができません。
そこで a-blog cms の方で用意されている ACMS.addListener("acmsReady" ... を利用します。詳しくは「組み込みJSのイベントハンドラ」をご覧ください。
ACMS.addListener("acmsReady", function() { ... });
今回の JavaScript には input[name="entry_summary_loop_class" と Entry_Summary のコンフィグのフィールドが書かれていますが、他のモジュールでも使う事を考慮するような実装も考えられます。
このカスタム設定の SELECT 部分 + JavaScript のファイルを別ファイルにし target_input を変数化します。
@include("/admin/parts/loop-class.html", {"target_input": "entry_summary_loop_class"})
/admin/parts/loop-class.html の中の JavaScript 部分を
const loopClass = document.querySelector('input[name="{{target_input}}"]');
のように指定する事で、他のモジュールでも同様に扱いたいことがあれば共用して利用することができるようにできます。
次のバージョン Ver. 3.2 で標準提供するテーマには {entry:loop.class} を活用したテーマにするようにしようと考えています。
また private/config.system.default.yaml に
entry_summary_loop_class : acms-col-6 acms-col-sm-4
のような設定を追加を検討します。どのような初期値がいいかは検討しないといけませんが、こうする事で標準テーマで表示設定にある項目が機能する事になります。