123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497 |
- -- Copyright 2008 Steven Barth <steven@midlink.org>
- -- Copyright 2017 Matthias Schiffer <mschiffer@universe-factory.net>
- -- Licensed to the public under the Apache License 2.0.
- module("gluon.web.model", package.seeall)
- local util = require "gluon.web.util"
- local fs = require "nixio.fs"
- local datatypes = require "gluon.web.model.datatypes"
- local dispatcher = require "gluon.web.dispatcher"
- local class = util.class
- local instanceof = util.instanceof
- FORM_NODATA = 0
- FORM_VALID = 1
- FORM_INVALID = -1
- -- Loads a model from given file, creating an environment and returns it
- function load(name, renderer, pkg)
- local modeldir = util.libpath() .. "/model/"
- if not fs.access(modeldir..name..".lua") then
- error("Model '" .. name .. "' not found!")
- end
- local func = assert(loadfile(modeldir..name..".lua"))
- local i18n = setmetatable({
- i18n = renderer.i18n
- }, {
- __index = renderer.i18n(pkg)
- })
- setfenv(func, setmetatable({}, {__index =
- function(tbl, key)
- return _M[key] or i18n[key] or _G[key]
- end
- }))
- local models = { func() }
- for k, model in ipairs(models) do
- if not instanceof(model, Node) then
- error("model definition returned an invalid model object")
- end
- model.index = k
- end
- return models
- end
- local function parse_datatype(code)
- local match, arg, arg2
- match, arg, arg2 = code:match('^([^%(]+)%(([^,]+),([^%)]+)%)$')
- if match then
- return datatypes[match], {arg, arg2}
- end
- match, arg = code:match('^([^%(]+)%(([^%)]+)%)$')
- if match then
- return datatypes[match], {arg}
- end
- return datatypes[code], {}
- end
- local function verify_datatype(dt, value)
- if dt then
- local c, args = parse_datatype(dt)
- assert(c, "Invalid datatype")
- return c(value, unpack(args))
- end
- return true
- end
- Node = class()
- function Node:__init__(title, description, name)
- self.children = {}
- self.title = title or ""
- self.description = description or ""
- self.name = name
- self.index = nil
- self.parent = nil
- self.package = 'gluon-web'
- end
- function Node:append(obj)
- table.insert(self.children, obj)
- obj.index = #self.children
- obj.parent = self
- end
- function Node:id_suffix()
- return self.name or (self.index and tostring(self.index)) or '_'
- end
- function Node:id()
- local prefix = self.parent and self.parent:id() or "id"
- return prefix.."."..self:id_suffix()
- end
- function Node:parse(http)
- for _, child in ipairs(self.children) do
- child:parse(http)
- end
- end
- function Node:render(renderer, scope)
- if self.template then
- local env = setmetatable({
- self = self,
- id = self:id(),
- scope = scope,
- }, {__index = scope})
- renderer.render(self.template, env, self.package)
- end
- end
- function Node:render_children(renderer, scope)
- for _, node in ipairs(self.children) do
- node:render(renderer, scope)
- end
- end
- function Node:resolve_depends()
- local updated = false
- for _, node in ipairs(self.children) do
- update = updated or node:resolve_depends()
- end
- return updated
- end
- function Node:handle()
- for _, node in ipairs(self.children) do
- node:handle()
- end
- end
- Template = class(Node)
- function Template:__init__(template)
- Node.__init__(self)
- self.template = template
- end
- Form = class(Node)
- function Form:__init__(...)
- Node.__init__(self, ...)
- self.template = "model/form"
- end
- function Form:submitstate(http)
- return http:getenv("REQUEST_METHOD") == "POST" and http:formvalue(self:id()) ~= nil
- end
- function Form:parse(http)
- if not self:submitstate(http) then
- self.state = FORM_NODATA
- return
- end
- Node.parse(self, http)
- while self:resolve_depends() do end
- for _, s in ipairs(self.children) do
- for _, v in ipairs(s.children) do
- if v.state == FORM_INVALID then
- self.state = FORM_INVALID
- return
- end
- end
- end
- self.state = FORM_VALID
- end
- function Form:handle()
- if self.state == FORM_VALID then
- Node.handle(self)
- self:write()
- end
- end
- function Form:write()
- end
- function Form:section(t, ...)
- assert(instanceof(t, Section), "class must be a descendent of Section")
- local obj = t(...)
- self:append(obj)
- return obj
- end
- Section = class(Node)
- function Section:__init__(...)
- Node.__init__(self, ...)
- self.fields = {}
- self.template = "model/section"
- end
- function Section:option(t, option, title, description, ...)
- assert(instanceof(t, AbstractValue), "class must be a descendant of AbstractValue")
- local obj = t(title, description, option, ...)
- self:append(obj)
- self.fields[option] = obj
- return obj
- end
- AbstractValue = class(Node)
- function AbstractValue:__init__(option, ...)
- Node.__init__(self, option, ...)
- self.deps = {}
- self.default = nil
- self.size = nil
- self.optional = false
- self.template = "model/valuewrapper"
- self.state = FORM_NODATA
- end
- function AbstractValue:depends(field, value)
- local deps
- if instanceof(field, Node) then
- deps = { [field] = value }
- else
- deps = field
- end
- table.insert(self.deps, deps)
- end
- function AbstractValue:deplist(section, deplist)
- local deps = {}
- for _, d in ipairs(deplist or self.deps) do
- local a = {}
- for k, v in pairs(d) do
- a[k:id()] = v
- end
- table.insert(deps, a)
- end
- if next(deps) then
- return deps
- end
- end
- function AbstractValue:defaultvalue()
- return self.default
- end
- function AbstractValue:formvalue(http)
- return http:formvalue(self:id())
- end
- function AbstractValue:cfgvalue()
- if self.state == FORM_NODATA then
- return self:defaultvalue()
- else
- return self.data
- end
- end
- function AbstractValue:add_error(type, msg)
- self.error = msg or type
- if type == "invalid" then
- self.tag_invalid = true
- elseif type == "missing" then
- self.tag_missing = true
- end
- self.state = FORM_INVALID
- end
- function AbstractValue:reset()
- self.error = nil
- self.tag_invalid = nil
- self.tag_missing = nil
- self.data = nil
- self.state = FORM_NODATA
- end
- function AbstractValue:parse(http)
- self.data = self:formvalue(http)
- local ok, err = self:validate()
- if not ok then
- if type(self.data) ~= "string" or #self.data > 0 then
- self:add_error("invalid", err)
- else
- self:add_error("missing", err)
- end
- return
- end
- self.state = FORM_VALID
- end
- function AbstractValue:resolve_depends()
- if self.state == FORM_NODATA or #self.deps == 0 then
- return false
- end
- for _, d in ipairs(self.deps) do
- local valid = true
- for k, v in pairs(d) do
- if k.state ~= FORM_VALID or k.data ~= v then
- valid = false
- break
- end
- end
- if valid then return false end
- end
- self:reset()
- return true
- end
- function AbstractValue:validate()
- if self.data and verify_datatype(self.datatype, self.data) then
- return true
- end
- if type(self.data) == "string" and #self.data == 0 then
- self.data = nil
- end
- if self.data == nil then
- return self.optional
- end
- return false
- end
- function AbstractValue:handle()
- if self.state == FORM_VALID then
- self:write(self.data)
- end
- end
- function AbstractValue:write(value)
- end
- Value = class(AbstractValue)
- function Value:__init__(...)
- AbstractValue.__init__(self, ...)
- self.subtemplate = "model/value"
- end
- Flag = class(AbstractValue)
- function Flag:__init__(...)
- AbstractValue.__init__(self, ...)
- self.subtemplate = "model/fvalue"
- self.default = false
- end
- function Flag:formvalue(http)
- return http:formvalue(self:id()) ~= nil
- end
- function Flag:validate()
- return true
- end
- ListValue = class(AbstractValue)
- function ListValue:__init__(...)
- AbstractValue.__init__(self, ...)
- self.subtemplate = "model/lvalue"
- self.size = 1
- self.widget = "select"
- self.keys = {}
- self.entry_list = {}
- end
- function ListValue:value(key, val, ...)
- key = tostring(key)
- if self.keys[key] then
- return
- end
- self.keys[key] = true
- val = val or key
- table.insert(self.entry_list, {
- key = key,
- value = tostring(val),
- deps = {...},
- })
- end
- function ListValue:entries()
- local ret = {unpack(self.entry_list)}
- if self:cfgvalue() == nil or self.optional then
- table.insert(ret, 1, {
- key = '',
- value = '',
- deps = {},
- })
- end
- return ret
- end
- function ListValue:validate()
- if self.keys[self.data] then
- return true
- end
- if type(self.data) == "string" and #self.data == 0 then
- self.data = nil
- end
- if self.data == nil then
- return self.optional
- end
- return false
- end
- DynamicList = class(AbstractValue)
- function DynamicList:__init__(...)
- AbstractValue.__init__(self, ...)
- self.subtemplate = "model/dynlist"
- end
- function DynamicList:defaultvalue()
- local value = self.default
- if type(value) == "table" then
- return value
- else
- return { value }
- end
- end
- function DynamicList:formvalue(http)
- return http:formvaluetable(self:id())
- end
- function DynamicList:validate()
- if self.data == nil then
- self.data = {}
- end
- if #self.data == 0 then
- return self.optional
- end
- for _, v in ipairs(self.data) do
- if not verify_datatype(self.datatype, v) then
- return false
- end
- end
- return true
- end
- TextValue = class(AbstractValue)
- function TextValue:__init__(...)
- AbstractValue.__init__(self, ...)
- self.subtemplate = "model/tvalue"
- end
|