Project

General

Profile

Form List - Suppliers » History » Version 1

Lê Sĩ Quý, 08/29/2025 01:54 PM

1 1 Lê Sĩ Quý
# Form List - Suppliers
2
3
{{TOC}}
4
5
## JSON Schema
6
7
Form List không dùng FormIO vì không có input data, đơn thuần hiển thị danh sách dữ liệu từ spreadsheet. **Nếu dữ liệu chỉ giới hạn truy cập bởi ít người hoặc không cần public thì có thể quản lý data trực tiếp bằng spreadsheet.**
8
9
## HTML Template
10
11
``` html
12
<!DOCTYPE html>
13
<html lang="en">
14
15
<head>
16
    <meta name="viewport"
17
        content="user-scalable=no, initial-scale=1.0001, maximum-scale=1.0001, width=device-width, minimal-ui shrink-to-fit=no">
18
    <meta charset="utf-8" name="viewport" content="width=device-width, initial-scale=1.0" />
19
    <title>Northwind</title>
20
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/awesome-notifications/3.1.0/style.min.css">
21
    <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/w2ui@2.0.0/w2ui-2.0.min.css">
22
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
23
    <script src="https://cdnjs.cloudflare.com/ajax/libs/awesome-notifications/3.1.0/modern.var.min.js"></script>
24
    <script src="https://cdn.jsdelivr.net/npm/localstorage-slim"></script>
25
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
26
    <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>
27
</head>
28
29
<body>
30
    <div id="toolbar"></div>
31
    <div style="position: relative; height: 800px;">
32
        <div id="master" style="display: inline-block; width: 100%; height: 100%;"></div>
33
    </div>
34
35
    <script type="module">
36
        import { w2toolbar, w2grid } from "https://cdn.jsdelivr.net/npm/w2ui@2.0.0/w2ui-2.0.es6.min.js";
37
        var notifier = new AWN({ option: "top-right" });
38
        var sid = ls.get("sid");
39
        var email = ls.get("email");
40
        var $ds = {};
41
        var master;
42
43
        $(function () {
44
            new w2toolbar({
45
                box: "#toolbar",
46
                name: "toolbar",
47
                items: [
48
                    {
49
                        type: "menu", id: "sales", icon: "w2ui-icon-info", text: "Sales",
50
                        items: [
51
                            { id: "customers", text: "Customers", icon: "fa fa-user" },
52
                            { id: "orders", text: "Orders", icon: "fa fa-file-text" },
53
                        ]
54
                    },
55
                    { type: 'break' },
56
                    {
57
                        type: "menu", id: "operations", icon: "w2ui-icon-settings", text: "Operations",
58
                        items: [
59
                            { id: "employees", text: "Employees", icon: "fa fa-address-card" },
60
                            { id: "products", text: "Products", icon: "fa fa-archive" },
61
                            { id: "categories", text: "Product Categories", icon: "fa fa-sitemap" },
62
                            { id: "suppliers", text: "Suppliers", icon: "fa fa-user" },
63
                            { id: "shippers", text: "Shippers", icon: "fa fa-shopping-cart" }
64
                        ]
65
                    },
66
                    { type: "spacer" },
67
                    {
68
                        type: "menu", id: "auth", text: email,
69
                        items: [
70
                            { text: "Logout", id: "logout", icon: "fa fa-sign-out" }
71
                        ]
72
                    }
73
                ],
74
                onClick(event) {
75
                    switch (event.target) {
76
                        case "sales:customers": {
77
                            window.open("<?= base ?>?url=/form/customers", "_top");
78
                            break;
79
                        }
80
                        case "sales:orders": {
81
                            window.open("<?= base ?>?url=/form/orders", "_top");
82
                            break;
83
                        }
84
                        case "operations:employees": {
85
                            window.open("<?= base ?>?url=/form/employees", "_top");
86
                            break;
87
                        }
88
                        case "operations:products": {
89
                            window.open("<?= base ?>?url=/form/products", "_top");
90
                            break;
91
                        }
92
                        case "operations:categories": {
93
                            window.open("<?= base ?>?url=/form/categories", "_top");
94
                            break;
95
                        }
96
                        case "operations:suppliers": {
97
                            window.open("<?= base ?>?url=/form/suppliers", "_top");
98
                            break;
99
                        }
100
                        case "operations:shippers": {
101
                            window.open("<?= base ?>?url=/form/shippers", "_top");
102
                            break;
103
                        }
104
                        case "auth:logout": {
105
                            ls.remove("sid");
106
                            ls.remove("email");
107
                            window.open("<?= base ?>?url=/form/login&callback=<?!= url ?>", "_top");
108
                            break;
109
                        }
110
                    }
111
                }
112
            });
113
            master = new w2grid({
114
                name: "master",
115
                box: "#master",
116
                header: "Suppliers",
117
                show: {
118
                    header: true,
119
                    footer: true,
120
                    toolbar: true,
121
                    //toolbarAdd: true,
122
                    //toolbarEdit: true
123
                },
124
                columns: [
125
                    { field: "recid", hidden: true },
126
                    { field: "CompanyName", text: "Company Name", size: "15%", sortable: true },
127
                    { field: "ContactName", text: "Contact Name", size: "15%", sortable: true },
128
                    { field: "Address", text: "Address", size: "15%" },
129
                    { field: "City", text: "City", size: "15%" },
130
                    { field: "Country", text: "Country", size: "15%" },
131
                    { field: "Phone", text: "Phone", size: "15%" },
132
                ],
133
                searches: [
134
                    { type: "text", field: "CompanyName", label: "Company Name" },
135
                    { type: "text", field: "ContactName", label: "Contact Name" },
136
                    { type: "text", field: "Address", label: "Address" },
137
                    { type: "text", field: "City", label: "City" },
138
                    { type: "text", field: "Country", label: "Country" },
139
                    { type: "text", field: "Phone", label: "Phone" }
140
                ],
141
                onClick(event) {
142
                    let record = this.get(event.detail.recid);
143
144
                    if (record) {
145
                        // Do nothing
146
                    }
147
                },
148
                onAdd: function (event) {
149
                    // Do nothing
150
                },
151
                onEdit: function (event) {
152
                    //let recid = event.detail.recid;
153
                    //recid && window.open("<?= base ?>?url=/form/fast-sale/" + recid, "_blank");
154
                },
155
            });
156
157
            if (sid) {
158
                google.script.run
159
                    .withSuccessHandler(onCheckSidSuccess)
160
                    .withFailureHandler(onInvalidSid)
161
                    .checkSid(sid);
162
            }
163
            else {
164
                onInvalidSid();
165
            }
166
        });
167
168
        function onInvalidSid(err) {
169
            if (err) {
170
                notifier.warning(err.message);
171
            }
172
            else {
173
                let url = "<?!= base ?>?url=/form/login&callback=<?!= url ?>";
174
                notifier.modal(`<b>You need to login to access this page.</b><br><a href="${url}">👉 <b>Login</b></a>`)
175
            }
176
        }
177
178
        function onCheckSidSuccess(valid) {
179
            if (valid) {
180
                google.script.run.withSuccessHandler((ds) => {
181
                    $ds = ds;
182
                    google.script.run.withSuccessHandler((parents => {
183
                        master.clear();
184
                        master.add(parents);
185
                    }))
186
                        .runSql("Suppliers", "label A 'recid' options no_format");
187
                })
188
                    .loadDatasources("<?!= datasource ?>");
189
190
            } else {
191
                onInvalidSid();
192
            }
193
        }
194
    </script>
195
196
</body>
197
198
</html>
199
```
200
201
## Giải thích Code
202
203
Form List Suppliers sử dụng cácthư viện sau:
204
- [jQuery](https://jquery.com/) để khởi tạo grid sau khi page load xong.
205
- [localstorage-slim](https://github.com/digitalfortress-tech/localstorage-slim) để đọc sid & email để bỏ qua bước login nếu trước đó đã login thành công. Trường hợp login không thành công thì sẽ thông báo và điều hướng đến trang login.
206
- [Notyf](https://carlosroso.com/notyf/) hiển thị thông báo.
207
- [w2ui](https://w2ui.com/web/home) cung cấp các thành phần UI cơ bản và nâng cao, ví dụ: toolbar và grid, dialog...
208
- Các thư viện đều được load từ CDN để tối ưu tốc độ, đơn giản hóa HTML Templated. Các bạn cần có kiến thức trong phần yêu cầu để hiểu logic của code JS + templated nhúng trong page. 
209
- Trong page này gọi đến
210
  - Hàm backend **checkSid** sid lưu trữ có khớp với giá trị trên server, nếu đúng thì sẽ sử dụng email trả về, còn sai sẽ điều hướng đến trang login
211
  - Hàm backend **loadDatasources** để tải các dữ liệu tham chiếu phía client, ví dụ danh sách Products
212
  - Hàm backend **runSql** thực hiện query nâng cao trên Datasource chỉ định, ví dụ chỉ hiển thị ra danh sách Orders được tạo bởi Saler đang login
213
214
## Demo
215
- Link: https://script.google.com/macros/s/AKfycbwIjB-hULVZdfCtsXFPg4Af_8WoKx2AFf85KMVwnsO_WkeAXW3zarT6vZNFVfwccz1_sA/exec?url=/form/suppliers
216
- Account: user@northwind.com
217
- Password: user