$npx -y skills add Impertio-Studio/Frappe_Claude_Skill_Package --skill frappe-impl-ui-componentsUse when building custom dialogs, extending List View, creating Page controllers, or adding Kanban/Calendar views and realtime updates. Prevents UI freezes from synchronous calls, broken dialogs from wrong field definitions, and missed socket events. Covers frappe.ui.Dialog, frap
| 1 | # Frappe UI Components & Realtime — Implementation Workflows |
| 2 | |
| 3 | Step-by-step workflows for building client-side UI. For form scripting see `frappe-impl-clientscripts`. For server-side API see `frappe-syntax-serverscripts`. |
| 4 | |
| 5 | **Version**: v14/v15/v16 | **Note**: v15+ uses Bootstrap 5; Dialog API is stable across all versions. |
| 6 | |
| 7 | ## Quick Decision: Which UI Component? |
| 8 | |
| 9 | ``` |
| 10 | WHAT do you need? |
| 11 | ├── Prompt user for input → frappe.prompt (simple) or frappe.ui.Dialog (complex) |
| 12 | ├── Show a message/alert → frappe.msgprint / frappe.show_alert / frappe.throw |
| 13 | ├── Confirm an action → frappe.confirm |
| 14 | ├── Multi-field data entry popup → frappe.ui.Dialog with fields |
| 15 | ├── Select from a list of records → frappe.ui.form.MultiSelectDialog |
| 16 | ├── Full custom page (not a form) → frappe.ui.Page |
| 17 | ├── Customize list columns/colors → frappe.listview_settings |
| 18 | ├── Visual board for workflow → Kanban Board (Select field based) |
| 19 | ├── Date-based record view → Calendar View ({doctype}_calendar.js) |
| 20 | ├── Hierarchical data display → Tree View (is_tree DocType) |
| 21 | ├── Live updates without refresh → frappe.publish_realtime + frappe.realtime.on |
| 22 | ├── Show background job progress → frappe.publish_progress |
| 23 | ├── Scan barcode/QR code → frappe.ui.Scanner |
| 24 | └── Custom cell formatting → formatters in listview_settings or form |
| 25 | ``` |
| 26 | |
| 27 | See `references/decision-tree.md` for the complete decision tree. |
| 28 | |
| 29 | ## Workflow 1: Dialogs (frappe.ui.Dialog) |
| 30 | |
| 31 | ### Simple Dialog |
| 32 | |
| 33 | ```javascript |
| 34 | let d = new frappe.ui.Dialog({ |
| 35 | title: "Enter Details", |
| 36 | fields: [ |
| 37 | { label: "Full Name", fieldname: "full_name", fieldtype: "Data", reqd: 1 }, |
| 38 | { label: "Email", fieldname: "email", fieldtype: "Data", options: "Email" }, |
| 39 | { label: "Role", fieldname: "role", fieldtype: "Select", |
| 40 | options: "Developer\nManager\nDesigner" }, |
| 41 | ], |
| 42 | size: "small", // "small", "large", or "extra-large" |
| 43 | primary_action_label: "Create", |
| 44 | primary_action(values) { |
| 45 | frappe.call({ |
| 46 | method: "myapp.api.create_user", |
| 47 | args: values, |
| 48 | callback(r) { |
| 49 | if (!r.exc) { |
| 50 | frappe.show_alert({ message: "User created", indicator: "green" }); |
| 51 | d.hide(); |
| 52 | } |
| 53 | } |
| 54 | }); |
| 55 | } |
| 56 | }); |
| 57 | d.show(); |
| 58 | ``` |
| 59 | |
| 60 | **Rule**: ALWAYS call `d.hide()` inside the callback, NEVER before the async call completes. |
| 61 | |
| 62 | ### Dialog with Table Field |
| 63 | |
| 64 | ```javascript |
| 65 | let d = new frappe.ui.Dialog({ |
| 66 | title: "Add Items", |
| 67 | fields: [ |
| 68 | { label: "Customer", fieldname: "customer", fieldtype: "Link", |
| 69 | options: "Customer", reqd: 1 }, |
| 70 | { fieldtype: "Section Break" }, |
| 71 | { label: "Items", fieldname: "items", fieldtype: "Table", |
| 72 | in_place_edit: true, reqd: 1, |
| 73 | fields: [ |
| 74 | { fieldname: "item", label: "Item", fieldtype: "Link", |
| 75 | options: "Item", in_list_view: 1, reqd: 1 }, |
| 76 | { fieldname: "qty", label: "Qty", fieldtype: "Int", |
| 77 | in_list_view: 1, default: 1 }, |
| 78 | { fieldname: "rate", label: "Rate", fieldtype: "Currency", |
| 79 | in_list_view: 1 }, |
| 80 | ], |
| 81 | }, |
| 82 | ], |
| 83 | primary_action_label: "Submit", |
| 84 | primary_action(values) { |
| 85 | console.log(values); // { customer: "...", items: [{item, qty, rate}] } |
| 86 | d.hide(); |
| 87 | } |
| 88 | }); |
| 89 | d.show(); |
| 90 | ``` |
| 91 | |
| 92 | **Rule**: ALWAYS set `in_list_view: 1` on table child fields you want visible. Fields without it are hidden in the grid. |
| 93 | |
| 94 | ### Multi-Step Dialog |
| 95 | |
| 96 | ```javascript |
| 97 | let d = new frappe.ui.Dialog({ |
| 98 | title: "Setup Wizard", |
| 99 | fields: [ |
| 100 | // Page 1 |
| 101 | { fieldtype: "Section Break", label: "Step 1: Basic Info", |
| 102 | collapsible: 0 }, |
| 103 | { label: "Name", fieldname: "name", fieldtype: "Data", reqd: 1 }, |
| 104 | // Page 2 |
| 105 | { fieldtype: "Section Break", label: "Step 2: Configuration", |
| 106 | collapsible: 0 }, |
| 107 | { label: "Option", fieldname: "option", fieldtype: "Select", |
| 108 | options: "A\nB\nC" }, |
| 109 | ], |
| 110 | primary_action_label: "Finish", |
| 111 | primary_action(values) { |
| 112 | d.hide(); |
| 113 | } |
| 114 | }); |
| 115 | d.show(); |
| 116 | ``` |
| 117 | |
| 118 | ### Key Dialog Methods |
| 119 | |
| 120 | | Metho |