diff --git a/CHANGELOG.md b/CHANGELOG.md index c6206b5..975e61c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,9 @@ -# v0.13.0 +# v0.16.0 +* Can use ListAdapter as "content" property value of ListLayout +* The IsListItemEnabled method of the ListAdapter interface has been made optional +* Bug fixing + +# v0.15.0 * Added "data-list" property * Bug fixing diff --git a/README-ru.md b/README-ru.md index a62bb90..a4635a1 100644 --- a/README-ru.md +++ b/README-ru.md @@ -2320,6 +2320,42 @@ ListLayout является контейнером, реализующим ин Элементы в данном контейнере располагаются в виде списка. Расположением дочерних элементов можно управлять. Для этого ListLayout имеет ряд свойств +### "content" + +Свойство "content" (константа Content) определяет массив дочерних View. +Данное свойство унаследовано от ViewsContainer. +Также как и для ViewsContainer данному свойству можно присваивать следующие типы данных: + +* View (преобразуется во []View содержащий один View); +* []View; +* string (преобразуется во []View содержащий один TextView); +* []string (преобразуется во []View содержащий TextView); +* []any - данный массив должен содержать только View и string (преобразуется в TextView). + +Однако кроме этих типов данных свойству "content" ListLayout может быть назначена реализация интерфейса ListAdapter. + +ListAdapter используется для создания дочерних View и объявлен как + + type ListAdapter interface { + ListSize() int + ListItem(index int, session Session) View + } + +Соответственно функции этого интерфейса должны возвращать количество элементов и View i-го элемента. + +ListAdapter создает дочерние View в момент установки свойства "content". +Для пересоздания дочерних элементов ListLayout имеет свойство UpdateContent(). +Данный метод удаляет все дочерние View и создает их заново используя ListAdapter. + +Внимание! При вызове метода UpdateContent() данные из старых View не переносятся в заново создаваемые. +Вы должны сделать это в ручную. + +Если свойство "content" присвоен не ListAdapter, то метод UpdateContent() ничего не делает. + +Вызвать метод UpdateContent можно также с помощью глобальной функции + + func UpdateContent(view View, subviewID ...string) + ### "orientation" Свойство "orientation" (константа Orientation) типа int задает то как дочерние элементы будут @@ -3606,11 +3642,14 @@ ListView реализован на основе ListLayout и поэтому о type ListAdapter interface { ListSize() int ListItem(index int, session Session) View - IsListItemEnabled(index int) bool } -Соответственно функции этого интерфейса должны возвращать количество элементов, View i-го элемента и -статус i-го элемента разрешен/запрещен. +Соответственно функции этого интерфейса должны возвращать количество элементов и View i-го элемента. + +Кроме этих двух обязательных методов может быть определен третий опциональный который задает статус i-го элемента разрешен/запрещен. +Данный метод объявлен как + + IsListItemEnabled(index int) bool Вы можете реализовать этот интерфейс сами или воспользоваться вспомогательными функциями: diff --git a/README.md b/README.md index 1c4c5ac..b34bf37 100644 --- a/README.md +++ b/README.md @@ -2294,6 +2294,43 @@ ListLayout is a container that implements the ViewsContainer interface. To creat Items in this container are arranged as a list. The position of the children can be controlled. For this, ListLayout has a number of properties +### "content" property + +The "content" property (Content constant) defines an array of child Views. +This property is inherited from ViewsContainer. +Just like for ViewsContainer, this property can be assigned the following data types: + +* View (converts to []View containing one View); +* []View; +* string (converts to []View containing one TextView); +* []string (converts to []View containing TextView); +* []any - this array must contain only View and string. + +However, in addition to these data types, the "content" property of a ListLayout +can be assigned an implementation of the ListAdapter interface. + +ListAdapter is used to create child Views and is declared as + + type ListAdapter interface { + ListSize() int + ListItem(index int, session Session) View + } + +Accordingly, the functions of this interface must return the number of elements and View of the i-th element. + +ListAdapter creates child Views when the "content" property is set. +To recreate child elements, ListLayout has the UpdateContent() property. +This method deletes all child Views and creates them again using the ListAdapter. + +Attention! When calling the UpdateContent() method, data from old Views is not transferred to newly created ones. +You must do this manually. + +If the "content" property is not assigned to the ListAdapter, then the UpdateContent() method does nothing. + +You can also call the UpdateContent method using the global function + + func UpdateContent(view View, subviewID ...string) + ### "orientation" property The "orientation" int property (Orientation constant) specifies how the children will be positioned @@ -3573,9 +3610,13 @@ The main value of the "items" property is the ListAdapter interface: type ListAdapter interface { ListSize() int ListItem(index int, session Session) View - IsListItemEnabled(index int) bool } +In addition to these two mandatory methods, a third optional one can be defined which specifies the status of the i-th element as allowed/disabled. +This method is declared as + + IsListItemEnabled(index int) bool + Accordingly, the functions of this interface must return the number of elements, the View of the i-th element and the status of the i-th element (allowed/denied). diff --git a/listAdapter.go b/listAdapter.go index e554d69..aa28ed2 100644 --- a/listAdapter.go +++ b/listAdapter.go @@ -7,7 +7,10 @@ type ListAdapter interface { // ListItem creates a View of a list item at the given index ListItem(index int, session Session) View +} +// ListItemEnabled implements the optional method of ListAdapter interface +type ListItemEnabled interface { // IsListItemEnabled returns the status (enabled/disabled) of a list item at the given index IsListItemEnabled(index int) bool } diff --git a/listLayout.go b/listLayout.go index c244e75..294df07 100644 --- a/listLayout.go +++ b/listLayout.go @@ -30,10 +30,14 @@ const ( // ListLayout - list-container of View type ListLayout interface { ViewsContainer + // UpdateContent updates child Views if the "content" property value is set to ListAdapter, + // otherwise does nothing + UpdateContent() } type listLayoutData struct { viewsContainerData + adapter ListAdapter } // NewListLayout create new ListLayout object and return it @@ -94,11 +98,16 @@ func (listLayout *listLayoutData) Remove(tag string) { } func (listLayout *listLayoutData) remove(tag string) { - if tag == Gap { + switch tag { + case Gap: listLayout.remove(ListRowGap) listLayout.remove(ListColumnGap) return + + case Content: + listLayout.adapter = nil } + listLayout.viewsContainerData.remove(tag) if listLayout.created { switch tag { @@ -118,8 +127,18 @@ func (listLayout *listLayoutData) set(tag string, value any) bool { return true } - if tag == Gap { + switch tag { + case Gap: return listLayout.set(ListRowGap, value) && listLayout.set(ListColumnGap, value) + + case Content: + if adapter, ok := value.(ListAdapter); ok { + listLayout.adapter = adapter + listLayout.UpdateContent() + // TODO + return true + } + listLayout.adapter = nil } if listLayout.viewsContainerData.set(tag, value) { @@ -143,6 +162,33 @@ func (listLayout *listLayoutData) htmlSubviews(self View, buffer *strings.Builde } } +func (listLayout *listLayoutData) UpdateContent() { + if adapter := listLayout.adapter; adapter != nil { + listLayout.views = []View{} + + session := listLayout.session + htmlID := listLayout.htmlID() + isDisabled := IsDisabled(listLayout) + + count := adapter.ListSize() + for i := 0; i < count; i++ { + if view := adapter.ListItem(i, session); view != nil { + view.setParentID(htmlID) + if isDisabled { + view.Set(Disabled, true) + } + listLayout.views = append(listLayout.views, view) + } + } + + if listLayout.created { + updateInnerHTML(htmlID, session) + } + + listLayout.propertyChangedEvent(Content) + } +} + // GetListVerticalAlign returns the vertical align of a ListLayout or ListView sibview: // TopAlign (0), BottomAlign (1), CenterAlign (2), or StretchAlign (3) // If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. @@ -198,3 +244,22 @@ func GetListRowGap(view View, subviewID ...string) SizeUnit { func GetListColumnGap(view View, subviewID ...string) SizeUnit { return sizeStyledProperty(view, subviewID, ListColumnGap, false) } + +// UpdateContent updates child Views of ListLayout subview if the "content" property value is set to ListAdapter, +// otherwise does nothing. +// If the second argument (subviewID) is not specified or it is "" then the first argument (view) updates. +func UpdateContent(view View, subviewID ...string) { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) + } + + if view != nil { + switch view := view.(type) { + case ListLayout: + view.UpdateContent() + + case ListView: + view.ReloadListViewData() + } + } +} diff --git a/listView.go b/listView.go index b32407e..336d0e7 100644 --- a/listView.go +++ b/listView.go @@ -790,8 +790,10 @@ func (listView *listViewData) checkboxSubviews(buffer *strings.Builder, checkbox } buffer.WriteString(`" onclick="listItemClickEvent(this, event)" data-left="0" data-top="0" data-width="0" data-height="0" style="display: grid; justify-items: stretch; align-items: stretch;`) listView.itemSize(buffer) - if !listView.adapter.IsListItemEnabled(i) { - buffer.WriteString(`" data-disabled="1`) + if ext, ok := listView.adapter.(ListItemEnabled); ok { + if !ext.IsListItemEnabled(i) { + buffer.WriteString(`" data-disabled="1`) + } } buffer.WriteString(`">`) buffer.WriteString(itemDiv) @@ -849,8 +851,10 @@ func (listView *listViewData) noneCheckboxSubviews(buffer *strings.Builder) { } buffer.WriteString(`" `) buffer.WriteString(itemStyle) - if !listView.adapter.IsListItemEnabled(i) { - buffer.WriteString(` data-disabled="1"`) + if ext, ok := listView.adapter.(ListItemEnabled); ok { + if !ext.IsListItemEnabled(i) { + buffer.WriteString(` data-disabled="1"`) + } } buffer.WriteString(`>`) diff --git a/viewsContainer.go b/viewsContainer.go index facd0e3..bbae50b 100644 --- a/viewsContainer.go +++ b/viewsContainer.go @@ -198,7 +198,7 @@ func (container *viewsContainerData) set(tag string, value any) bool { switch tag { case Content: - // do nothing + return container.setContent(value) case Disabled: oldDisabled := IsDisabled(container) @@ -215,11 +215,12 @@ func (container *viewsContainerData) set(tag string, value any) bool { return true } return false - - default: - return container.viewData.set(tag, value) } + return container.viewData.set(tag, value) +} + +func (container *viewsContainerData) setContent(value any) bool { session := container.Session() switch value := value.(type) { case View: @@ -249,7 +250,7 @@ func (container *viewsContainerData) set(tag string, value any) bool { views = append(views, viewFromTextValue(v, session)) default: - notCompatibleType(tag, value) + notCompatibleType(Content, value) return false } } @@ -276,13 +277,17 @@ func (container *viewsContainerData) set(tag string, value any) bool { container.views = views default: - notCompatibleType(tag, value) + notCompatibleType(Content, value) return false } htmlID := container.htmlID() + isDisabled := IsDisabled(container) for _, view := range container.views { view.setParentID(htmlID) + if isDisabled { + view.Set(Disabled, true) + } } if container.created {