Project

General

Profile

Form List - Shippers » History » Version 1

Lê Sĩ Quý, 08/29/2025 10:52 AM

1 1 Lê Sĩ Quý
# Form List - Shippers
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: "Shippers",
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: "70%", sortable: true },
127
                    { field: "Phone", text: "Phone", size: "30%" },
128
                ],
129
                searches: [
130
                    { type: "text", field: "CompanyName", label: "Company Name" },
131
                    { type: "text", field: "Phone", label: "Phone" }
132
                ],
133
                onClick(event) {
134
                    let record = this.get(event.detail.recid);
135
136
                    if (record) {
137
                        // Do nothing
138
                    }
139
                },
140
                onAdd: function (event) {
141
                    // Do nothing
142
                },
143
                onEdit: function (event) {
144
                    //let recid = event.detail.recid;
145
                    //recid && window.open("<?= base ?>?url=/form/fast-sale/" + recid, "_blank");
146
                },
147
            });
148
149
            if (sid) {
150
                google.script.run
151
                    .withSuccessHandler(onCheckSidSuccess)
152
                    .withFailureHandler(onInvalidSid)
153
                    .checkSid(sid);
154
            }
155
            else {
156
                onInvalidSid();
157
            }
158
        });
159
160
        function onInvalidSid(err) {
161
            if (err) {
162
                notifier.warning(err.message);
163
            }
164
            else {
165
                let url = "<?!= base ?>?url=/form/login&callback=<?!= url ?>";
166
                notifier.modal(`<b>You need to login to access this page.</b><br><a href="${url}">👉 <b>Login</b></a>`)
167
            }
168
        }
169
170
        function onCheckSidSuccess(valid) {
171
            if (valid) {
172
                google.script.run.withSuccessHandler((ds) => {
173
                    $ds = ds;
174
                    google.script.run.withSuccessHandler((parents => {
175
                        master.clear();
176
                        master.add(parents);
177
                    }))
178
                        .runSql("Shippers", "label A 'recid' options no_format");
179
                })
180
                    .loadDatasources("<?!= datasource ?>");
181
182
            } else {
183
                onInvalidSid();
184
            }
185
        }
186
    </script>
187
188
</body>
189
190
</html>
191
```
192
193
## Giải thích Code
194
195
Form List Shippers sử dụng cácthư viện sau:
196
- [jQuery](https://jquery.com/) để khởi tạo grid sau khi page load xong.
197
- [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.
198
- [Notyf](https://carlosroso.com/notyf/) hiển thị thông báo.
199
- [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...
200
- 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. 
201
- Trong page này gọi đến
202
  - 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
203
  - Hàm backend **loadDatasources** để tải các dữ liệu tham chiếu phía client, ví dụ danh sách Products
204
  - 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
205
206
## Demo
207
- Link: https://script.google.com/macros/s/AKfycbwIjB-hULVZdfCtsXFPg4Af_8WoKx2AFf85KMVwnsO_WkeAXW3zarT6vZNFVfwccz1_sA/exec?url=/form/products
208
- Account: user@northwind.com
209
- Password: user