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)
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

View 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%>

View 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%>

View 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%>

View 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%>

View 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%>

View 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%>