@agung_dhewe/webapps 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/LICENSE +28 -0
  2. package/README.md +2 -0
  3. package/jsconfig.json +10 -0
  4. package/libs/fgta5js-dist/fgta5js-v1.8.3.min.css +2 -0
  5. package/libs/fgta5js-dist/fgta5js-v1.8.3.min.js +11 -0
  6. package/libs/fgta5js-dist/fgta5js-v1.8.3.min.js.map +1 -0
  7. package/libs/fgta5js-dist/fonts/karla-italic-latin-ext.woff2 +0 -0
  8. package/libs/fgta5js-dist/fonts/karla-italic-latin.woff2 +0 -0
  9. package/libs/fgta5js-dist/fonts/karla-normal-latin-ext.woff2 +0 -0
  10. package/libs/fgta5js-dist/fonts/karla-normal-latin.woff2 +0 -0
  11. package/libs/fgta5js-dist/fonts/karla.css +142 -0
  12. package/libs/webmodule/module-edit.css +163 -0
  13. package/libs/webmodule/module-footer.css +22 -0
  14. package/libs/webmodule/module-list.css +25 -0
  15. package/libs/webmodule/module.css +52 -0
  16. package/libs/webmodule/module.js +195 -0
  17. package/libs/webmodule/pagehelper.mjs +45 -0
  18. package/modules/generator/appgen-components.mjs +142 -0
  19. package/modules/generator/appgen-icons.mjs +6 -0
  20. package/modules/generator/appgen-io.mjs +784 -0
  21. package/modules/generator/appgen-ui-search.mjs +173 -0
  22. package/modules/generator/appgen-ui-unique.mjs +153 -0
  23. package/modules/generator/appgen-ui.mjs +1181 -0
  24. package/modules/generator/generator-context.mjs +18 -0
  25. package/modules/generator/generator-designtemplate.html +1508 -0
  26. package/modules/generator/generator-ext.html +0 -0
  27. package/modules/generator/generator-ext.mjs +3 -0
  28. package/modules/generator/generator.css +642 -0
  29. package/modules/generator/generator.mjs +195 -0
  30. package/modules/generator/generator.png +0 -0
  31. package/modules/generator/generatorEdit.html +185 -0
  32. package/modules/generator/generatorEdit.mjs +238 -0
  33. package/modules/generator/generatorList.html +32 -0
  34. package/modules/generator/generatorList.mjs +243 -0
  35. package/modules/login/login.css +11 -0
  36. package/modules/login/login.html +12 -0
  37. package/modules/login/login.mjs +111 -0
  38. package/package.json +46 -0
  39. package/percobaan/simmpan-ke-minio.js +24 -0
  40. package/src/api.js +80 -0
  41. package/src/apis/generator.api.js +226 -0
  42. package/src/apis/login.api.js +109 -0
  43. package/src/bucket.js +24 -0
  44. package/src/context.js +26 -0
  45. package/src/datalog.sql +22 -0
  46. package/src/datarecords.js +0 -0
  47. package/src/db.js +61 -0
  48. package/src/generator/createApiExtenderModule.js +54 -0
  49. package/src/generator/createApiModule.js +218 -0
  50. package/src/generator/createIcon.js +62 -0
  51. package/src/generator/createInfoAboutExtender.js +42 -0
  52. package/src/generator/createInfoLogs.js +41 -0
  53. package/src/generator/createInfoRecordExtender.js +41 -0
  54. package/src/generator/createModuleContext.js +48 -0
  55. package/src/generator/createModuleDetilEditHtml.js +110 -0
  56. package/src/generator/createModuleDetilEditMjs.js +172 -0
  57. package/src/generator/createModuleDetilListHtml.js +146 -0
  58. package/src/generator/createModuleDetilListMjs.js +73 -0
  59. package/src/generator/createModuleEjs.js +51 -0
  60. package/src/generator/createModuleExtenderHtml.js +43 -0
  61. package/src/generator/createModuleExtenderMjs.js +43 -0
  62. package/src/generator/createModuleHeaderEditHtml.js +148 -0
  63. package/src/generator/createModuleHeaderEditMjs.js +197 -0
  64. package/src/generator/createModuleHeaderListHtml.js +144 -0
  65. package/src/generator/createModuleHeaderListMjs.js +67 -0
  66. package/src/generator/createModuleMjs.js +67 -0
  67. package/src/generator/createModuleRollup.js +42 -0
  68. package/src/generator/createProgramData.js +96 -0
  69. package/src/generator/createTable.js +156 -0
  70. package/src/generator/ddl.js +475 -0
  71. package/src/generator/helper.js +149 -0
  72. package/src/generator/templates/__rollup-module.ejs +90 -0
  73. package/src/generator/templates/api-extender-module.js.ejs +0 -0
  74. package/src/generator/templates/api-module.js.ejs +818 -0
  75. package/src/generator/templates/module-context.ejs +16 -0
  76. package/src/generator/templates/module-ext-about.ejs +1 -0
  77. package/src/generator/templates/module-ext-record.ejs +1 -0
  78. package/src/generator/templates/module-ext.html.ejs +3 -0
  79. package/src/generator/templates/module-ext.mjs.ejs +21 -0
  80. package/src/generator/templates/module-logs.ejs +14 -0
  81. package/src/generator/templates/module.ejs.ejs +48 -0
  82. package/src/generator/templates/module.mjs.ejs +256 -0
  83. package/src/generator/templates/moduleDetilEdit.html.ejs +34 -0
  84. package/src/generator/templates/moduleDetilEdit.mjs.ejs +792 -0
  85. package/src/generator/templates/moduleDetilList.html.ejs +26 -0
  86. package/src/generator/templates/moduleDetilList.mjs.ejs +319 -0
  87. package/src/generator/templates/moduleHeaderEdit.html.ejs +53 -0
  88. package/src/generator/templates/moduleHeaderEdit.mjs.ejs +807 -0
  89. package/src/generator/templates/moduleHeaderList.html.ejs +24 -0
  90. package/src/generator/templates/moduleHeaderList.mjs.ejs +308 -0
  91. package/src/generator/templates/sqlAddField.ejs +3 -0
  92. package/src/generator/templates/sqlAddForeignKey.ejs +12 -0
  93. package/src/generator/templates/sqlAddUniqueIndex.ejs +4 -0
  94. package/src/generator/templates/sqlCreateTable.ejs +9 -0
  95. package/src/generator/templates/sqlDropForeignKey.ejs +3 -0
  96. package/src/generator/templates/sqlDropUniqueIndex.ejs +4 -0
  97. package/src/generator/templates/sqlModifyField.ejs +6 -0
  98. package/src/generator/trygenerate.js +83 -0
  99. package/src/generator/worker.js +389 -0
  100. package/src/helper.js +82 -0
  101. package/src/logger.js +39 -0
  102. package/src/router.js +84 -0
  103. package/src/routers/defaultLoginApi.js +29 -0
  104. package/src/routers/defaultLoginAsset.js +18 -0
  105. package/src/routers/defaultLoginPage.js +36 -0
  106. package/src/routers/defaultRootIndex.js +16 -0
  107. package/src/routers/downloadHandler.js +51 -0
  108. package/src/routers/fileUploadApi.js +15 -0
  109. package/src/routers/generatorApi.js +30 -0
  110. package/src/routers/generatorAsset.js +18 -0
  111. package/src/routers/generatorPage.js +37 -0
  112. package/src/routers/handleError.js +43 -0
  113. package/src/routers/handleModuleNotfound.js +12 -0
  114. package/src/routers/moduleApi.js +34 -0
  115. package/src/routers/modulePage.js +102 -0
  116. package/src/sequencerdoc.js +311 -0
  117. package/src/sequencerline.js +214 -0
  118. package/src/session.js +57 -0
  119. package/src/startup.js +59 -0
  120. package/src/webapps.js +239 -0
  121. package/src/workermanager.js +83 -0
  122. package/templates/_lib_debug.ejs +11 -0
  123. package/templates/_lib_production.ejs +5 -0
  124. package/templates/application.page.ejs +143 -0
  125. package/templates/generator.page.ejs +131 -0
  126. package/templates/index.page.ejs +24 -0
  127. package/templates/login.page.ejs +102 -0
  128. package/templates/moduleError.ejs +16 -0
  129. package/templates/moduleNotfound.ejs +14 -0
  130. package/webapps.code-workspace +11 -0
@@ -0,0 +1,1181 @@
1
+ import Components from './appgen-components.mjs'
2
+ import AppGenIO from './appgen-io.mjs'
3
+ import { AppGenLayout_setUniqueME, AppGenLayout_setupUniqueDesigner, AppGenLayout_addUnique } from './appgen-ui-unique.mjs'
4
+ import { AppGenLayout_setSearchME, AppGenLayout_setupSearchDesigner, AppGenLayout_addSearch } from './appgen-ui-search.mjs'
5
+
6
+
7
+ const IO = new AppGenIO()
8
+
9
+
10
+ const URL_LAYOUT = 'appgen-layout'
11
+ const ME = {}
12
+
13
+ const ENT_COL_ID = 'col_id'
14
+ const ENT_COL_NAME = 'col_name'
15
+ const ENT_COL_TITLE = 'col_title'
16
+ const ENT_COL_TABLE = 'col_table'
17
+ const ENT_COL_PK = 'col_pk'
18
+ const ENT_COL_BTNDESIGN = 'col_btndesign'
19
+ const ENT_COL_BTNREMOVE = 'col_btnremove'
20
+
21
+ const ATTR_NAME = 'name'
22
+ const ATTR_ENTITYID = 'data-entity-id'
23
+ const ATTR_DROPTARGET = 'drop-target'
24
+ const ATTR_DRAGOVER = 'data-dragover'
25
+ const ATTR_CURRENTENTITY = 'data-currententity'
26
+ const ATTR_COMPNAME = 'data-component-name'
27
+
28
+
29
+
30
+ const ID_ENTITYEDITOR = 'entity-editor'
31
+ const ID_DESIGNERINFO = 'designer-info'
32
+ const ID_DESIGNERSEARCH = 'designer-search'
33
+ const ID_DESIGNERUNIQ = 'designer-unique'
34
+ const ID_ICONTOOL = 'component-icon-tool'
35
+ const ID_DESIGNFIELD = 'design-data-field'
36
+
37
+ const CLS_HIDDEN = 'hidden'
38
+ const CLS_ENTITYEDITOR = 'entity-editor'
39
+
40
+
41
+
42
+ const DRAG_ICONTOOL = 'drag-icon-tool'
43
+ const DRAG_REORDERFIELD = 'drag-reorderfield'
44
+
45
+ /* icon default untuk program yang akan di generate, muncul di tombol [upload icon program] */
46
+ const ICON_DEFAULT = `<svg version="1.1" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
47
+ <g fill="currentColor">
48
+ <path d="m20.019 2c-2.9332 0-5.2348 2.3057-5.2348 5.2389v3.1417h-8.3806c-2.3047 0-4.1903 1.8856-4.1903 4.1903v7.9628h3.1417c3.1427 0 5.6567 2.514 5.6567 5.6567 0 3.1427-2.514 5.6567-5.6567 5.6567h-3.3547v7.9628c0 2.3047 1.8856 4.1903 4.1903 4.1903h7.9628v-3.1458c0-3.1427 2.514-5.6567 5.6567-5.6567 3.1427 0 5.6567 2.514 5.6567 5.6567v3.1458h7.9628c2.3047 0 4.1903-1.8856 4.1903-4.1903v-8.3806h3.1417c2.9332 0 5.2389-2.3057 5.2389-5.2389 0-2.9332-2.3057-5.2389-5.2389-5.2389h-3.1417v-8.3806c0-2.3047-1.8856-4.1903-4.1903-4.1903h-8.3806v-3.1417c0.20951-2.9332-2.0968-5.2389-5.03-5.2389z"/>
49
+ </g>
50
+ </svg>`
51
+
52
+ const ICON_CLOSE = `<svg version="1.1" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
53
+ <g stroke="currentColor" stroke-linecap="round" stroke-width="5">
54
+ <path d="M4 4 L28 28"/>
55
+ <path d="M4 28 L28 4"/>
56
+ </g>
57
+ </svg>`
58
+
59
+ const ICON_PK = `<svg version="1.1" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
60
+ <g stroke-linecap="round" stroke-linejoin="round">
61
+ <rect x="11" y="8" width="26" height="32" fill="#fc0" stroke="#ffd42a" stroke-width="2"/>
62
+ <ellipse cx="23.333" cy="15.79" rx="8.6667" ry="4.2097" fill="none" stroke="#540" stroke-width="4"/>
63
+ <path d="m22 22.419v13.161" fill="none" stroke="#540" stroke-width="4"/>
64
+ <path d="m22 29.387h7.3333" fill="none" stroke="#540" stroke-width="4"/>
65
+ <path d="m22 35.581h10.667" fill="none" stroke="#540" stroke-width="4"/>
66
+ </g>
67
+ </svg>`
68
+
69
+
70
+ // let drop_valid = false
71
+
72
+ const CURRENT = {
73
+ drag_action: null,
74
+ drop_valid: false,
75
+ entity_id: null,
76
+ Design: null,
77
+ droptarget: null
78
+ }
79
+
80
+
81
+ const generateId = (prefix = "el") => {
82
+ return `${prefix}-${Date.now().toString(36)}-${Math.random().toString(36).substr(2, 5)}`;
83
+ }
84
+ const isValidName = str => /^[_a-z0-9]+$/.test(str) ;
85
+
86
+ const capitalizeFirst = (str) => {
87
+ return str.charAt(0).toUpperCase() + str.slice(1);
88
+ }
89
+
90
+ const createInputElement = (type, entity_id, data={}) => {
91
+ const input = document.createElement('input')
92
+ input.setAttribute('type', type)
93
+ input.setAttribute(ATTR_NAME, data.name)
94
+ input.setAttribute(ATTR_ENTITYID, entity_id)
95
+ input.value = data.value
96
+ return input
97
+ }
98
+
99
+ export default class AppGenUI {
100
+ #app
101
+
102
+ constructor(app) {
103
+ this.#app = app
104
+
105
+ IO.addFunction('addUnique', AppGenLayout_addUnique)
106
+ IO.addFunction('addSearch', AppGenLayout_addSearch)
107
+ }
108
+
109
+ get App() { return this.#app}
110
+
111
+ async NewData() {
112
+ await AppGenLayout_NewData(this)
113
+ }
114
+
115
+ async Init(context) {
116
+ this.Context = context
117
+ await AppGenLayout_Render(this, context)
118
+ AppGenLayout_NewData(this)
119
+ }
120
+
121
+ async getCurrentData() {
122
+ return await IO.getCurrentData()
123
+ }
124
+
125
+ setCurrentId(id) {
126
+ const obj_programid = document.getElementById('obj_programid')
127
+ obj_programid.value = id
128
+ }
129
+
130
+ load(data) {
131
+ IO.load(data)
132
+ }
133
+
134
+ pauseAutoSave(pause) {
135
+ IO.pauseAutoSave(pause)
136
+ }
137
+
138
+ async updateCache() {
139
+ IO.updateCache()
140
+ }
141
+
142
+ async reset() {
143
+ await IO.reset()
144
+ }
145
+ }
146
+
147
+
148
+
149
+ async function AppGenLayout_Render(self, context) {
150
+ ME.IconButton = document.getElementById('upload-icon')
151
+ ME.ComponentList = document.getElementById('crud-component-list')
152
+ ME.DesignTemplate = document.getElementById('DESIGNTEMPLATE')
153
+ ME.DataEntities = document.getElementById('data-entities')
154
+ ME.EntityDesigner = document.getElementById('entities-design')
155
+ ME.LayoutEditor = document.getElementById('layout-editor')
156
+ ME.LayoutSidebar = document.getElementById('layout-sidebar')
157
+ // ME.EntityDesigner = document.getElementById('entities-design')
158
+ ME.tbl_entity = document.getElementById('tbl_entity')
159
+ ME.btn_addEntity = document.getElementById('btn_addentity')
160
+ ME.btn_addEntity.addEventListener('click', (evt)=>{
161
+ btn_addEntity_click(self, evt)
162
+ })
163
+
164
+ AppGenLayout_createComponentList(this)
165
+ AppGenLayout_createButtons(this)
166
+ AppGenLayout_handleActionForm(this)
167
+
168
+ AppGenLayout_setUniqueME(ME)
169
+ AppGenLayout_setSearchME(ME)
170
+
171
+ console.log(context.appsUrls)
172
+
173
+
174
+ IO.Setup({
175
+ AddEntity: (data) => {
176
+ AppGenLayout_AddEntity(self, data)
177
+ },
178
+ startDesign: (entity_id, suppress) => {
179
+ AppGenLayout_startDesign(self, entity_id, suppress)
180
+ },
181
+ addComponentToDesigner: (droptarget, comp) => {
182
+ return AppGenLayout_addComponentToDesigner(self, droptarget, comp)
183
+ },
184
+ addAction: (data) => {
185
+ AppGenLayout_addAction(self, data)
186
+ }
187
+
188
+ })
189
+ }
190
+
191
+ function AppGenLayout_createButtons(self) {
192
+ // upload image icon
193
+ ME.IconButton.style.backgroundImage = `url('data:image/svg+xml,${encodeURIComponent(ICON_DEFAULT)}')`;
194
+ console.log(ME.IconButton.style.backgroundImage)
195
+
196
+
197
+ const btnUpload = document.getElementById('btn_IconUpload')
198
+ btnUpload.addEventListener('change', (evt)=>{
199
+ const file = btnUpload.files[0];
200
+ if (!file) return;
201
+
202
+ const validTypes = ["image/png", "image/svg+xml"];
203
+ if (!validTypes.includes(file.type)) {
204
+ alert("Hanya file PNG atau SVG yang diperbolehkan.");
205
+ return;
206
+ }
207
+
208
+ const reader = new FileReader();
209
+ reader.onload = function (e) {
210
+ ME.IconButton.style.backgroundImage = `url('${e.target.result}')`
211
+ };
212
+ reader.readAsDataURL(file);
213
+ })
214
+
215
+
216
+ //button design summary
217
+ const btn_ShowSummary = document.getElementById('btn_ShowSummary')
218
+ btn_ShowSummary.addEventListener('click', (evt)=>{
219
+ btn_ShowSummary_click(self, evt)
220
+ })
221
+
222
+ // button design detil
223
+ const btn_ShowDetail = document.getElementById('btn_ShowDetail')
224
+ btn_ShowDetail.addEventListener('click', (evt)=>{
225
+ btn_ShowDetail_click(self, evt)
226
+ })
227
+
228
+
229
+ // menampilkan design fields
230
+ const btn_ShowFields = document.getElementById('btn_ShowFields')
231
+ btn_ShowFields.addEventListener('click', (evt)=>{
232
+ btn_ShowFields_click(self, evt)
233
+ })
234
+
235
+ // menampilkan design uniq
236
+ const btn_ShowUniq = document.getElementById('btn_ShowUniq')
237
+ btn_ShowUniq.addEventListener('click', (evt)=>{
238
+ btn_ShowUniq_click(self, evt)
239
+ })
240
+
241
+ // menampilkan design search
242
+ const btn_ShowSearch = document.getElementById('btn_ShowSearch')
243
+ btn_ShowSearch.addEventListener('click', (evt)=>{
244
+ btn_ShowSearch_click(self, evt)
245
+ })
246
+ }
247
+
248
+ function btn_ShowSummary_click(self, evt) {
249
+ CURRENT.Design.classList.add('design-view-as-summary')
250
+
251
+ const entitties = CURRENT.Design.querySelectorAll('div[name="design-data-field"]')
252
+ entitties.forEach(el=>{
253
+ el.classList.remove('minimized')
254
+ el.classList.remove('maximized')
255
+ })
256
+
257
+ const droptarget = CURRENT.Design.querySelector('div[name="drop-target"')
258
+ droptarget.classList.add('minimized-drop-target')
259
+ }
260
+
261
+ function btn_ShowDetail_click(self, evt) {
262
+ CURRENT.Design.classList.remove('design-view-as-summary')
263
+ const entitties = CURRENT.Design.querySelectorAll('div[name="design-data-field"]')
264
+ entitties.forEach(el=>{
265
+ el.classList.remove('minimized')
266
+ el.classList.remove('maximized')
267
+ })
268
+
269
+ const droptarget = CURRENT.Design.querySelector('div[name="drop-target"')
270
+ droptarget.classList.remove('minimized-drop-target')
271
+ }
272
+
273
+ function showOnly(toShow) {
274
+ var nodes = CURRENT.Design.children
275
+ for (var node of nodes) {
276
+ var name = node.getAttribute('name')
277
+ if (toShow.includes(name)) {
278
+ node.classList.remove('hidden')
279
+ } else {
280
+ node.classList.add('hidden')
281
+ }
282
+
283
+ }
284
+ }
285
+
286
+ function btn_ShowFields_click(self, evt) {
287
+ showOnly(['designer-info', 'design-data-field'])
288
+
289
+ // tampilkan kembali drop target
290
+ const entity_id = CURRENT.entity_id
291
+ const designer = ME.EntityDesigner.querySelector(`div[${ATTR_ENTITYID}="${entity_id}"]`)
292
+ let droptarget = designer.querySelector(`[name="${ATTR_DROPTARGET}"]`)
293
+ if (droptarget!=null) {
294
+ droptarget.classList.remove('hidden')
295
+ }
296
+
297
+ }
298
+
299
+ function btn_ShowUniq_click(self, evt) {
300
+ showOnly(['designer-info', 'designer-unique'])
301
+ }
302
+
303
+ function btn_ShowSearch_click(self, evt) {
304
+ showOnly(['designer-info', 'designer-search'])
305
+ }
306
+
307
+
308
+
309
+
310
+
311
+
312
+
313
+ function btn_addEntity_click(self, evt) {
314
+ ME.btn_addEntity.style.animation = 'tombolDiTekanMenghilang 0.3s forwards'
315
+ setTimeout(()=>{
316
+ ME.btn_addEntity.classList.add('hidden')
317
+ ME.btn_addEntity.style.animation = 'unset'
318
+ AppGenLayout_AddEntity(self, {})
319
+ }, 300)
320
+
321
+ // nanti muncul lagi setelah 1 detil
322
+ setTimeout(()=>{
323
+ ME.btn_addEntity.classList.remove('hidden')
324
+ ME.btn_addEntity.style.animation = 'tombolMunculLagi 0.3s forwards'
325
+ setTimeout(()=>{
326
+ ME.btn_addEntity.style.animation = 'unset'
327
+ }, 300)
328
+ }, 1000)
329
+
330
+
331
+
332
+
333
+
334
+ }
335
+
336
+ async function AppGenLayout_AddEntity(self, entity={}) {
337
+ const ID = entity[ENT_COL_ID] ?? generateId('en')
338
+ const tbl_entity = ME.tbl_entity
339
+ const tbody = tbl_entity.querySelector('tbody')
340
+ const cols = tbl_entity.querySelectorAll('thead > tr th')
341
+
342
+
343
+ const newtr = document.createElement('tr')
344
+ newtr.setAttribute(ATTR_ENTITYID, ID)
345
+
346
+ if (entity.isheader) {
347
+ newtr.setAttribute('data-isheader', '')
348
+ }
349
+
350
+ for (var th of cols) {
351
+ const name = th.getAttribute(ATTR_NAME)
352
+ const td = document.createElement('td')
353
+ td.setAttribute(ATTR_NAME, name)
354
+ newtr.appendChild(td)
355
+ }
356
+ tbody.appendChild(newtr)
357
+
358
+
359
+
360
+
361
+ const fn_set_input = (type, value, entity_column_identity, isheader) => {
362
+ const col = newtr.querySelector(`td[${ATTR_NAME}="${entity_column_identity}"]`)
363
+ const data = {}
364
+ data[`${ATTR_ENTITYID}`] = ID
365
+ data[`${ATTR_NAME}`] = entity_column_identity
366
+ data['value'] = value ?? ""
367
+ const input = col.appendChild(createInputElement(type, ID, data))
368
+
369
+ input.setAttribute('autocomplete', 'off')
370
+ if (type=='text') {
371
+ input.addEventListener('change', (evt)=>{
372
+ AppGenLayout_entityInputChanged(self, evt)
373
+ })
374
+ }
375
+
376
+
377
+ if (isheader===true) {
378
+ type='hidden'
379
+ input.setAttribute('type', 'hidden')
380
+ }
381
+ if (type=='hidden') {
382
+ col.innerHTML = `<span>${data['value']}</span>`
383
+ }
384
+ col.appendChild(input)
385
+ return input
386
+ }
387
+
388
+ fn_set_input('hidden', ID, ENT_COL_ID, false)
389
+ fn_set_input('text', entity[ENT_COL_NAME], ENT_COL_NAME, entity.isheader)
390
+ fn_set_input('text', entity[ENT_COL_TITLE], ENT_COL_TITLE)
391
+ fn_set_input('text', entity[ENT_COL_TABLE], ENT_COL_TABLE)
392
+ fn_set_input('text', entity[ENT_COL_PK], ENT_COL_PK)
393
+
394
+ let btn_design = fn_set_input('button', "design", ENT_COL_BTNDESIGN)
395
+ btn_design.addEventListener('click', (evt)=>{
396
+ btn_design_click(self, evt)
397
+ })
398
+
399
+ if (entity.isheader!==true) {
400
+ let btn_remove = fn_set_input('button', "remove", ENT_COL_BTNREMOVE)
401
+ btn_remove.addEventListener('click', (evt)=>{
402
+ btn_remove_click(self, evt)
403
+ })
404
+ }
405
+
406
+
407
+
408
+ // tambahkan ke ME.EntityDesigner
409
+ AppGenLayout_addDesigner(self, ID, entity.isheader)
410
+
411
+
412
+ return ID
413
+ }
414
+
415
+
416
+ async function AppGenLayout_entityInputChanged(self, evt) {
417
+ const input = evt.target
418
+ const name = input.getAttribute(ATTR_NAME)
419
+ const entity_id = input.getAttribute(ATTR_ENTITYID)
420
+ const value = input.value
421
+
422
+ const data = {}
423
+ data[name] = value
424
+
425
+ const valid = await AppGenLayout_CekEntityData(self, data)
426
+ if (valid) {
427
+ AppGenLayout_changeEntityInfo(self, entity_id, data)
428
+ }
429
+
430
+ if (name==ENT_COL_PK) {
431
+ AppGenLayout_designerFieldNameChanged(self, entity_id, input, true)
432
+ }
433
+
434
+ }
435
+
436
+
437
+ async function AppGenLayout_CekEntityData(self, data) {
438
+ for (var name in data) {
439
+ if (data[name]==null || data[name].trim()=='') {
440
+ await $fgta5.MessageBox.warning("lengkapi dahulu semua data entity")
441
+ return false
442
+ }
443
+ }
444
+
445
+ const rule = "hanya boleh alphabet dan angka, dengan huruf kecil,<br>tanpa spasi, tanpa character khusus."
446
+
447
+
448
+ if (data[ENT_COL_NAME]!==undefined) {
449
+ if (!isValidName(data[ENT_COL_NAME].trim())) {
450
+ await $fgta5.MessageBox.warning(`nama entity: <span style="font-weight:bold; color:red">${data[ENT_COL_NAME]}</span> tidak valid!<br>${rule}`)
451
+ return false
452
+ }
453
+ }
454
+
455
+ /*
456
+ if (data[ENT_COL_TABLE]!==undefined) {
457
+ if (!isValidName(data[ENT_COL_TABLE].trim())) {
458
+ await $fgta5.MessageBox.warning(`nama table: <span style="font-weight:bold; color:red">${data[ENT_COL_TABLE]}</span> tidak valid!<br>${rule}`)
459
+ return false
460
+ }
461
+ }
462
+ */
463
+
464
+ if (data[ENT_COL_PK]!==undefined) {
465
+ if (!isValidName(data[ENT_COL_PK].trim())) {
466
+ await $fgta5.MessageBox.warning(`nama PK: <span style="font-weight:bold; color:red">${data[ENT_COL_PK]}</span> tidak valid!<br>${rule}`)
467
+ return false
468
+ }
469
+ }
470
+
471
+ return true
472
+ }
473
+
474
+
475
+ async function btn_design_click(self, evt) {
476
+ const btn = evt.target
477
+ const entity_id = btn.getAttribute(ATTR_ENTITYID)
478
+ const editores = ME.EntityDesigner.querySelectorAll(`div[name="${ID_ENTITYEDITOR}"]`)
479
+ const tr = btn.closest('tr')
480
+
481
+
482
+ const data = AppGenLayout_GetEntityData(self, tr)
483
+ const valid = await AppGenLayout_CekEntityData(self, data)
484
+
485
+ if (!valid) {
486
+ return
487
+ }
488
+
489
+ for (let el of editores) {
490
+ let ceid = el.getAttribute(ATTR_ENTITYID)
491
+ if (ceid==entity_id) {
492
+ el.classList.remove(CLS_HIDDEN)
493
+ AppGenLayout_entityDesign(self, entity_id, tr)
494
+ AppGenLayout_startDesign(self, entity_id)
495
+ } else {
496
+ el.classList.add(CLS_HIDDEN)
497
+ }
498
+ }
499
+ }
500
+
501
+
502
+ function AppGenLayout_GetEntityData(self, tr) {
503
+ const ens = [ENT_COL_NAME, ENT_COL_TITLE, ENT_COL_TABLE, ENT_COL_PK]
504
+ const data = {}
505
+
506
+ for (var name of ens) {
507
+ const obj = tr.querySelector(`td[name="${name}"] input`)
508
+ data[name] = obj.value
509
+ }
510
+
511
+ return data
512
+ }
513
+
514
+
515
+ function AppGenLayout_entityDesign(self, entity_id, tr) {
516
+ const data = AppGenLayout_GetEntityData(self, tr)
517
+ AppGenLayout_changeEntityInfo(self, entity_id, data)
518
+
519
+ // tandaii tool: ATTR_CURRENTENTITY
520
+ let arr = Array.from(ME.ComponentList.children)
521
+ for (let el of arr) {
522
+ el.setAttribute(ATTR_CURRENTENTITY, entity_id)
523
+ }
524
+ }
525
+
526
+
527
+ function AppGenLayout_changeEntityInfo(self, entity_id, data) {
528
+ const info = ME.EntityDesigner.querySelector(`div[${ATTR_ENTITYID}="${entity_id}"] div[name="${ID_DESIGNERINFO}"]`)
529
+ const ens = [ENT_COL_NAME, ENT_COL_TITLE, ENT_COL_TABLE, ENT_COL_PK]
530
+ for (var name of ens) {
531
+ if (data[name]!==undefined) {
532
+ const ed = info.querySelector(`div[name="${name}"]`)
533
+ ed.innerHTML = data[name]
534
+ }
535
+ }
536
+ }
537
+
538
+
539
+ async function btn_remove_click(self, evt) {
540
+ const btn = evt.target
541
+ const tr = btn.closest('tr')
542
+ const data = AppGenLayout_GetEntityData(self, tr)
543
+
544
+ var sudahadadata = false
545
+ for (var name in data) {
546
+ if (data[name].trim()!='') {
547
+ sudahadadata = true
548
+ }
549
+ }
550
+
551
+ const remove = (tr) => {
552
+ var entity_id = tr.getAttribute(ATTR_ENTITYID)
553
+ var editor = ME.EntityDesigner.querySelector(`[${ATTR_ENTITYID}="${entity_id}"]`)
554
+ if (editor!=null) {
555
+ editor.remove()
556
+ }
557
+ tr.remove()
558
+ }
559
+
560
+
561
+ if (sudahadadata) {
562
+ var ret = await $fgta5.MessageBox.confirm("Apakah anda yakin mau menghapus design entity ini?")
563
+ if (ret=='ok') {
564
+ remove(tr)
565
+ }
566
+ } else {
567
+ remove(tr)
568
+ }
569
+
570
+
571
+ }
572
+
573
+
574
+ function AppGenLayout_addDesigner(self, ID, isheader) {
575
+
576
+ const editem = document.createElement('div')
577
+ editem.classList.add('hidden')
578
+ editem.classList.add(CLS_ENTITYEDITOR)
579
+ editem.setAttribute('name', ID_ENTITYEDITOR)
580
+ editem.setAttribute(ATTR_ENTITYID, ID)
581
+
582
+ // ambil data template untuk entity info
583
+ const tplInfo = ME.DesignTemplate.querySelector(`div[name="${ID_DESIGNERINFO}"]`)
584
+ const elinfo = tplInfo.cloneNode(true)
585
+
586
+ // ambil data template uniq design
587
+ const tplUniq = ME.DesignTemplate.querySelector(`div[name="${ID_DESIGNERUNIQ}"]`)
588
+ const eluniq = tplUniq.cloneNode(true)
589
+
590
+ // ambil data template uniq search
591
+ const tplSearch = ME.DesignTemplate.querySelector(`div[name="${ID_DESIGNERSEARCH}"]`)
592
+ const elsearch = tplSearch.cloneNode(true)
593
+
594
+
595
+
596
+ // console.log(ME.EntityDesigner)
597
+ // eluniq.innerHTML = `designer unig ${ID}`
598
+
599
+
600
+
601
+ editem.appendChild(elinfo)
602
+ editem.appendChild(eluniq)
603
+ editem.appendChild(elsearch)
604
+
605
+ const chka = elinfo.querySelector('input[type="checkbox"][name="allow-row-add"]')
606
+ const chkr = elinfo.querySelector('input[type="checkbox"][name="allow-row-remove"]')
607
+
608
+ chka.checked = true
609
+ chkr.checked = true
610
+
611
+ if (isheader===true) {
612
+ chka.disabled = true
613
+ chkr.disabled = true
614
+ const dcs = elinfo.querySelectorAll('[data-control]')
615
+ for (var dc of dcs) {
616
+ dc.classList.add('hidden')
617
+ }
618
+
619
+ } else {
620
+ const dai = elinfo.querySelectorAll('[data-autoid]')
621
+ for (var ai of dai) {
622
+ ai.classList.add('hidden')
623
+ }
624
+ }
625
+
626
+ // setup designer uniq & search
627
+ AppGenLayout_setupUniqueDesigner(eluniq, ID)
628
+ AppGenLayout_setupSearchDesigner(elsearch, ID)
629
+
630
+ // tambahkan editor ke designer
631
+ ME.EntityDesigner.appendChild(editem)
632
+ }
633
+
634
+
635
+
636
+
637
+
638
+
639
+
640
+
641
+
642
+
643
+
644
+
645
+
646
+
647
+
648
+
649
+
650
+
651
+
652
+
653
+
654
+ async function AppGenLayout_NewData(self) {
655
+ // jika belum ada header
656
+ const tbl_entity = ME.tbl_entity
657
+ const tbody = tbl_entity.querySelector('tbody')
658
+ const entity_id = await AppGenLayout_AddEntity(self, {
659
+ isheader: true,
660
+ col_name: 'header',
661
+ col_title: 'programtitle',
662
+ col_table: `schema.tablename`,
663
+ col_pk: 'pk'
664
+ })
665
+
666
+ const btn = tbody.querySelector(`[name="col_btndesign"][${ATTR_ENTITYID}="${entity_id}"]`)
667
+ btn.click()
668
+
669
+ setTimeout(()=>{
670
+ IO.AutoSave()
671
+ }, 2000)
672
+
673
+ }
674
+
675
+
676
+ function AppGenLayout_highlightElement(self, droptarget) {
677
+
678
+
679
+ droptarget.style.animation = 'pulseHighlight 1s forwards'
680
+ setTimeout(()=>{
681
+ droptarget.style.animation = 'unset'
682
+ }, 1000)
683
+
684
+ }
685
+
686
+ function AppGenLayout_startDesign(self, entity_id, suppress) {
687
+ if (suppress===undefined) {
688
+ suppress = false
689
+ }
690
+
691
+
692
+ if (!suppress) {
693
+ ME.LayoutEditor.classList.remove('hidden')
694
+ ME.LayoutSidebar.classList.remove('hidden')
695
+ }
696
+
697
+
698
+ // buat drop target
699
+ // cek apakah sudah ada drop target
700
+ const designer = ME.EntityDesigner.querySelector(`div[${ATTR_ENTITYID}="${entity_id}"]`)
701
+ let droptarget = designer.querySelector(`[name="${ATTR_DROPTARGET}"]`)
702
+ if (droptarget==null) {
703
+ // blum ada, buat dulu
704
+ const tpl = ME.DesignTemplate.querySelector(`div[name="${ATTR_DROPTARGET}"]`)
705
+ droptarget = tpl.cloneNode(true)
706
+ droptarget.setAttribute(ATTR_ENTITYID, entity_id)
707
+ designer.appendChild(droptarget)
708
+ }
709
+
710
+ // scroll ke element editor
711
+ designer.scrollIntoView({
712
+ behavior: 'smooth',
713
+ block: 'start'
714
+ });
715
+
716
+ AppGenLayout_setupDropTarget(self, droptarget)
717
+
718
+ // higlight drop target
719
+ AppGenLayout_highlightElement(self, droptarget)
720
+
721
+ CURRENT.entity_id = entity_id
722
+ CURRENT.Design = designer
723
+ CURRENT.droptarget = droptarget
724
+
725
+ }
726
+
727
+ function AppGenLayout_setupDropTarget(self, droptarget) {
728
+ const dr = droptarget.getAttribute(ATTR_DROPTARGET)
729
+ if (dr===true) {
730
+ // drop target sudah di setup
731
+ return
732
+ }
733
+
734
+
735
+ droptarget.addEventListener('dragover', (evt)=>{
736
+ evt.preventDefault()
737
+ droptarget.setAttribute(ATTR_DRAGOVER, '')
738
+ })
739
+
740
+ droptarget.addEventListener('dragleave', (evt)=>{
741
+ droptarget.removeAttribute(ATTR_DRAGOVER)
742
+ // droptarget.classList.add('hidden')
743
+ })
744
+
745
+ droptarget.addEventListener('drop', (evt)=>{
746
+ CURRENT.drop_valid = true
747
+ droptarget.removeAttribute(ATTR_DRAGOVER)
748
+
749
+
750
+ if (CURRENT.drag_action==DRAG_ICONTOOL) {
751
+ const compname = evt.dataTransfer.getData('compname');
752
+ AppGenLayout_addComponentToDesigner(self, droptarget, Components[compname])
753
+
754
+ setTimeout(()=>{
755
+ CURRENT.Design.appendChild(droptarget)
756
+ droptarget.scrollIntoView({
757
+ behavior: 'smooth',
758
+ block: 'start'
759
+ });
760
+ }, 300)
761
+
762
+ } else if (CURRENT.drag_action==DRAG_REORDERFIELD) {
763
+ const datafield_id = evt.dataTransfer.getData('datafield_id');
764
+ const el = document.getElementById(datafield_id)
765
+ droptarget.after(el)
766
+ setTimeout(()=>{
767
+ droptarget.classList.add('hidden')
768
+ }, 300)
769
+ }
770
+
771
+ CURRENT.drag_action = ''
772
+
773
+
774
+ })
775
+
776
+ }
777
+
778
+
779
+ function AppGenLayout_createComponentList(self) {
780
+ const tpl = ME.DesignTemplate.querySelector(`div[name="${ID_ICONTOOL}"]`)
781
+ for (var name in Components) {
782
+ let comp = Components[name]
783
+
784
+ comp.name = name
785
+ var iconTool = AppGenLayout_createIconTool(self, comp, tpl)
786
+ ME.ComponentList.appendChild(iconTool)
787
+ }
788
+
789
+ }
790
+
791
+ function AppGenLayout_createIconTool(self, comp, tpl) {
792
+ const tool = tpl.cloneNode(true)
793
+ const icon = tool.querySelector('div[data-icon')
794
+ const label = tool.querySelector('div[data-label]')
795
+
796
+
797
+ icon.innerHTML =comp.icon
798
+ label.innerHTML = comp.title
799
+
800
+
801
+ tool.addEventListener('dragstart', (evt)=>{
802
+ CURRENT.drop_valid = false
803
+ CURRENT.drag_action = DRAG_ICONTOOL
804
+ evt.dataTransfer.setData('compname', comp.name);
805
+ })
806
+
807
+ tool.addEventListener('dragend', (evt)=>{
808
+ if (CURRENT.droptarget==null) {
809
+ return
810
+ }
811
+ const fields = CURRENT.Design.querySelectorAll('[name="design-data-field"]')
812
+ if (fields.length==0) {
813
+ return
814
+ }
815
+ if (!CURRENT.drop_valid) {
816
+ CURRENT.droptarget.classList.add('hidden')
817
+ }
818
+ })
819
+
820
+ tool.addEventListener('dblclick', (evt)=>{
821
+ // pindah drop target ke bawah
822
+ // droptarget.classList.remove('hidden')
823
+ CURRENT.droptarget.classList.remove('hidden')
824
+ CURRENT.Design.appendChild(CURRENT.droptarget)
825
+ AppGenLayout_addComponentToDesigner(self, CURRENT.droptarget, comp)
826
+
827
+ // terus sroll ke bawah
828
+ setTimeout(()=>{
829
+ CURRENT.droptarget.scrollIntoView({
830
+ behavior: 'smooth',
831
+ block: 'start'
832
+ });
833
+ }, 300)
834
+ })
835
+
836
+ return tool
837
+ }
838
+
839
+
840
+ function AppGenLayout_addComponentToDesigner(self, droptarget, comp) {
841
+ const tpl = ME.DesignTemplate.querySelector(`div[name="${ID_DESIGNFIELD}"][data-template=${comp.template}]`)
842
+ const datafield = tpl.cloneNode(true)
843
+ const entity_id = droptarget.getAttribute(ATTR_ENTITYID)
844
+
845
+ const datafield_id = generateId('datafield')
846
+ datafield.setAttribute('id', datafield_id)
847
+ datafield.setAttribute(ATTR_ENTITYID, entity_id)
848
+ datafield.setAttribute(ATTR_COMPNAME, comp.name)
849
+
850
+ // masukkan element baru sebelum drop target
851
+ droptarget.before(datafield)
852
+
853
+
854
+ // setup datafiled
855
+ const comptype = datafield.querySelector('[name="component-type"]')
856
+
857
+ const compicon = comptype.querySelector('[name="icon"]')
858
+ const comptitle = comptype.querySelector('[name="title"]')
859
+ comptitle.innerHTML = comp.title
860
+
861
+ const compsummary = datafield.querySelector('[name="component-summary"]')
862
+ const compiconsummary = compsummary.querySelector('[name="icon"]')
863
+
864
+ const compiconspk = datafield.querySelectorAll('[name="icon-pk"]')
865
+ for (var iconspk of compiconspk) {
866
+ iconspk.setAttribute(ATTR_ENTITYID, entity_id)
867
+ }
868
+
869
+ compsummary.setAttribute('draggable', true)
870
+ compsummary.addEventListener('dragstart', (evt)=>{
871
+ CURRENT.drop_valid = false
872
+ CURRENT.drag_action = DRAG_REORDERFIELD
873
+ evt.dataTransfer.setData('datafield_id', datafield_id);
874
+ })
875
+ compsummary.addEventListener('dragend', (evt)=>{
876
+ if (CURRENT.droptarget==null) {
877
+ return
878
+ }
879
+ if (!CURRENT.drop_valid) {
880
+ CURRENT.droptarget.classList.add('hidden')
881
+ }
882
+ })
883
+
884
+
885
+
886
+ // copy icon dari detil design ke summary design
887
+ compiconsummary.innerHTML = compicon.innerHTML
888
+
889
+
890
+
891
+ const obj_namesummary = datafield.querySelector('input[name="fieldname-summary"]')
892
+ obj_namesummary.addEventListener('dblclick', (evt)=>{
893
+ evt.stopPropagation() // mencegar trigger maximize/minimized saat dblclick textbox
894
+ })
895
+
896
+ // kalau data field dilewati DRAG ICON, munculkan droptarget di bawahnya
897
+ datafield.addEventListener('dragover', (evt)=>{
898
+ if (CURRENT.drag_action==DRAG_ICONTOOL || CURRENT.drag_action==DRAG_REORDERFIELD) {
899
+ evt.preventDefault()
900
+ droptarget.classList.remove('hidden')
901
+
902
+ const rect = datafield.getBoundingClientRect();
903
+ const offsetY = evt.clientY - rect.top;
904
+ const offsetX = evt.clientX - rect.left;
905
+
906
+ if (offsetX > rect.width - 100) {
907
+ return;
908
+ }
909
+
910
+
911
+ if (offsetY < rect.height / 2) {
912
+ setTimeout(()=>{
913
+ datafield.before(droptarget)
914
+ }, 300)
915
+
916
+ } else {
917
+ setTimeout(()=>{
918
+ datafield.after(droptarget)
919
+ }, 300)
920
+ }
921
+
922
+ }
923
+ })
924
+
925
+ // handle button close
926
+ const btncs = datafield.querySelectorAll(`[class="field-remove-button"]`)
927
+ for (const btn of btncs) {
928
+ btn.innerHTML = ICON_CLOSE
929
+ btn.addEventListener('click', async (evt)=>{
930
+ var res = await $fgta5.MessageBox.confirm('removing field is irreversible. Are you sure ?')
931
+ if (res=='ok') {
932
+ // hapus
933
+ datafield.style.animation = 'fieldDihapus 0.3s forwards'
934
+ setTimeout(()=>{
935
+ datafield.remove()
936
+ }, 300)
937
+ }
938
+ })
939
+ }
940
+
941
+
942
+ const bars = datafield.querySelectorAll("div[field-handle-bar]")
943
+ for (let bar of bars) {
944
+ bar.addEventListener('dblclick', (evt)=>{
945
+ const varname = bar.getAttribute('name')
946
+ if (varname=='component-summary') {
947
+ datafield.classList.add('maximized')
948
+ datafield.classList.remove('minimized')
949
+ } else {
950
+ datafield.classList.add('minimized')
951
+ datafield.classList.remove('maximized')
952
+ }
953
+ })
954
+ }
955
+
956
+ AppGenGenLayout_HandleDataField(self, entity_id, comp, datafield)
957
+
958
+
959
+ return datafield
960
+ }
961
+
962
+
963
+ function AppGenLayout_designerFieldNameChanged(self, entity_id, obj, fromentity) {
964
+ const query = `tr[${ATTR_ENTITYID}="${entity_id}"] [name="${ENT_COL_PK}"] input`
965
+ const elpk = ME.DataEntities.querySelector(query)
966
+
967
+ const design = ME.EntityDesigner.querySelector(`[name="entity-editor"][${ATTR_ENTITYID}="${entity_id}"]`)
968
+ const datatypeEl = design.querySelector('select[name="datatype"]')
969
+ datatypeEl.disabled = false
970
+
971
+ console.log('AppGenLayout_designerFieldNameChanged')
972
+ const setPk = (df, primary_key) => {
973
+ // reset seluruh primary key
974
+ const alliconspk = df.querySelectorAll('[name="icon-pk"]')
975
+ for (var icoalpk of alliconspk) {
976
+ icoalpk.innerHTML = ''
977
+ }
978
+
979
+ const field = df.querySelector(`input[name="fieldname"][${ATTR_ENTITYID}="${entity_id}"]`)
980
+ const current_fieldname_value = field.value
981
+ if (current_fieldname_value==primary_key) {
982
+ const iconspk = df.querySelectorAll(`[name="icon-pk"][${ATTR_ENTITYID}="${entity_id}"]`)
983
+ for (var ico of iconspk) {
984
+ ico.innerHTML = ICON_PK
985
+ }
986
+
987
+ }
988
+
989
+
990
+ // untuk primary key detail tipe data otomatis di set ke bigInt
991
+ // ambil enentity header untuk pengecekan tipe data
992
+ if (current_fieldname_value==primary_key) {
993
+ const entityHeader = ME.DataEntities.querySelector('tr[data-isheader')
994
+ const headerEntityId = entityHeader.getAttribute(ATTR_ENTITYID)
995
+ if (entity_id!=headerEntityId) {
996
+ // set tipedata untuk PK di detil menjadi bigint
997
+ console.log('SET tO BIG INT')
998
+ datatypeEl.value = 'bigint'
999
+ datatypeEl.disabled = true
1000
+ // console.log(datatypeEl)
1001
+ }
1002
+ }
1003
+ }
1004
+
1005
+
1006
+
1007
+ let primary_key
1008
+ if (fromentity===true) {
1009
+ // diedit dari tabel entity
1010
+ primary_key = obj.value
1011
+
1012
+ // cari semua field dengan valuenya primary_key
1013
+ const design = ME.EntityDesigner.querySelector(`[name="entity-editor"][${ATTR_ENTITYID}="${entity_id}"]`)
1014
+ const dfs = design.querySelectorAll('[name="design-data-field"]')
1015
+ for (const df of dfs) {
1016
+ setPk(df, primary_key)
1017
+ }
1018
+ } else {
1019
+ // diedit dari designer
1020
+ primary_key = elpk.value
1021
+ const df = obj.closest('[name="design-data-field"]') //cari [name="design-data-field"] terdekat
1022
+ setPk(df, primary_key)
1023
+ }
1024
+
1025
+
1026
+
1027
+
1028
+
1029
+
1030
+
1031
+ }
1032
+
1033
+ function AppGenLayout_handleActionForm(self) {
1034
+ const btn_action_add = document.getElementById('btn_action_add')
1035
+ btn_action_add.addEventListener('click', (evt)=>{
1036
+ const txt_action_name = document.getElementById('txt_action_name')
1037
+ const txt_action_title = document.getElementById('txt_action_title')
1038
+ const txt_action_permission = document.getElementById('txt_action_permission')
1039
+
1040
+ AppGenLayout_addAction(self, {
1041
+ name: txt_action_name.value,
1042
+ title: txt_action_title.value,
1043
+ permission: txt_action_permission.value
1044
+ })
1045
+ })
1046
+ }
1047
+
1048
+ async function AppGenLayout_addAction(self, data) {
1049
+ const tbody = document.getElementById('action-lists')
1050
+ const name = data.name
1051
+ const title = data.title
1052
+ const permission = data.permission ?? ''
1053
+
1054
+ if (!isValidName(name)) {
1055
+ await $fgta5.MessageBox.warning('nama action tidak valid')
1056
+ return
1057
+ }
1058
+
1059
+ if (title.trim()=='') {
1060
+ await $fgta5.MessageBox.warning('title tidak valid')
1061
+ return
1062
+ }
1063
+
1064
+
1065
+ const tr = document.createElement('tr')
1066
+ const tdName = document.createElement('td')
1067
+ const tdTitle = document.createElement('td')
1068
+ const tdPermission = document.createElement('td')
1069
+ const tdButton = document.createElement('td')
1070
+ const rmButton = document.createElement('div')
1071
+
1072
+
1073
+ tdName.innerHTML = `<div name="action-name" class="action-cell">${name}</div>`
1074
+ tdTitle.innerHTML = `<div name="action-title" class="action-cell">${title}</div>`
1075
+ tdPermission.innerHTML = `<div name="action-title" class="action-cell">${permission}</div>`
1076
+
1077
+ rmButton.innerHTML = ICON_CLOSE
1078
+ rmButton.classList.add("action-button-remove")
1079
+ rmButton.addEventListener('click', async (evt)=>{
1080
+ var res = await $fgta5.MessageBox.confirm('Are you sure removing this action ?')
1081
+ if (res=='ok') {
1082
+ tr.remove()
1083
+ }
1084
+ })
1085
+
1086
+ tdButton.appendChild(rmButton)
1087
+
1088
+
1089
+ tr.appendChild(tdName)
1090
+ tr.appendChild(tdTitle)
1091
+ tr.appendChild(tdPermission)
1092
+ tr.appendChild(tdButton)
1093
+ tbody.appendChild(tr)
1094
+
1095
+ txt_action_name.value = ''
1096
+ txt_action_title.value = ''
1097
+ txt_action_permission.value = ''
1098
+ }
1099
+
1100
+ function AppGenGenLayout_HandleDataField(self, entity_id, comp, datafield) {
1101
+ const fieldname = datafield.querySelector('input[name="fieldname"]')
1102
+ const namesummary = datafield.querySelector('input[name="fieldname-summary"]')
1103
+ const objectname = datafield.querySelector('input[name="objectname"]')
1104
+ const labeltext = datafield.querySelector('input[name="labeltext"]')
1105
+ const placeholder = datafield.querySelector('input[name="placeholder"]')
1106
+
1107
+
1108
+
1109
+
1110
+ const nameChanged = (value) => {
1111
+ if (objectname.value.trim()=='') {
1112
+ objectname.value = 'obj_' + fieldname.value
1113
+ }
1114
+
1115
+ if (labeltext.value.trim()=='') {
1116
+ labeltext.value = capitalizeFirst(fieldname.value)
1117
+ }
1118
+
1119
+ if (placeholder.value.trim()=='') {
1120
+ placeholder.value = fieldname.value
1121
+ }
1122
+ }
1123
+
1124
+ const nameKeydown = (evt) => {
1125
+ const allowed = /^[a-zA-Z0-9_]$/; // hanya huruf, angka, dan underscore
1126
+
1127
+ if (
1128
+ evt.ctrlKey || evt.metaKey || evt.altKey || // biarkan shortcut seperti Ctrl+C
1129
+ evt.key === "Backspace" ||
1130
+ evt.key === "Tab" ||
1131
+ evt.key === "ArrowLeft" ||
1132
+ evt.key === "ArrowRight" ||
1133
+ evt.key === "Delete"
1134
+ ) {
1135
+ return; // izinkan navigasi dan kontrol
1136
+ }
1137
+
1138
+ if (!allowed.test(evt.key)) {
1139
+ evt.preventDefault(); // blokir karakter lain
1140
+ }
1141
+ }
1142
+
1143
+
1144
+ fieldname.setAttribute(ATTR_ENTITYID, entity_id)
1145
+ fieldname.addEventListener('change', (evt)=>{
1146
+ namesummary.value = fieldname.value
1147
+ const entity_id = fieldname.getAttribute(ATTR_ENTITYID)
1148
+ AppGenLayout_designerFieldNameChanged(self, entity_id, fieldname)
1149
+ nameChanged(fieldname.value)
1150
+ })
1151
+
1152
+
1153
+ namesummary.setAttribute(ATTR_ENTITYID, entity_id)
1154
+ namesummary.addEventListener('change', (evt)=>{
1155
+ fieldname.value = namesummary.value
1156
+ const entity_id = namesummary.getAttribute(ATTR_ENTITYID)
1157
+ AppGenLayout_designerFieldNameChanged(self, entity_id, namesummary)
1158
+ nameChanged(namesummary.value)
1159
+ })
1160
+
1161
+
1162
+ fieldname.addEventListener('keydown', (evt)=>{
1163
+ nameKeydown(evt)
1164
+ });
1165
+
1166
+ namesummary.addEventListener('keydown', (evt)=>{
1167
+ nameKeydown(evt)
1168
+ });
1169
+
1170
+
1171
+ if (comp.name=='Textbox') {
1172
+ const datalength = datafield.querySelector('input[name="datalength"]')
1173
+ const maximum = datafield.querySelector('input[name="maximum"]')
1174
+ maximum.value = datalength.value
1175
+ datalength.addEventListener('change', (evt)=>{
1176
+ maximum.value = datalength.value
1177
+ })
1178
+
1179
+ }
1180
+
1181
+ }