luci-base: switch from server side to client side widget markup
authorJo-Philipp Wich <jo@mein.io>
Mon, 1 Apr 2019 14:09:41 +0000 (16:09 +0200)
committerJo-Philipp Wich <jo@mein.io>
Sun, 7 Jul 2019 13:36:24 +0000 (15:36 +0200)
Do not render standard widgets like checkboxes, select boxes,
text input fields etc. on the server side anymore but utilize
the ui.js primitives instead.

This avoids logic duplication between server side cbi templates
and JS widgets in the future.

Signed-off-by: Jo-Philipp Wich <jo@mein.io>
modules/luci-base/luasrc/cbi.lua
modules/luci-base/luasrc/view/cbi/dropdown.htm
modules/luci-base/luasrc/view/cbi/dynlist.htm
modules/luci-base/luasrc/view/cbi/fvalue.htm
modules/luci-base/luasrc/view/cbi/lvalue.htm
modules/luci-base/luasrc/view/cbi/mvalue.htm
modules/luci-base/luasrc/view/cbi/value.htm

index 971830fe81c4989c4907edd61b78873dbe3ef530..450e413916774669e9e7c4bca75247ab6c25c407 100644 (file)
@@ -1347,6 +1347,18 @@ function AbstractValue.deplist2json(self, section, deplist)
        return util.serialize_json(deps)
 end
 
+-- Serialize choices
+function AbstractValue.choices(self)
+       if type(self.keylist) == "table" and #self.keylist > 0 then
+               local i, k, v = nil, nil, {}
+               for i, k in ipairs(self.keylist) do
+                       v[k] = self.vallist[i] or k
+               end
+               return v
+       end
+       return nil
+end
+
 -- Generates the unique CBID
 function AbstractValue.cbid(self, section)
        return "cbid."..self.map.config.."."..section.."."..self.option
index 6f4b89905ba69c4f5876340ff863eee7bc4516e3..40bd8e953698ab963d3f1565386c9f3c2fd7aa0c 100644 (file)
@@ -1,54 +1,19 @@
 <%+cbi/valueheader%>
-
-<%-
-       local selected = { }
-
-       if self.multiple then
-               local val
-               for val in luci.util.imatch(self:cfgvalue(section)) do
-                       selected[val] = true
-               end
-       else
-               selected[self:cfgvalue(section)] = true
-       end
-
-       if not next(selected) and self.default then
-               selected[self.default] = true
-       end
--%>
-
-<div class="cbi-dropdown"<%=
-       attr("name", cbid) ..
-       attr("display-items", self.display or self.size or 3) ..
-       attr("dropdown-items", self.dropdown or self.display or self.size or 5) ..
-       attr("placeholder", self.placeholder or translate("-- please select --")) ..
-       ifattr(self.multiple, "multiple", "multiple") ..
-       ifattr(self.optional or self.rmempty, "optional", "optional")
-%>>
-       <ul>
-               <% local i, key; for i, key in pairs(self.keylist) do %>
-                       <li<%=
-                               attr("data-index", i) ..
-                               attr("data-depends", self:deplist2json(section, self.deplist[i])) ..
-                               attr("data-value", key) ..
-                               ifattr(selected[key], "selected", "selected")
-                       %>>
-                               <%=pcdata(self.vallist[i])%>
-                       </li>
-               <% end %>
-               <% if self.custom then %>
-                       <li>
-                               <input type="password" style="display:none" />
-                               <input class="create-item-input" type="text"<%=
-                                       attr("placeholder", self.custom ~= true and
-                                               self.custom or
-                                               (self.multiple and
-                                                       translate("Enter custom values") or
-                                                       translate("Enter custom value")))
-                               %> />
-                       </li>
-               <% end %>
-       </ul>
-</div>
-
+<div<%=attr("data-ui-widget", luci.util.serialize_json({
+       "Dropdown", self:cfgvalue(section), self:choices(), {
+               id = cbid,
+               name = cbid,
+               sort = self.keylist,
+               multi = self.multiple,
+               datatype = self.datatype,
+               optional = self.optional or self.rmempty,
+               readonly = self.readonly,
+               maxlength = self.maxlength,
+               placeholder = self.placeholder,
+               display_items = self.display or self.size or 3,
+               dropdown_items = self.dropdown or self.display or self.size or 5,
+               custom_placeholder = self.custom or
+                       (self.multiple and translate("Enter custom values") or translate("Enter custom value"))
+       }
+}))%>></div>
 <%+cbi/valuefooter%>
index fa7dbdb4189f54959e63ed7038c03f4cfc2dc685..d50328d79f262e71d9e582baa32201243b61945e 100644 (file)
@@ -1,13 +1,12 @@
 <%+cbi/valueheader%>
-<div<%=
-       attr("data-prefix", cbid) ..
-       attr("data-browser-path", self.default_path) ..
-       attr("data-dynlist", luci.util.serialize_json({
-               self.keylist, self.vallist,
-               self.datatype, self.optional or self.rmempty
-       })) ..
-       attr("data-values", luci.util.serialize_json(self:cfgvalue(section))) ..
-       ifattr(self.size, "data-size", self.size) ..
-       ifattr(self.placeholder, "data-placeholder", self.placeholder)
-%>></div>
+<div<%=attr("data-ui-widget", luci.util.serialize_json({
+       "DynamicList", self:cfgvalue(section), self:choices(), {
+               name = cbid,
+               size = self.size,
+               sort = self.keylist,
+               datatype = self.datatype,
+               optional = self.optional or self.rmempty,
+               placeholder = self.placeholder
+       }
+}))%>></div>
 <%+cbi/valuefooter%>
index 197d03cf31a78e333cb4ce0938b402bb733049d3..7f975b95e1ba2952b68fbe5af1c6ff1d38ce6c4e 100644 (file)
@@ -1,10 +1,12 @@
 <%+cbi/valueheader%>
-       <input type="hidden" value="1"<%=
-               attr("name", "cbi.cbe." .. self.config .. "." .. section .. "." .. self.option)
-       %> />
-       <input class="cbi-input-checkbox" data-update="click change" type="checkbox"<%=
-               attr("id", cbid) .. attr("name", cbid) .. attr("value", self.enabled or 1) ..
-               ifattr((self:cfgvalue(section) or self.default) == self.enabled, "checked", "checked")
-       %> />
-       <label<%= attr("for", cbid)%>></label>
+<div<%=attr("data-ui-widget", luci.util.serialize_json({
+       "Checkbox", self:cfgvalue(section) or self.default, {
+               id = cbid,
+               name = cbid,
+               readonly = self.readonly,
+               hiddenname = "cbi.cbe." .. self.config .. "." .. section .. "." .. self.option,
+               value_enabled = self.enabled or 1,
+               value_disabled = self.disabled or 0
+       }
+}))%>></div>
 <%+cbi/valuefooter%>
index 34d02eeca0cb4250be96fe003288409cc8f57dd2..e07648835668640ea198d4ce4fbacf68642e25d3 100644 (file)
@@ -1,43 +1,14 @@
-<%
-       local i, key
-       local br = self.orientation == "horizontal" and '&#160;' or '<br />'
-%>
-
 <%+cbi/valueheader%>
-<% if self.widget == "select" then %>
-       <select class="cbi-input-select" data-update="change"<%=
-               attr("id", cbid) ..
-               attr("name", cbid) ..
-               ifattr(self.size, "size")
-       %>>
-               <% for i, key in pairs(self.keylist) do -%>
-                       <option<%=
-                               attr("id", cbid.."-"..key) ..
-                               attr("value", key) ..
-                               attr("data-index", i) ..
-                               attr("data-depends", self:deplist2json(section, self.deplist[i])) ..
-                               ifattr(tostring(self:cfgvalue(section) or self.default) == key, "selected", "selected")
-                       %>><%=pcdata(self.vallist[i])%></option>
-               <%- end %>
-       </select>
-<% elseif self.widget == "radio" then %>
-       <div>
-               <% for i, key in pairs(self.keylist) do %>
-                       <label<%=
-                               attr("data-index", i) ..
-                               attr("data-depends", self:deplist2json(section, self.deplist[i]))
-                       %>>
-                               <input class="cbi-input-radio" data-update="click change" type="radio"<%=
-                                       attr("id", cbid.."-"..key) ..
-                                       attr("name", cbid) ..
-                                       attr("value", key) ..
-                                       ifattr((self:cfgvalue(section) or self.default) == key, "checked", "checked")
-                               %> />
-                               <label<%= attr("for", cbid.."-"..key)%>></label>
-                               <%=pcdata(self.vallist[i])%>
-                       </label>
-                       <% if i == self.size then write(br) end %>
-               <% end %>
-       </div>
-<% end %>
+<div<%=attr("data-ui-widget", luci.util.serialize_json({
+       "Select", self:cfgvalue(section), self:choices(), {
+               id = cbid,
+               name = cbid,
+               size = self.size,
+               sort = self.keylist,
+               widget = self.widget,
+               datatype = self.datatype,
+               optional = self.optional or self.rmempty,
+               placeholder = self.placeholder
+       }
+}))%>></div>
 <%+cbi/valuefooter%>
index db17450d27f09409b2a239153aad46dbcaa71fc0..4974a4ed288cda33d3127e79a887565614861cf3 100644 (file)
@@ -1,43 +1,16 @@
-<%
-       local i, key
-       local v = self:valuelist(section) or {}
--%>
-
 <%+cbi/valueheader%>
-<% if self.widget == "select" then %>
-       <select class="cbi-input-select" multiple="multiple" data-update="click change"<%=
-               attr("id", cbid) ..
-               attr("name", cbid) ..
-               ifattr(self.size, "size")
-       %>>
-               <% for i, key in pairs(self.keylist) do -%>
-                       <option<%=
-                               attr("id", cbid.."-"..key) ..
-                               attr("value", key) ..
-                               attr("data-index", i) ..
-                               attr("data-depends", self:deplist2json(section, self.deplist[i])) ..
-                               ifattr(luci.util.contains(v, key), "selected", "selected")
-                       %>><%=pcdata(self.vallist[i])%></option>
-               <%- end %>
-       </select>
-<% elseif self.widget == "checkbox" then %>
-       <div>
-               <% for i, key in pairs(self.keylist) do %>
-                       <label<%=
-                               attr("data-index", i) ..
-                               attr("data-depends", self:deplist2json(section, self.deplist[i]))
-                       %>>
-                               <input class="cbi-input-checkbox" type="checkbox" data-update="click change"<%=
-                                       attr("id", cbid.."-"..key) ..
-                                       attr("name", cbid) ..
-                                       attr("value", key) ..
-                                       ifattr(luci.util.contains(v, key), "checked", "checked")
-                               %> />
-                               <label<%= attr("for", cbid.."-"..key)%>></label>
-                               <%=pcdata(self.vallist[i])%>
-                       </label>
-                       <% if self.size and (i % self.size) == 0 then write('<br />') end %>
-               <% end %>
-       </div>
-<% end %>
+<div<%=attr("data-ui-widget", luci.util.serialize_json({
+       "Select", self:cfgvalues(section), self:choices(), {
+               id = cbid,
+               name = cbid,
+               size = self.size,
+               sort = self.keylist,
+               multi = true,
+               widget = self.widget,
+               datatype = self.datatype,
+               optional = self.optional or self.rmempty,
+               readonly = self.readonly,
+               placeholder = self.placeholder
+       }
+}))%>></div>
 <%+cbi/valuefooter%>
index 144853fd9f65c08415c90a05c38f687aeeb78af4..6060310b19b8531d91124ff139a8aefaa6560c36 100644 (file)
@@ -1,26 +1,35 @@
 <%+cbi/valueheader%>
-       <%- if self.password then -%>
-               <input type="password" style="position:absolute; left:-100000px" aria-hidden="true" tabindex="-1"<%=
-                       attr("name", "password." .. cbid)
-               %> />
-       <%- end -%>
-       <input data-update="change"<%=
-               attr("id", cbid) ..
-               attr("name", cbid) ..
-               attr("type", self.password and "password" or "text") ..
-               attr("class", self.password and "cbi-input-password" or "cbi-input-text") ..
-               attr("value", self:cfgvalue(section) or self.default) ..
-               ifattr(self.password, "autocomplete", "new-password") ..
-               ifattr(self.size, "size") ..
-               ifattr(self.placeholder, "placeholder") ..
-               ifattr(self.readonly, "readonly") ..
-               ifattr(self.maxlength, "maxlength") ..
-               ifattr(self.datatype, "data-type", self.datatype) ..
-               ifattr(self.datatype, "data-optional", self.optional or self.rmempty) ..
-               ifattr(self.combobox_manual, "data-manual", self.combobox_manual) ..
-               ifattr(#self.keylist > 0, "data-choices", { self.keylist, self.vallist })
-       %> />
-       <%- if self.password then -%>
-               <button class="cbi-button cbi-button-neutral" title="<%:Reveal/hide password%>" aria-label="<%:Reveal/hide password%>" onclick="var e = this.previousElementSibling; e.type = (e.type === 'password') ? 'text' : 'password'; event.preventDefault()">∗</button>
-       <% end %>
+
+<% local choices = self:choices()
+   if choices then %>
+       <div<%=attr("data-ui-widget", luci.util.serialize_json({
+               "Combobox", self:cfgvalue(section) or self.default, choices, {
+                       id = cbid,
+                       name = cbid,
+                       size = self.size,
+                       sort = self.keylist,
+                       datatype = self.datatype,
+                       optional = self.optional or self.rmempty,
+                       readonly = self.readonly,
+                       maxlength = self.maxlength,
+                       placeholder = self.placeholder,
+                       custom_placeholder = self.combobox_manual
+               }
+       }))%>></div>
+<% else %>
+       <div<%=attr("data-ui-widget", luci.util.serialize_json({
+               "Textfield", self:cfgvalue(section) or self.default, {
+                       id = cbid,
+                       name = cbid,
+                       size = self.size,
+                       datatype = self.datatype,
+                       optional = self.optional or self.rmempty,
+                       password = self.password,
+                       readonly = self.readonly,
+                       maxlength = self.maxlength,
+                       placeholder = self.placeholder
+               }
+       }))%>></div>
+<% end %>
+
 <%+cbi/valuefooter%>