) and "div" (just using  tags) are supported. For each column several options must be specified.
jbe/bsw@0: 
jbe/bsw@0: --]]--
jbe/bsw@0: 
jbe/bsw@0: -- TODO: documentation of the prefix option
jbe/bsw@0: -- TODO: check short descriptions of fields in documentation
jbe/bsw@0: -- TODO: field_attr is used for the OUTER html tag's attributes, while attr is used for the INNER html tag's attributes (produced by ui.field.*), is that okay?
jbe/bsw@0: -- TODO: use field information of record class, if no columns are given
jbe/bsw@0: -- TODO: callback to set row attr's for a specific row
jbe/bsw@0: 
jbe/bsw@0: function ui.list(args)
jbe/bsw@0:   local args = args or {}
jbe/bsw@0:   local label     = args.label
jbe/bsw@0:   local list_type = args.style or "table"
jbe/bsw@0:   local prefix    = args.prefix
jbe/bsw@0:   local records   = assert(args.records, "ui.list{...} needs records.")
jbe/bsw@0:   local columns   = assert(args.columns, "ui.list{...} needs column definitions.")
jbe/bsw@0:   local outer_attr = table.new(args.attr)
jbe/bsw@0:   local header_existent = false
jbe/bsw@0:   for idx, column in ipairs(columns) do
jbe/bsw@0:     if column.label then
jbe/bsw@0:       header_existent = true
jbe/bsw@0:       break
jbe/bsw@0:     end
jbe/bsw@0:   end
jbe/bsw@0:   local slot_state = slot.get_state_table()
jbe@244:   local outer_tag, head_tag, head_tag2, label_tag, body_tag, row_tag, field_tag
jbe/bsw@0:   if list_type == "table" then
jbe/bsw@0:     outer_tag = "table"
jbe/bsw@0:     head_tag  = "thead"
jbe/bsw@0:     head_tag2 = "tr"
jbe/bsw@0:     label_tag = "th"
jbe/bsw@0:     body_tag  = "tbody"
jbe/bsw@0:     row_tag   = "tr"
jbe/bsw@0:     field_tag = "td"
jbe/bsw@0:   elseif list_type == "ulli" then
jbe/bsw@0:     outer_tag = "div"
jbe/bsw@0:     head_tag  = "div"
jbe/bsw@0:     label_tag = "div"
jbe/bsw@0:     body_tag  = "ul"
jbe/bsw@0:     row_tag   = "li"
jbe/bsw@0:     field_tag = "td"
jbe/bsw@0:   elseif list_type == "div" then
jbe/bsw@0:     outer_tag = "div"
jbe/bsw@0:     head_tag  = "div"
jbe/bsw@0:     label_tag = "div"
jbe/bsw@0:     body_tag  = "div"
jbe/bsw@0:     row_tag   = "div"
jbe/bsw@0:     field_tag = "div"
jbe/bsw@0:   else
jbe/bsw@0:     error("Unknown list type specified for ui.list{...}.")
jbe/bsw@0:   end
jbe/bsw@0:   outer_attr.class = outer_attr.class or "ui_list"
jbe/bsw@0:   ui.container{
jbe/bsw@0:     auto_args = args,
jbe/bsw@0:     content   = function()
jbe/bsw@0:       ui.tag{
jbe/bsw@0:         tag     = outer_tag,
jbe/bsw@0:         attr    = outer_attr,
jbe/bsw@0:         content = function()
jbe/bsw@0:           if header_existent then
jbe/bsw@0:             ui.tag{
jbe/bsw@0:               tag     = head_tag,
jbe/bsw@0:               attr    = { class = "ui_list_head" },
jbe/bsw@0:               content = function()
jbe/bsw@0:                 local function header_content()
jbe/bsw@0:                   for idx, column in ipairs(columns) do
jbe/bsw@0:                     if column.ui_field_type ~= "hidden" then
jbe/bsw@0:                       local label_attr = table.new(column.label_attr)
jbe/bsw@0:                       label_attr.class =
jbe/bsw@0:                         label_attr.class or { class = "ui_list_label" }
jbe/bsw@0:                       ui.tag{
jbe/bsw@0:                         tag     = label_tag,
jbe/bsw@0:                         attr    = label_attr,
jbe/bsw@0:                         content = column.label or ""
jbe/bsw@0:                       }
jbe/bsw@0:                     end
jbe/bsw@0:                   end
jbe/bsw@0:                 end
jbe/bsw@0:                 if head_tag2 then
jbe/bsw@0:                   ui.tag{ tag = head_tag2, content = header_content }
jbe/bsw@0:                 else
jbe/bsw@0:                   header_content()
jbe/bsw@0:                 end
jbe/bsw@0:               end
jbe/bsw@0:             }
jbe/bsw@0:           end
jbe/bsw@0:           ui.tag{
jbe/bsw@0:             tag     = body_tag,
jbe/bsw@0:             attr    = { class = "ui_list_body" },
jbe/bsw@0:             content = function()
jbe/bsw@0:               for record_idx, record in ipairs(records) do
jbe/bsw@0:                 local row_class
jbe/bsw@0:                 if record_idx % 2 == 0 then
jbe/bsw@0:                   row_class = "ui_list_row ui_list_even"
jbe/bsw@0:                 else
jbe/bsw@0:                   row_class = "ui_list_row ui_list_odd"
jbe/bsw@0:                 end
jbe/bsw@0:                 ui.tag{
jbe/bsw@0:                   tag     = row_tag,
jbe/bsw@0:                   attr    = { class = row_class },
jbe/bsw@0:                   content = function()
jbe/bsw@0:                     local old_html_name_prefix, old_form_record
jbe/bsw@0:                     if prefix then
jbe/bsw@0:                       old_html_name_prefix        = slot_state.html_name_prefix
jbe/bsw@0:                       old_form_record             = slot_state.form_record
jbe/bsw@0:                       slot_state.html_name_prefix = prefix .. "[" .. record_idx .. "]"
jbe/bsw@0:                       slot_state.form_record      = record
jbe/bsw@0:                     end
jbe/bsw@0:                     local first_column = true
jbe/bsw@0:                     for column_idx, column in ipairs(columns) do
jbe/bsw@0:                       if column.ui_field_type ~= "hidden" then
jbe/bsw@0:                         local field_attr = table.new(column.field_attr)
jbe/bsw@0:                         field_attr.class =
jbe/bsw@0:                           field_attr.class or { class = "ui_list_field" }
jbe/bsw@0:                         local field_content
jbe/bsw@0:                         if column.content then
jbe/bsw@0:                           field_content = function()
jbe/bsw@0:                             return column.content(record)
jbe/bsw@0:                           end
jbe/bsw@0:                         elseif column.name then
jbe/bsw@0:                           if column.ui_field_type then
jbe/bsw@0:                             local ui_field_func = ui.field[column.ui_field_type]
jbe/bsw@0:                             if not ui_field_func then
jbe/bsw@0:                               error('Unknown ui_field_type "' .. column.ui_field_type .. '".')
jbe/bsw@0:                             end
jbe/bsw@0:                             local ui_field_options = table.new(column)
jbe/bsw@0:                             ui_field_options.record = record
jbe/bsw@0:                             ui_field_options.label  = nil
jbe/bsw@0:                             if not prefix and ui_field_options.readonly == nil then
jbe/bsw@0:                               ui_field_options.readonly = true
jbe/bsw@0:                             end
jbe/bsw@0:                             field_content = function()
jbe/bsw@0:                               return ui.field[column.ui_field_type](ui_field_options)
jbe/bsw@0:                             end
jbe/bsw@0:                           elseif column.format then
jbe/bsw@0:                             local formatter = format[column.format]
jbe/bsw@0:                             if not formatter then
jbe/bsw@0:                               error('Unknown format "' .. column.format .. '".')
jbe/bsw@0:                             end
jbe/bsw@0:                             field_content = formatter(
jbe/bsw@0:                               record[column.name], column.format_options
jbe/bsw@0:                             )
jbe/bsw@0:                           else
jbe/bsw@0:                             field_content = function()
jbe/bsw@0:                               return ui.autofield{
jbe/bsw@0:                                 record    = record,
jbe/bsw@0:                                 name      = column.name,
jbe/bsw@0:                                 html_name = column.html_name
jbe/bsw@0:                               }
jbe/bsw@0:                             end
jbe/bsw@0:                           end
jbe/bsw@0:                         else
jbe/bsw@0:                           error("Each column needs either a 'content' or a 'name'.")
jbe/bsw@0:                         end
jbe/bsw@0:                         local extended_field_content
jbe/bsw@0:                         if first_column then
jbe/bsw@0:                           first_column = false
jbe/bsw@0:                           extended_field_content = function()
jbe/bsw@0:                             for column_idx, column in ipairs(columns) do
jbe/bsw@0:                               if column.ui_field_type == "hidden" then
jbe/bsw@0:                                 local ui_field_options = table.new(column)
jbe/bsw@0:                                 ui_field_options.record = record
jbe/bsw@0:                                 ui_field_options.label  = nil
jbe/bsw@0:                                 if not prefix and ui_field_options.readonly == nil then
jbe/bsw@0:                                   ui_field_options.readonly = true
jbe/bsw@0:                                 end
jbe/bsw@0:                                 ui.field.hidden(ui_field_options)
jbe/bsw@0:                               end
jbe/bsw@0:                             end
jbe/bsw@0:                             field_content()
jbe/bsw@0:                           end
jbe/bsw@0:                         else
jbe/bsw@0:                           extended_field_content = field_content
jbe/bsw@0:                         end
jbe/bsw@0:                         ui.tag{
jbe/bsw@0:                           tag     = field_tag,
jbe/bsw@0:                           attr    = field_attr,
jbe/bsw@0:                           content = extended_field_content
jbe/bsw@0:                         }
jbe/bsw@0:                       end
jbe/bsw@0:                     end
jbe/bsw@0:                     if prefix then
jbe/bsw@0:                       slot_state.html_name_prefix = old_html_name_prefix
jbe/bsw@0:                       slot_state.form_record      = old_form_record
jbe/bsw@0:                     end
jbe/bsw@0:                   end
jbe/bsw@0:                 }
jbe/bsw@0:               end
jbe/bsw@0:             end
jbe/bsw@0:           }
jbe/bsw@0:         end
jbe/bsw@0:       }
jbe/bsw@0:     end
jbe/bsw@0:   }
jbe/bsw@0:   if prefix then
jbe/bsw@0:     -- ui.field.hidden is used instead of ui.hidden_field to suppress output in case of read-only mode.
jbe/bsw@0:     ui.field.hidden{
jbe/bsw@0:       html_name = prefix .. "[len]",
jbe/bsw@0:       value     = #records
jbe/bsw@0:     }
jbe/bsw@0:   end
jbe/bsw@0: end