editing.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556
  1. package presets
  2. import (
  3. "fmt"
  4. "strings"
  5. "github.com/google/uuid"
  6. "github.com/jinzhu/inflection"
  7. "github.com/qor5/admin/presets/actions"
  8. . "github.com/qor5/ui/vuetify"
  9. vx "github.com/qor5/ui/vuetifyx"
  10. "github.com/qor5/web"
  11. "github.com/qor5/x/i18n"
  12. "github.com/qor5/x/perm"
  13. h "github.com/theplant/htmlgo"
  14. )
  15. type EditingBuilder struct {
  16. mb *ModelBuilder
  17. Fetcher FetchFunc
  18. Setter SetterFunc
  19. Saver SaveFunc
  20. Deleter DeleteFunc
  21. Validator ValidateFunc
  22. tabPanels []ObjectComponentFunc
  23. hiddenFuncs []ObjectComponentFunc
  24. sidePanel ComponentFunc
  25. actionsFunc ObjectComponentFunc
  26. editingTitleFunc EditingTitleComponentFunc
  27. FieldsBuilder
  28. }
  29. // string / []string / *FieldsSection
  30. func (mb *ModelBuilder) Editing(vs ...interface{}) (r *EditingBuilder) {
  31. r = mb.editing
  32. if len(vs) == 0 {
  33. return
  34. }
  35. r.Only(vs...)
  36. return r
  37. }
  38. // string / []string / *FieldsSection
  39. func (b *EditingBuilder) Only(vs ...interface{}) (r *EditingBuilder) {
  40. r = b
  41. r.FieldsBuilder = *r.FieldsBuilder.Only(vs...)
  42. return
  43. }
  44. func (b *EditingBuilder) Creating(vs ...interface{}) (r *EditingBuilder) {
  45. if b.mb.creating == nil {
  46. b.mb.creating = &EditingBuilder{
  47. mb: b.mb,
  48. Fetcher: b.Fetcher,
  49. Setter: b.Setter,
  50. Saver: b.Saver,
  51. Deleter: b.Deleter,
  52. Validator: b.Validator,
  53. }
  54. }
  55. r = b.mb.creating
  56. r.FieldsBuilder = *b.mb.writeFields.Only(vs...)
  57. return r
  58. }
  59. func (b *EditingBuilder) FetchFunc(v FetchFunc) (r *EditingBuilder) {
  60. b.Fetcher = v
  61. return b
  62. }
  63. func (b *EditingBuilder) SaveFunc(v SaveFunc) (r *EditingBuilder) {
  64. b.Saver = v
  65. return b
  66. }
  67. func (b *EditingBuilder) DeleteFunc(v DeleteFunc) (r *EditingBuilder) {
  68. b.Deleter = v
  69. return b
  70. }
  71. func (b *EditingBuilder) ValidateFunc(v ValidateFunc) (r *EditingBuilder) {
  72. b.Validator = v
  73. return b
  74. }
  75. func (b *EditingBuilder) SetterFunc(v SetterFunc) (r *EditingBuilder) {
  76. b.Setter = v
  77. return b
  78. }
  79. func (b *EditingBuilder) AppendTabsPanelFunc(v ObjectComponentFunc) (r *EditingBuilder) {
  80. b.tabPanels = append(b.tabPanels, v)
  81. return b
  82. }
  83. func (b *EditingBuilder) CleanTabsPanels() (r *EditingBuilder) {
  84. b.tabPanels = nil
  85. return b
  86. }
  87. func (b *EditingBuilder) SidePanelFunc(v ComponentFunc) (r *EditingBuilder) {
  88. b.sidePanel = v
  89. return b
  90. }
  91. func (b *EditingBuilder) AppendHiddenFunc(v ObjectComponentFunc) (r *EditingBuilder) {
  92. b.hiddenFuncs = append(b.hiddenFuncs, v)
  93. return b
  94. }
  95. func (b *EditingBuilder) ActionsFunc(v ObjectComponentFunc) (r *EditingBuilder) {
  96. b.actionsFunc = v
  97. return b
  98. }
  99. func (b *EditingBuilder) EditingTitleFunc(v EditingTitleComponentFunc) (r *EditingBuilder) {
  100. b.editingTitleFunc = v
  101. return b
  102. }
  103. func (b *EditingBuilder) formNew(ctx *web.EventContext) (r web.EventResponse, err error) {
  104. if b.mb.Info().Verifier().Do(PermCreate).WithReq(ctx.R).IsAllowed() != nil {
  105. ShowMessage(&r, perm.PermissionDenied.Error(), "warning")
  106. return
  107. }
  108. creatingB := b
  109. if b.mb.creating != nil {
  110. creatingB = b.mb.creating
  111. }
  112. b.mb.p.overlay(ctx.R.FormValue(ParamOverlay), &r, creatingB.editFormFor(nil, ctx), b.mb.rightDrawerWidth)
  113. return
  114. }
  115. func (b *EditingBuilder) formEdit(ctx *web.EventContext) (r web.EventResponse, err error) {
  116. if b.mb.Info().Verifier().Do(PermGet).WithReq(ctx.R).IsAllowed() != nil {
  117. ShowMessage(&r, perm.PermissionDenied.Error(), "warning")
  118. return
  119. }
  120. b.mb.p.overlay(ctx.R.FormValue(ParamOverlay), &r, b.editFormFor(nil, ctx), b.mb.rightDrawerWidth)
  121. return
  122. }
  123. func (b *EditingBuilder) singletonPageFunc(ctx *web.EventContext) (r web.PageResponse, err error) {
  124. if b.mb.Info().Verifier().Do(PermUpdate).WithReq(ctx.R).IsAllowed() != nil {
  125. err = perm.PermissionDenied
  126. return
  127. }
  128. msgr := MustGetMessages(ctx.R)
  129. title := msgr.EditingObjectTitle(i18n.T(ctx.R, ModelsI18nModuleKey, inflection.Singular(b.mb.label)), "")
  130. r.PageTitle = title
  131. obj, err := b.Fetcher(b.mb.NewModel(), "", ctx)
  132. if err == ErrRecordNotFound {
  133. if err = b.Saver(b.mb.NewModel(), "", ctx); err != nil {
  134. return
  135. }
  136. obj, err = b.Fetcher(b.mb.NewModel(), "", ctx)
  137. }
  138. if err != nil {
  139. return
  140. }
  141. r.Body = web.Portal(b.editFormFor(obj, ctx)).Name(singletonEditingPortalName)
  142. return
  143. }
  144. func (b *EditingBuilder) editFormFor(obj interface{}, ctx *web.EventContext) h.HTMLComponent {
  145. msgr := MustGetMessages(ctx.R)
  146. id := ctx.R.FormValue(ParamID)
  147. if b.mb.singleton {
  148. id = vx.ObjectID(obj)
  149. }
  150. var buttonLabel = msgr.Create
  151. var disableUpdateBtn bool
  152. var title h.HTMLComponent
  153. title = h.Text(msgr.CreatingObjectTitle(
  154. i18n.T(ctx.R, ModelsI18nModuleKey, inflection.Singular(b.mb.label)),
  155. ))
  156. if len(id) > 0 {
  157. if obj == nil {
  158. var err error
  159. obj, err = b.Fetcher(b.mb.NewModel(), id, ctx)
  160. if err != nil {
  161. panic(err)
  162. }
  163. }
  164. disableUpdateBtn = b.mb.Info().Verifier().Do(PermUpdate).ObjectOn(obj).WithReq(ctx.R).IsAllowed() != nil
  165. buttonLabel = msgr.Update
  166. editingTitleText := msgr.EditingObjectTitle(
  167. i18n.T(ctx.R, ModelsI18nModuleKey, inflection.Singular(b.mb.label)),
  168. getPageTitle(obj, id))
  169. if b.editingTitleFunc != nil {
  170. title = b.editingTitleFunc(obj, editingTitleText, ctx)
  171. } else {
  172. title = h.Text(editingTitleText)
  173. }
  174. }
  175. if obj == nil {
  176. obj = b.mb.NewModel()
  177. }
  178. var notice h.HTMLComponent
  179. {
  180. var text string
  181. var color string
  182. if msg, ok := ctx.Flash.(string); ok {
  183. if len(msg) > 0 {
  184. text = msg
  185. color = "success"
  186. }
  187. }
  188. vErr, ok := ctx.Flash.(*web.ValidationErrors)
  189. if ok {
  190. gErr := vErr.GetGlobalError()
  191. if len(gErr) > 0 {
  192. text = gErr
  193. color = "error"
  194. }
  195. }
  196. if text != "" {
  197. showVar := fmt.Sprintf("id%d", uuid.New().ID())
  198. notice = VSnackbar().Top(true).Timeout(-1).Color(color).
  199. Attr("v-model", fmt.Sprintf("vars.%s", showVar)).
  200. Attr(web.InitContextVars, fmt.Sprintf(`{%s: true}`, showVar)).
  201. Children(
  202. h.Text(text),
  203. h.Template().Attr("v-slot:action", "{ attrs }").Children(
  204. VBtn("").Text(true).
  205. Attr("v-bind", "attrs").
  206. Attr("@click", fmt.Sprintf("vars.%s = false", showVar)).
  207. Children(VIcon("close")),
  208. ),
  209. )
  210. }
  211. }
  212. queries := ctx.Queries()
  213. if b.mb.singleton {
  214. queries.Add(ParamID, id)
  215. }
  216. updateBtn := VBtn(buttonLabel).
  217. Color("primary").
  218. Attr("@click", web.Plaid().
  219. EventFunc(actions.Update).
  220. Queries(queries).
  221. URL(b.mb.Info().ListingHref()).
  222. Go())
  223. if disableUpdateBtn {
  224. updateBtn = updateBtn.Disabled(disableUpdateBtn)
  225. } else {
  226. updateBtn = updateBtn.Attr(":disabled", "isFetching").
  227. Attr(":loading", "isFetching")
  228. }
  229. var actionButtons h.HTMLComponent = h.Components(
  230. VSpacer(),
  231. updateBtn,
  232. )
  233. if b.actionsFunc != nil {
  234. actionButtons = b.actionsFunc(obj, ctx)
  235. }
  236. var hiddenComps []h.HTMLComponent
  237. for _, hf := range b.hiddenFuncs {
  238. hiddenComps = append(hiddenComps, hf(obj, ctx))
  239. }
  240. formContent := h.Components(
  241. VCardText(
  242. h.Components(hiddenComps...),
  243. b.ToComponent(b.mb.Info(), obj, ctx),
  244. ),
  245. VCardActions(actionButtons),
  246. )
  247. var asideContent h.HTMLComponent = formContent
  248. if len(b.tabPanels) != 0 {
  249. var tabs []h.HTMLComponent
  250. for _, panelFunc := range b.tabPanels {
  251. value := panelFunc(obj, ctx)
  252. if value != nil {
  253. tabs = append(tabs, value)
  254. }
  255. }
  256. if len(tabs) != 0 {
  257. asideContent = VTabs(
  258. VTab(h.Text(msgr.FormTitle)),
  259. VTabItem(web.Scope(formContent).VSlot("{plaidForm}")),
  260. h.Components(tabs...),
  261. ).Class("v-tabs--fixed-tabs")
  262. }
  263. }
  264. if b.sidePanel != nil {
  265. sidePanel := b.sidePanel(ctx)
  266. if sidePanel != nil {
  267. asideContent = VContainer(
  268. VRow(
  269. VCol(asideContent).Cols(8),
  270. VCol(sidePanel).Cols(4),
  271. ),
  272. )
  273. }
  274. }
  275. overlayType := ctx.R.FormValue(ParamOverlay)
  276. closeBtnVarScript := closeRightDrawerVarScript
  277. if overlayType == actions.Dialog {
  278. closeBtnVarScript = closeDialogVarScript
  279. }
  280. return web.Scope(
  281. notice,
  282. h.If(!b.mb.singleton,
  283. VAppBar(
  284. VToolbarTitle("").Class("pl-2").
  285. Children(title),
  286. VSpacer(),
  287. VBtn("").Icon(true).Children(
  288. VIcon("close"),
  289. ).Attr("@click.stop", closeBtnVarScript),
  290. ).Color("white").Elevation(0).Dense(true),
  291. ),
  292. VSheet(
  293. VCard(asideContent).Flat(true),
  294. ).Class("pa-2"),
  295. ).VSlot("{ plaidForm }")
  296. }
  297. func (b *EditingBuilder) doDelete(ctx *web.EventContext) (r web.EventResponse, err1 error) {
  298. if b.mb.Info().Verifier().Do(PermDelete).WithReq(ctx.R).IsAllowed() != nil {
  299. ShowMessage(&r, perm.PermissionDenied.Error(), "warning")
  300. return
  301. }
  302. id := ctx.R.FormValue(ParamID)
  303. var obj = b.mb.NewModel()
  304. if len(id) > 0 {
  305. err := b.Deleter(obj, id, ctx)
  306. if err != nil {
  307. ShowMessage(&r, err.Error(), "warning")
  308. return
  309. }
  310. }
  311. if event := ctx.Queries().Get(ParamAfterDeleteEvent); event != "" {
  312. web.AppendVarsScripts(&r,
  313. "vars.deleteConfirmation = false",
  314. web.Plaid().
  315. EventFunc(event).
  316. Queries(ctx.Queries()).
  317. Go(),
  318. )
  319. } else {
  320. removeSelectQuery := web.Var(fmt.Sprintf(`{value: %s, add: false, remove: true}`, h.JSONString(id)))
  321. if isInDialogFromQuery(ctx) {
  322. u := fmt.Sprintf("%s?%s", b.mb.Info().ListingHref(), ctx.Queries().Get(ParamListingQueries))
  323. web.AppendVarsScripts(&r,
  324. "vars.deleteConfirmation = false",
  325. web.Plaid().
  326. URL(u).
  327. EventFunc(actions.UpdateListingDialog).
  328. MergeQuery(true).
  329. Queries(ctx.Queries()).
  330. Query(ParamSelectedIds, removeSelectQuery).
  331. Go(),
  332. )
  333. } else {
  334. // refresh current page
  335. // TODO: response location does not support `valueOp`
  336. // r.PushState = web.Location(nil).
  337. // MergeQuery(true).
  338. // Query(ParamSelectedIds, removeSelectQuery)
  339. web.AppendVarsScripts(&r,
  340. web.Plaid().
  341. PushState(true).
  342. MergeQuery(true).
  343. Query(ParamSelectedIds, removeSelectQuery).
  344. Go(),
  345. )
  346. }
  347. }
  348. return
  349. }
  350. func (b *EditingBuilder) FetchAndUnmarshal(id string, removeDeletedAndSort bool, ctx *web.EventContext) (obj interface{}, vErr web.ValidationErrors) {
  351. obj = b.mb.NewModel()
  352. if len(id) > 0 {
  353. var err1 error
  354. obj, err1 = b.Fetcher(obj, id, ctx)
  355. if err1 != nil {
  356. vErr.GlobalError(err1.Error())
  357. // b.UpdateOverlayContent(ctx, &r, obj, "", err1)
  358. return
  359. }
  360. }
  361. vErr = b.RunSetterFunc(ctx, removeDeletedAndSort, obj)
  362. return
  363. }
  364. func (b *EditingBuilder) doUpdate(
  365. ctx *web.EventContext,
  366. r *web.EventResponse,
  367. // will not close drawer/dialog
  368. silent bool,
  369. ) (err error) {
  370. id := ctx.R.FormValue(ParamID)
  371. usingB := b
  372. if b.mb.creating != nil && id == "" {
  373. usingB = b.mb.creating
  374. }
  375. obj, vErr := usingB.FetchAndUnmarshal(id, true, ctx)
  376. if vErr.HaveErrors() {
  377. usingB.UpdateOverlayContent(ctx, r, obj, "", &vErr)
  378. return &vErr
  379. }
  380. if len(id) > 0 {
  381. if b.mb.Info().Verifier().Do(PermUpdate).ObjectOn(obj).WithReq(ctx.R).IsAllowed() != nil {
  382. b.UpdateOverlayContent(ctx, r, obj, "", perm.PermissionDenied)
  383. return perm.PermissionDenied
  384. }
  385. } else {
  386. if b.mb.Info().Verifier().Do(PermCreate).ObjectOn(obj).WithReq(ctx.R).IsAllowed() != nil {
  387. b.UpdateOverlayContent(ctx, r, obj, "", perm.PermissionDenied)
  388. return perm.PermissionDenied
  389. }
  390. }
  391. if usingB.Validator != nil {
  392. if vErr = usingB.Validator(obj, ctx); vErr.HaveErrors() {
  393. usingB.UpdateOverlayContent(ctx, r, obj, "", &vErr)
  394. return &vErr
  395. }
  396. }
  397. err1 := usingB.Saver(obj, id, ctx)
  398. if err1 != nil {
  399. usingB.UpdateOverlayContent(ctx, r, obj, "", err1)
  400. return err1
  401. }
  402. overlayType := ctx.R.FormValue(ParamOverlay)
  403. script := closeRightDrawerVarScript
  404. if overlayType == actions.Dialog {
  405. script = closeDialogVarScript
  406. }
  407. if silent {
  408. script = ""
  409. }
  410. afterUpdateScript := ctx.R.FormValue(ParamOverlayAfterUpdateScript)
  411. if afterUpdateScript != "" {
  412. web.AppendVarsScripts(r, script, strings.NewReplacer(".go()",
  413. fmt.Sprintf(".query(%s, %s).go()",
  414. h.JSONString(ParamOverlayUpdateID),
  415. h.JSONString(vx.ObjectID(obj)),
  416. )).Replace(afterUpdateScript),
  417. )
  418. return
  419. }
  420. if isInDialogFromQuery(ctx) {
  421. web.AppendVarsScripts(r,
  422. web.Plaid().
  423. URL(ctx.R.RequestURI).
  424. EventFunc(actions.UpdateListingDialog).
  425. StringQuery(ctx.R.URL.Query().Get(ParamListingQueries)).
  426. Go(),
  427. )
  428. } else {
  429. r.PushState = web.Location(nil)
  430. }
  431. web.AppendVarsScripts(r, script)
  432. return
  433. }
  434. func (b *EditingBuilder) defaultUpdate(ctx *web.EventContext) (r web.EventResponse, err error) {
  435. uErr := b.doUpdate(ctx, &r, false)
  436. if uErr == nil {
  437. msgr := MustGetMessages(ctx.R)
  438. ShowMessage(&r, msgr.SuccessfullyUpdated, "")
  439. }
  440. return r, nil
  441. }
  442. func (b *EditingBuilder) SaveOverlayContent(
  443. ctx *web.EventContext,
  444. r *web.EventResponse,
  445. ) (err error) {
  446. return b.doUpdate(ctx, r, true)
  447. }
  448. func (b *EditingBuilder) RunSetterFunc(ctx *web.EventContext, removeDeletedAndSort bool, toObj interface{}) (vErr web.ValidationErrors) {
  449. if b.Setter != nil {
  450. b.Setter(toObj, ctx)
  451. }
  452. vErr = b.Unmarshal(toObj, b.mb.Info(), removeDeletedAndSort, ctx)
  453. return
  454. }
  455. func (b *EditingBuilder) UpdateOverlayContent(
  456. ctx *web.EventContext,
  457. r *web.EventResponse,
  458. obj interface{},
  459. successMessage string,
  460. err error,
  461. ) {
  462. ctx.Flash = err
  463. if err != nil {
  464. if _, ok := err.(*web.ValidationErrors); !ok {
  465. vErr := &web.ValidationErrors{}
  466. vErr.GlobalError(err.Error())
  467. ctx.Flash = vErr
  468. }
  469. }
  470. if ctx.Flash == nil {
  471. ctx.Flash = successMessage
  472. }
  473. overlayType := ctx.R.FormValue(ParamOverlay)
  474. p := rightDrawerContentPortalName
  475. if overlayType == actions.Dialog {
  476. p = dialogContentPortalName
  477. }
  478. if b.mb.singleton {
  479. p = singletonEditingPortalName
  480. }
  481. r.UpdatePortals = append(r.UpdatePortals, &web.PortalUpdate{
  482. Name: p,
  483. Body: b.editFormFor(obj, ctx),
  484. })
  485. }