frappe-impl-ui-components について
FrappeのUIを開発する人向けに、クライアント側コンポーネントとリアルタイム更新の実装を案内するスキルです。ダイアログ、ページ、リストビュー、イベント購読の手順とAPIをまとめています。
主な機能
- 01ダイアログとフォーム入力のパターン
- 02メッセージ、通知、確認操作
- 03リストビューのカスタマイズ
- 04カスタムページとカレンダー
- 05カンバンボードの設定
- 06リアルタイムイベントとスキャナーAPI
活用シーン
- 01ダイアログでの入力収集
- 02DocTypeリストビューのカスタマイズ
- 03カスタムアプリページの構築
- 04リアルタイムイベントによる画面更新
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ impertio-studio/ frappe_claude_skill_package --skill frappe-impl-ui-componentsインストール後、Claude Code で/frappe-impl-ui-componentsを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/.gitkeepanti-patterns.mdcontrols-api.mddecision-tree.mdexamples.mdtree-view.mdworkflows.md詳細
Frappe UI コンポーネントとリアルタイム — 実装ワークフロー
クライアント側の UI を構築するためのステップバイステップのワークフローです。フォームのスクリプトについては frappe-impl-clientscripts を参照してください。サーバー側 API については frappe-syntax-serverscripts を参照してください。
バージョン:v14/v15/v16 | 注:v15 以降は Bootstrap 5 を使用します。Dialog API はすべてのバージョンで安定しています。
クイック判断:どの UI コンポーネントを使うか?
WHAT do you need?
├── Prompt user for input → frappe.prompt (simple) or frappe.ui.Dialog (complex)
├── Show a message/alert → frappe.msgprint / frappe.show_alert / frappe.throw
├── Confirm an action → frappe.confirm
├── Multi-field data entry popup → frappe.ui.Dialog with fields
├── Select from a list of records → frappe.ui.form.MultiSelectDialog
├── Full custom page (not a form) → frappe.ui.Page
├── Customize list columns/colors → frappe.listview_settings
├── Visual board for workflow → Kanban Board (Select field based)
├── Date-based record view → Calendar View ({doctype}_calendar.js)
├── Hierarchical data display → Tree View (is_tree DocType)
├── Live updates without refresh → frappe.publish_realtime + frappe.realtime.on
├── Show background job progress → frappe.publish_progress
├── Scan barcode/QR code → frappe.ui.Scanner
└── Custom cell formatting → formatters in listview_settings or form
完全な判断ツリーについては references/decision-tree.md を参照してください。
ワークフロー 1:ダイアログ(frappe.ui.Dialog)
シンプルなダイアログ
let d = new frappe.ui.Dialog({
title: "Enter Details",
fields: [
{ label: "Full Name", fieldname: "full_name", fieldtype: "Data", reqd: 1 },
{ label: "Email", fieldname: "email", fieldtype: "Data", options: "Email" },
{ label: "Role", fieldname: "role", fieldtype: "Select",
options: "Developer\nManager\nDesigner" },
],
size: "small", // "small", "large", or "extra-large"
primary_action_label: "Create",
primary_action(values) {
frappe.call({
method: "myapp.api.create_user",
args: values,
callback(r) {
if (!r.exc) {
frappe.show_alert({ message: "User created", indicator: "green" });
d.hide();
}
}
});
}
});
d.show();
ルール:非同期呼び出しが完了する前ではなく、必ずコールバック内で d.hide() を呼び出してください。
テーブルフィールド付きダイアログ
let d = new frappe.ui.Dialog({
title: "Add Items",
fields: [
{ label: "Customer", fieldname: "customer", fieldtype: "Link",
options: "Customer", reqd: 1 },
{ fieldtype: "Section Break" },
{ label: "Items", fieldname: "items", fieldtype: "Table",
in_place_edit: true, reqd: 1,
fields: [
{ fieldname: "item", label: "Item", fieldtype: "Link",
options: "Item", in_list_view: 1, reqd: 1 },
{ fieldname: "qty", label: "Qty", fieldtype: "Int",
in_list_view: 1, default: 1 },
{ fieldname: "rate", label: "Rate", fieldtype: "Currency",
in_list_view: 1 },
],
},
],
primary_action_label: "Submit",
primary_action(values) {
console.log(values); // { customer: "...", items: [{item, qty, rate}] }
d.hide();
}
});
d.show();
ルール:表示するテーブルの子フィールドには、必ず in_list_view: 1 を設定してください。設定されていないフィールドはグリッド内で非表示になります。
複数ステップのダイアログ
let d = new frappe.ui.Dialog({
title: "Setup Wizard",
fields: [
// Page 1
{ fieldtype: "Section Break", label: "Step 1: Basic Info",
collapsible: 0 },
{ label: "Name", fieldname: "name", fieldtype: "Data", reqd: 1 },
// Page 2
{ fieldtype: "Section Break", label: "Step 2: Configuration",
collapsible: 0 },
{ label: "Option", fieldname: "option", fieldtype: "Select",
options: "A\nB\nC" },
],
primary_action_label: "Finish",
primary_action(values) {
d.hide();
}
});
d.show();
主なダイアログメソッド
| メソッド | 用途 |
|---|---|
d.show() | ダイアログを表示します |
d.hide() | ダイアログを閉じます |
d.get_values() | すべてのフィールド値をオブジェクトとして取得します |
d.set_values({field: val}) | フィールド値を設定します |
d.get_field("name") | 特定のフィールドコントロールを取得します |
d.set_df_property("name", "hidden", 1) | フィールドの表示/非表示を動的に切り替えます |
d.disable_primary_action() | 送信ボタンをグレーアウトします |
d.enable_primary_action() | 送信ボタンを再度有効にします |
ワークフロー 2:メッセージとアラート
frappe.msgprint:モーダルメッセージ
// Simple message
frappe.msgprint("Record saved successfully");
// With options
frappe.msgprint({
title: "Warning",
message: "This action cannot be undone",
indicator: "orange", // green, blue, orange, red
primary_action: {
label: "Proceed",
action() { do_something(); }
}
});
// List of messages
frappe.msgprint({
title: "Validation Errors",
message: "Please fix the following:",
as_list: true,
indicator: "red",
});
frappe.throw:例外を伴うエラー
// Client-side: shows msgprint and stops execution
frappe.throw("Amount cannot be negative");
# Server-side: raises ValidationError, shown as red msgprint
frappe.throw("Amount cannot be negative")
frappe.throw("Not Permitted", frappe.PermissionError) # specific exception
ルール:検証エラーには必ず frappe.throw を使用してください。エラーに frappe.msgprint は絶対に使用しないでください。実行が停止しないためです。
frappe.confirm:はい/いいえのダイアログ
frappe.confirm(
"Are you sure you want to delete this record?",
() => { /* Yes callback */ delete_record(); },
() => { /* No callback (optional) */ }
);
frappe.prompt:単一フィールドへの簡易入力
frappe.prompt(
{ label: "Reason", fieldname: "reason", fieldtype: "Small Text", reqd: 1 },
(values) => {
console.log(values.reason);
},
"Enter Reason", // dialog title
"Submit" // primary action label
);
// Multiple fields
frappe.prompt([
{ label: "Reason", fieldname: "reason", fieldtype: "Small Text", reqd: 1 },
{ label: "Priority", fieldname: "priority", fieldtype: "Select",
options: "Low\nMedium\nHigh" },
], (values) => { console.log(values); }, "Details");
frappe.show_alert:トースト通知
// Simple
frappe.show_alert("Saved");
// With indicator and duration
frappe.show_alert({ message: "Email sent", indicator: "green" }, 5);
// Duration in seconds (default: 7)
ルール:ブロックしない成功メッセージには frappe.show_alert を使用してください。ユーザーに必ず確認してもらう必要がある場合は frappe.msgprint を使用してください。
ワークフロー 3:リストビューのカスタマイズ
DocType ディレクトリに {doctype_name}_list.js を作成します。
// myapp/doctype/task/task_list.js
frappe.listview_settings["Task"] = {
// Extra fields to fetch (beyond standard)
add_fields: ["priority", "status", "assigned_to"],
// Hide the name column
hide_name_column: true,
// Row indicator (colored dot)
get_indicator(doc) {
// MUST return [label, color, comma-separated-filter]
if (doc.status === "Completed") return ["Completed", "green", "status,=,Completed"];
if (doc.status === "Overdue") return ["Overdue", "red", "status,=,Overdue"];
return ["Open", "orange", "status,=,Open"];
},
// Custom column formatters
formatters: {
priority(val) {
const colors = { High: "red", Medium: "orange", Low: "green" };
return `<span class="indicator-pill ${colors[val] || ""}">${val}</span>`;
}
},
// Row action button
button: {
show(doc) { return doc.status === "Open"; },
get_label() { return __("Complete"); },
get_description(doc) { return __("Mark {0} as complete", [doc.name]); },
action(doc) {
frappe.xcall("myapp.api.complete_task", { task: doc.name })
.then(() => cur_list.refresh());
}
},
// Lifecycle hooks
onload(listview) {
listview.page.add_inner_button("Export", () => export_tasks());
},
refresh(listview) {
// Runs on every list refresh
},
// Default filters
filters: [["status", "!=", "Cancelled"]],
};
ルール:get_indicator から必ず 3 要素の配列を返してください。3 つ目の要素は、クリックしてフィルターするためのフィルター文字列です。
ワークフロー 4:カスタムページ(frappe.ui.Page)
ステップ 1:hooks.py に登録する
# hooks.py
page_js = { "my-custom-page": "public/js/my_custom_page.js" }
ステップ 2:ページ定義を作成する
// myapp/myapp/my_custom_page/my_custom_page.js
frappe.pages["my-custom-page"].on_page_load = function(wrapper) {
let page = frappe.ui.make_app_page({
parent: wrapper,
title: "My Custom Page",
single_column: true,
});
// Primary action button
page.set_primary_action("Create", () => create_new(), "octicon octicon-plus");
// Secondary action
page.set_secondary_action("Refresh", () => refresh_data());
// Dropdown menu
page.add_menu_item("Export CSV", () => export_csv());
page.add_menu_item("Settings", () => frappe.set_route("Form", "My Settings"));
// Inner toolbar buttons
page.add_inner_button("Update All", () => update_all());
page.add_inner_button("New Post", () => new_post(), "Make"); // grouped
// Toolbar filter fields
let status_field = page.add_field({
label: "Status",
fieldtype: "Select",
fieldname: "status",
options: ["", "Open", "Closed", "Cancelled"],
change() { refresh_data(); }
});
// Status indicator
page.set_indicator("Active", "green");
// Content area
$(page.body).html(`<div class="my-page-content"></div>`);
// Load initial data
refresh_data();
};
主なページメソッド
| メソッド | 用途 |
|---|---|
page.set_title(title) | ページの見出しを設定します |
page.set_indicator(label, color) | ステータスバッジ(緑/赤/オレンジ/青) |
page.set_primary_action(label, fn, icon) | メインのアクションボタンを設定します |
page.set_secondary_action(label, fn) | セカンダリボタンを設定します |
page.add_menu_item(label, fn) | ドロップダウンメニュー項目を追加します |
page.add_inner_button(label, fn, group) | ツールバーにボタンを追加します(グループは任意) |
page.add_field({...}) | ツールバーにフィルター/入力フィールドを追加します |
page.get_form_values() | ツールバーのすべてのフィールド値を取得します |
page.clear_fields() | ツールバーのフィールドをすべて削除します |
page.clear_primary_action() | プライマリボタンを削除します |
ワークフロー 5:カレンダービュー
DocType ディレクトリに {doctype}_calendar.js を作成します。
// myapp/doctype/event/event_calendar.js
frappe.views.calendar["Event"] = {
field_map: {
start: "starts_on",
end: "ends_on",
id: "name",
title: "subject",
allDay: "all_day",
color: "color",
},
gantt: true, // Enable Gantt view toggle
get_events_method: "myapp.api.get_events", // Optional custom event source
filters: [
{ fieldtype: "Link", fieldname: "event_type", label: "Type",
options: "Event Type" }
],
};
ルール:start と end は、DocType の実際の Date または Datetime フィールドに必ずマッピングしてください。マッピングがないと、カレンダーが空白になります。
ワークフロー 6:カンバンボード
カンバンボードは、Select フィールドを持つ任意の DocType で使用できます。コードは不要です。
- リストビューを開く → サイドバー → カンバン → 新しいカンバンボード
- Select フィールド(例:
status)を選択します。オプションが列になります - 保存すると、カードを列間でドラッグできます
ルール:Select フィールドのない DocType には、カンバンボードを絶対に作成しないでください。プログラムによる設定については references/examples.md を参照してください。
ワークフロー 7:リアルタイム更新(Socket.IO)
サーバー:イベントを公開する
# Broadcast to all users
frappe.publish_realtime("task_updated", {"task": task.name, "status": "Done"})
# Send to specific user
frappe.publish_realtime("notification", {"msg": "Your report is ready"},
user="[email protected]")
# Send to users viewing a specific document
frappe.publish_realtime("doc_updated", {"field": "status"},
doctype="Task", docname="TASK-001")
# ALWAYS use after_commit=True in document events
frappe.publish_realtime("order_created", message, after_commit=True)
クライアント:イベントを購読する
// Listen for events
frappe.realtime.on("task_updated", (data) => {
frappe.show_alert({ message: `Task ${data.task}: ${data.status}`, indicator: "green" });
cur_list && cur_list.refresh();
});
// Stop listening
frappe.realtime.off("task_updated");
進捗インジケーター
# Server: publish progress during long operations
def process_items(items):
total = len(items)
for i, item in enumerate(items):
process(item)
frappe.publish_progress(
percent=(i + 1) / total * 100,
title="Processing Items",
description=f"Processing {item.name}",
)
ルール:ドキュメントイベントから公開する場合は、必ず after_commit=True を使用してください。設定しないと、トランザクションがロールバックされてもイベントが発行されます。
リアルタイムルーム
| ルーム | 対象ユーザー | 用途 |
|---|---|---|
| (default) | すべてのシステムユーザー | グローバル通知 |
user:{email} | 単一ユーザー | 個人向けアラート |
doctype:{dt} | リストを表示しているユーザー | リスト更新のトリガー |
doc:{dt}/{name} | ドキュメントを表示しているユーザー | ドキュメント変更アラート |
website | ゲストを含むすべてのユーザー | 公開アナウンス |
ワークフロー 8:スキャナー API(バーコード/QR)
// Single scan — closes after first scan
new frappe.ui.Scanner({
dialog: true, multiple: false,
on_scan(data) {
frappe.set_route("Form", "Item", data.decodedText);
}
});
// Continuous scanning — stays open for multiple scans
let scanner = new frappe.ui.Scanner({
dialog: true, multiple: true,
on_scan(data) { add_item_to_list(data.decodedText); }
});
// Stop: scanner.stop_scan() or close the dialog
ルール:単一アイテムを検索する場合は、必ず multiple: false を設定してください。バーコードを使った Stock-Entry の完全な例については、references/examples.md を参照してください。
アンチパターンのまとめ
| アンチパターン | 正しい方法 |
|---|---|
エラーに frappe.msgprint を使用する | frappe.throw を使用します。実行が停止します |
| 非同期処理が完了する前にダイアログを非表示にする | コールバック内で非表示にします:callback() { d.hide(); } |
| ダイアログ内で同期 API 呼び出しを行う | 必ず frappe.call / frappe.xcall(非同期)を使用します |
テーブルフィールドで in_list_view が未設定 | 表示する列に in_list_view: 1 を設定します |
after_commit なしで publish_realtime を使用する | ドキュメントイベントでは必ず after_commit=True を使用します |
| Select フィールドのない DocType で Kanban を使用する | Kanban では列に Select フィールドが必要です |
| カレンダーの field_map で start/end が未設定 | 必ず start と end の両方のフィールドをマッピングします |
| get_indicator から 2 要素の配列を返す | 必ず 3 要素を返します:[ラベル、色、フィルター] |
参考ファイル
references/controls-api.md—frappe.ui.form.make_control()によるスタンドアロンコントロール、コントロールタイプの完全なリファレンス、コントロールのメソッドとイベントreferences/tree-view.md— Tree DocType の設定、frappe.views.TreeViewAPI、frappe.ui.Treeの低レベル API、ツリーノードの操作references/workflows.md— 拡張ワークフローの手順references/examples.md— 完全なコード例references/decision-tree.md— UI コンポーネントの完全な判断ツリーreferences/anti-patterns.md— コード例を含む、詳しいアンチパターン
関連項目
frappe-impl-clientscripts— フォームレベルのクライアントスクリプトfrappe-syntax-clientscripts— クライアントサイド API の構文リファレンスfrappe-impl-hooks— ページとルートのフック登録