listeditor.go 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. package presets
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "reflect"
  7. "strconv"
  8. "strings"
  9. "github.com/qor5/admin/presets/actions"
  10. . "github.com/qor5/ui/vuetify"
  11. "github.com/qor5/web"
  12. "github.com/sunfmin/reflectutils"
  13. h "github.com/theplant/htmlgo"
  14. )
  15. type ListEditorBuilder struct {
  16. fieldContext *FieldContext
  17. value interface{}
  18. displayFieldInSorter string
  19. addListItemRowEvent string
  20. removeListItemRowEvent string
  21. sortListItemsEvent string
  22. }
  23. type ListSorter struct {
  24. Items []ListSorterItem `json:"items"`
  25. }
  26. type ListSorterItem struct {
  27. Index int `json:"index"`
  28. Label string `json:"label"`
  29. }
  30. func NewListEditor(v *FieldContext) *ListEditorBuilder {
  31. return &ListEditorBuilder{
  32. fieldContext: v,
  33. addListItemRowEvent: actions.AddRowEvent,
  34. removeListItemRowEvent: actions.RemoveRowEvent,
  35. sortListItemsEvent: actions.SortEvent,
  36. }
  37. }
  38. func (b *ListEditorBuilder) Value(v interface{}) (r *ListEditorBuilder) {
  39. if v == nil {
  40. return b
  41. }
  42. if reflect.TypeOf(v).Kind() != reflect.Slice {
  43. panic("value must be slice")
  44. }
  45. b.value = v
  46. return b
  47. }
  48. func (b *ListEditorBuilder) DisplayFieldInSorter(v string) (r *ListEditorBuilder) {
  49. b.displayFieldInSorter = v
  50. return b
  51. }
  52. func (b *ListEditorBuilder) AddListItemRowEvnet(v string) (r *ListEditorBuilder) {
  53. if v == "" {
  54. return b
  55. }
  56. b.addListItemRowEvent = v
  57. return b
  58. }
  59. func (b *ListEditorBuilder) RemoveListItemRowEvent(v string) (r *ListEditorBuilder) {
  60. if v == "" {
  61. return b
  62. }
  63. b.removeListItemRowEvent = v
  64. return b
  65. }
  66. func (b *ListEditorBuilder) SortListItemsEvent(v string) (r *ListEditorBuilder) {
  67. if v == "" {
  68. return b
  69. }
  70. b.sortListItemsEvent = v
  71. return b
  72. }
  73. func (b *ListEditorBuilder) MarshalHTML(c context.Context) (r []byte, err error) {
  74. ctx := web.MustGetEventContext(c)
  75. formKey := b.fieldContext.FormKey
  76. var form h.HTMLComponent
  77. if b.value != nil {
  78. form = b.fieldContext.NestedFieldsBuilder.ToComponentForEach(b.fieldContext, b.value, ctx, func(obj interface{}, formKey string, content h.HTMLComponent, ctx *web.EventContext) h.HTMLComponent {
  79. return VCard(
  80. h.If(!b.fieldContext.Disabled,
  81. VBtn("Delete").Icon(true).Class("float-right ma-2").
  82. Children(
  83. VIcon("delete"),
  84. ).Attr("@click", web.Plaid().
  85. URL(b.fieldContext.ModelInfo.ListingHref()).
  86. EventFunc(b.removeListItemRowEvent).
  87. Queries(ctx.Queries()).
  88. Query(ParamID, ctx.R.FormValue(ParamID)).
  89. Query(ParamOverlay, ctx.R.FormValue(ParamOverlay)).
  90. Query(ParamRemoveRowFormKey, formKey).
  91. Go()),
  92. ),
  93. content,
  94. ).Class("mx-0 mb-2 px-4 pb-0 pt-4").Outlined(true)
  95. })
  96. }
  97. isSortStart := ctx.R.FormValue(ParamIsStartSort) == "1" && ctx.R.FormValue(ParamSortSectionFormKey) == formKey
  98. haveSorterIcon := true
  99. var sorter h.HTMLComponent
  100. var sorterData ListSorter
  101. if b.value != nil {
  102. deletedIndexes := ContextModifiedIndexesBuilder(ctx)
  103. deletedIndexes.SortedForEach(b.value, formKey, func(obj interface{}, i int) {
  104. if deletedIndexes.DeletedContains(b.fieldContext.FormKey, i) {
  105. return
  106. }
  107. var label = ""
  108. if b.displayFieldInSorter != "" {
  109. label = fmt.Sprint(reflectutils.MustGet(obj, b.displayFieldInSorter))
  110. } else {
  111. label = fmt.Sprintf("Item %d", i)
  112. }
  113. sorterData.Items = append(sorterData.Items, ListSorterItem{Label: label, Index: i})
  114. })
  115. }
  116. if len(sorterData.Items) < 2 {
  117. haveSorterIcon = false
  118. }
  119. if haveSorterIcon && isSortStart {
  120. sorter = VCard(VList(
  121. h.Tag("vx-draggable").Attr("v-model", "locals.items", "draggable", ".item", "animation", "300").Children(
  122. h.Div(
  123. VListItem(
  124. VListItemIcon(VIcon("drag_handle")),
  125. VListItemContent(
  126. VListItemTitle(h.Text("{{item.label}}")),
  127. ),
  128. ),
  129. VDivider().Attr("v-if", "index < locals.items.length - 1", ":key", "index"),
  130. ).Attr("v-for", "(item, index) in locals.items", ":key", "item.index", "class", "item"),
  131. ),
  132. ).Class("pa-0")).Outlined(true).Class("mx-0 mt-1 mb-4")
  133. }
  134. return h.Div(
  135. web.Scope(
  136. h.If(!b.fieldContext.Disabled,
  137. h.Div(
  138. h.Label(b.fieldContext.Label).Class("v-label theme--light text-caption"),
  139. VSpacer(),
  140. h.If(haveSorterIcon,
  141. h.If(!isSortStart,
  142. VBtn("SortStart").Icon(true).Children(
  143. VIcon("sort"),
  144. ).
  145. Class("mt-n4").
  146. Attr("@click",
  147. web.Plaid().
  148. URL(b.fieldContext.ModelInfo.ListingHref()).
  149. EventFunc(b.sortListItemsEvent).
  150. Queries(ctx.Queries()).
  151. Query(ParamID, ctx.R.FormValue(ParamID)).
  152. Query(ParamOverlay, ctx.R.FormValue(ParamOverlay)).
  153. Query(ParamSortSectionFormKey, b.fieldContext.FormKey).
  154. Query(ParamIsStartSort, "1").
  155. Go(),
  156. ),
  157. ).Else(
  158. VBtn("SortDone").Icon(true).Children(
  159. VIcon("done"),
  160. ).
  161. Class("mt-n4").
  162. Attr("@click",
  163. web.Plaid().
  164. URL(b.fieldContext.ModelInfo.ListingHref()).
  165. EventFunc(b.sortListItemsEvent).
  166. Queries(ctx.Queries()).
  167. Query(ParamID, ctx.R.FormValue(ParamID)).
  168. Query(ParamOverlay, ctx.R.FormValue(ParamOverlay)).
  169. Query(ParamSortSectionFormKey, b.fieldContext.FormKey).
  170. FieldValue(ParamSortResultFormKey, web.Var("JSON.stringify(locals.items)")).
  171. Query(ParamIsStartSort, "0").
  172. Go(),
  173. ),
  174. ),
  175. ),
  176. ).Class("d-flex align-end"),
  177. ),
  178. sorter,
  179. h.Div(
  180. form,
  181. h.If(!b.fieldContext.Disabled,
  182. VBtn("Add row").
  183. Text(true).
  184. Color("primary").
  185. Attr("@click", web.Plaid().
  186. URL(b.fieldContext.ModelInfo.ListingHref()).
  187. EventFunc(b.addListItemRowEvent).
  188. Queries(ctx.Queries()).
  189. Query(ParamID, ctx.R.FormValue(ParamID)).
  190. Query(ParamOverlay, ctx.R.FormValue(ParamOverlay)).
  191. Query(ParamAddRowFormKey, b.fieldContext.FormKey).
  192. Go(),
  193. ),
  194. ),
  195. ).Attr("v-show", h.JSONString(!isSortStart)).
  196. Class("mt-1 mb-4"),
  197. ).Init(h.JSONString(sorterData)).VSlot("{ locals }"),
  198. ).MarshalHTML(c)
  199. }
  200. func addListItemRow(mb *ModelBuilder) web.EventFunc {
  201. return func(ctx *web.EventContext) (r web.EventResponse, err error) {
  202. me := mb.Editing()
  203. obj, _ := me.FetchAndUnmarshal(ctx.R.FormValue(ParamID), false, ctx)
  204. formKey := ctx.R.FormValue(ParamAddRowFormKey)
  205. t := reflectutils.GetType(obj, formKey+"[0]")
  206. newVal := reflect.New(t.Elem()).Interface()
  207. err = reflectutils.Set(obj, formKey+"[]", newVal)
  208. if err != nil {
  209. panic(err)
  210. }
  211. me.UpdateOverlayContent(ctx, &r, obj, "", nil)
  212. return
  213. }
  214. }
  215. func removeListItemRow(mb *ModelBuilder) web.EventFunc {
  216. return func(ctx *web.EventContext) (r web.EventResponse, err error) {
  217. me := mb.Editing()
  218. obj, _ := me.FetchAndUnmarshal(ctx.R.FormValue(ParamID), false, ctx)
  219. formKey := ctx.R.FormValue(ParamRemoveRowFormKey)
  220. lb := strings.LastIndex(formKey, "[")
  221. sliceField := formKey[0:lb]
  222. strIndex := formKey[lb+1 : strings.LastIndex(formKey, "]")]
  223. var index int
  224. index, err = strconv.Atoi(strIndex)
  225. if err != nil {
  226. return
  227. }
  228. ContextModifiedIndexesBuilder(ctx).AppendDeleted(sliceField, index)
  229. me.UpdateOverlayContent(ctx, &r, obj, "", nil)
  230. return
  231. }
  232. }
  233. func sortListItems(mb *ModelBuilder) web.EventFunc {
  234. return func(ctx *web.EventContext) (r web.EventResponse, err error) {
  235. me := mb.Editing()
  236. obj, _ := me.FetchAndUnmarshal(ctx.R.FormValue(ParamID), false, ctx)
  237. sortSectionFormKey := ctx.R.FormValue(ParamSortSectionFormKey)
  238. isStartSort := ctx.R.FormValue(ParamIsStartSort)
  239. if isStartSort != "1" {
  240. sortResult := ctx.R.FormValue(ParamSortResultFormKey)
  241. var result []ListSorterItem
  242. err = json.Unmarshal([]byte(sortResult), &result)
  243. if err != nil {
  244. return
  245. }
  246. var indexes []string
  247. for _, i := range result {
  248. indexes = append(indexes, fmt.Sprint(i.Index))
  249. }
  250. ContextModifiedIndexesBuilder(ctx).SetSorted(sortSectionFormKey, indexes)
  251. }
  252. me.UpdateOverlayContent(ctx, &r, obj, "", nil)
  253. return
  254. }
  255. }