luci-base: switch from server side to client side widget markup

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>
This commit is contained in:
Jo-Philipp Wich 2019-04-01 16:09:41 +02:00
parent 808b9f36eb
commit 9c7eb1decd
7 changed files with 108 additions and 177 deletions

View file

@ -1347,6 +1347,18 @@ function AbstractValue.deplist2json(self, section, deplist)
return util.serialize_json(deps) return util.serialize_json(deps)
end 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 -- Generates the unique CBID
function AbstractValue.cbid(self, section) function AbstractValue.cbid(self, section)
return "cbid."..self.map.config.."."..section.."."..self.option return "cbid."..self.map.config.."."..section.."."..self.option

View file

@ -1,54 +1,19 @@
<%+cbi/valueheader%> <%+cbi/valueheader%>
<div<%=attr("data-ui-widget", luci.util.serialize_json({
<%- "Dropdown", self:cfgvalue(section), self:choices(), {
local selected = { } id = cbid,
name = cbid,
if self.multiple then sort = self.keylist,
local val multi = self.multiple,
for val in luci.util.imatch(self:cfgvalue(section)) do datatype = self.datatype,
selected[val] = true optional = self.optional or self.rmempty,
end readonly = self.readonly,
else maxlength = self.maxlength,
selected[self:cfgvalue(section)] = true placeholder = self.placeholder,
end display_items = self.display or self.size or 3,
dropdown_items = self.dropdown or self.display or self.size or 5,
if not next(selected) and self.default then custom_placeholder = self.custom or
selected[self.default] = true (self.multiple and translate("Enter custom values") or translate("Enter custom value"))
end }
-%> }))%>></div>
<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>
<%+cbi/valuefooter%> <%+cbi/valuefooter%>

View file

@ -1,13 +1,12 @@
<%+cbi/valueheader%> <%+cbi/valueheader%>
<div<%= <div<%=attr("data-ui-widget", luci.util.serialize_json({
attr("data-prefix", cbid) .. "DynamicList", self:cfgvalue(section), self:choices(), {
attr("data-browser-path", self.default_path) .. name = cbid,
attr("data-dynlist", luci.util.serialize_json({ size = self.size,
self.keylist, self.vallist, sort = self.keylist,
self.datatype, self.optional or self.rmempty datatype = self.datatype,
})) .. optional = self.optional or self.rmempty,
attr("data-values", luci.util.serialize_json(self:cfgvalue(section))) .. placeholder = self.placeholder
ifattr(self.size, "data-size", self.size) .. }
ifattr(self.placeholder, "data-placeholder", self.placeholder) }))%>></div>
%>></div>
<%+cbi/valuefooter%> <%+cbi/valuefooter%>

View file

@ -1,10 +1,12 @@
<%+cbi/valueheader%> <%+cbi/valueheader%>
<input type="hidden" value="1"<%= <div<%=attr("data-ui-widget", luci.util.serialize_json({
attr("name", "cbi.cbe." .. self.config .. "." .. section .. "." .. self.option) "Checkbox", self:cfgvalue(section) or self.default, {
%> /> id = cbid,
<input class="cbi-input-checkbox" data-update="click change" type="checkbox"<%= name = cbid,
attr("id", cbid) .. attr("name", cbid) .. attr("value", self.enabled or 1) .. readonly = self.readonly,
ifattr((self:cfgvalue(section) or self.default) == self.enabled, "checked", "checked") hiddenname = "cbi.cbe." .. self.config .. "." .. section .. "." .. self.option,
%> /> value_enabled = self.enabled or 1,
<label<%= attr("for", cbid)%>></label> value_disabled = self.disabled or 0
}
}))%>></div>
<%+cbi/valuefooter%> <%+cbi/valuefooter%>

View file

@ -1,43 +1,14 @@
<%
local i, key
local br = self.orientation == "horizontal" and '&#160;' or '<br />'
%>
<%+cbi/valueheader%> <%+cbi/valueheader%>
<% if self.widget == "select" then %> <div<%=attr("data-ui-widget", luci.util.serialize_json({
<select class="cbi-input-select" data-update="change"<%= "Select", self:cfgvalue(section), self:choices(), {
attr("id", cbid) .. id = cbid,
attr("name", cbid) .. name = cbid,
ifattr(self.size, "size") size = self.size,
%>> sort = self.keylist,
<% for i, key in pairs(self.keylist) do -%> widget = self.widget,
<option<%= datatype = self.datatype,
attr("id", cbid.."-"..key) .. optional = self.optional or self.rmempty,
attr("value", key) .. placeholder = self.placeholder
attr("data-index", i) .. }
attr("data-depends", self:deplist2json(section, self.deplist[i])) .. }))%>></div>
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 %>
<%+cbi/valuefooter%> <%+cbi/valuefooter%>

View file

@ -1,43 +1,16 @@
<%
local i, key
local v = self:valuelist(section) or {}
-%>
<%+cbi/valueheader%> <%+cbi/valueheader%>
<% if self.widget == "select" then %> <div<%=attr("data-ui-widget", luci.util.serialize_json({
<select class="cbi-input-select" multiple="multiple" data-update="click change"<%= "Select", self:cfgvalues(section), self:choices(), {
attr("id", cbid) .. id = cbid,
attr("name", cbid) .. name = cbid,
ifattr(self.size, "size") size = self.size,
%>> sort = self.keylist,
<% for i, key in pairs(self.keylist) do -%> multi = true,
<option<%= widget = self.widget,
attr("id", cbid.."-"..key) .. datatype = self.datatype,
attr("value", key) .. optional = self.optional or self.rmempty,
attr("data-index", i) .. readonly = self.readonly,
attr("data-depends", self:deplist2json(section, self.deplist[i])) .. placeholder = self.placeholder
ifattr(luci.util.contains(v, key), "selected", "selected") }
%>><%=pcdata(self.vallist[i])%></option> }))%>></div>
<%- 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 %>
<%+cbi/valuefooter%> <%+cbi/valuefooter%>

View file

@ -1,26 +1,35 @@
<%+cbi/valueheader%> <%+cbi/valueheader%>
<%- if self.password then -%>
<input type="password" style="position:absolute; left:-100000px" aria-hidden="true" tabindex="-1"<%= <% local choices = self:choices()
attr("name", "password." .. cbid) if choices then %>
%> /> <div<%=attr("data-ui-widget", luci.util.serialize_json({
<%- end -%> "Combobox", self:cfgvalue(section) or self.default, choices, {
<input data-update="change"<%= id = cbid,
attr("id", cbid) .. name = cbid,
attr("name", cbid) .. size = self.size,
attr("type", self.password and "password" or "text") .. sort = self.keylist,
attr("class", self.password and "cbi-input-password" or "cbi-input-text") .. datatype = self.datatype,
attr("value", self:cfgvalue(section) or self.default) .. optional = self.optional or self.rmempty,
ifattr(self.password, "autocomplete", "new-password") .. readonly = self.readonly,
ifattr(self.size, "size") .. maxlength = self.maxlength,
ifattr(self.placeholder, "placeholder") .. placeholder = self.placeholder,
ifattr(self.readonly, "readonly") .. custom_placeholder = self.combobox_manual
ifattr(self.maxlength, "maxlength") .. }
ifattr(self.datatype, "data-type", self.datatype) .. }))%>></div>
ifattr(self.datatype, "data-optional", self.optional or self.rmempty) .. <% else %>
ifattr(self.combobox_manual, "data-manual", self.combobox_manual) .. <div<%=attr("data-ui-widget", luci.util.serialize_json({
ifattr(#self.keylist > 0, "data-choices", { self.keylist, self.vallist }) "Textfield", self:cfgvalue(section) or self.default, {
%> /> id = cbid,
<%- if self.password then -%> name = cbid,
<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> 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 %> <% end %>
<%+cbi/valuefooter%> <%+cbi/valuefooter%>