model.lua 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497
  1. -- Copyright 2008 Steven Barth <steven@midlink.org>
  2. -- Copyright 2017 Matthias Schiffer <mschiffer@universe-factory.net>
  3. -- Licensed to the public under the Apache License 2.0.
  4. module("gluon.web.model", package.seeall)
  5. local util = require "gluon.web.util"
  6. local fs = require "nixio.fs"
  7. local datatypes = require "gluon.web.model.datatypes"
  8. local dispatcher = require "gluon.web.dispatcher"
  9. local class = util.class
  10. local instanceof = util.instanceof
  11. FORM_NODATA = 0
  12. FORM_VALID = 1
  13. FORM_INVALID = -1
  14. -- Loads a model from given file, creating an environment and returns it
  15. function load(name, renderer, pkg)
  16. local modeldir = util.libpath() .. "/model/"
  17. if not fs.access(modeldir..name..".lua") then
  18. error("Model '" .. name .. "' not found!")
  19. end
  20. local func = assert(loadfile(modeldir..name..".lua"))
  21. local i18n = setmetatable({
  22. i18n = renderer.i18n
  23. }, {
  24. __index = renderer.i18n(pkg)
  25. })
  26. setfenv(func, setmetatable({}, {__index =
  27. function(tbl, key)
  28. return _M[key] or i18n[key] or _G[key]
  29. end
  30. }))
  31. local models = { func() }
  32. for k, model in ipairs(models) do
  33. if not instanceof(model, Node) then
  34. error("model definition returned an invalid model object")
  35. end
  36. model.index = k
  37. end
  38. return models
  39. end
  40. local function parse_datatype(code)
  41. local match, arg, arg2
  42. match, arg, arg2 = code:match('^([^%(]+)%(([^,]+),([^%)]+)%)$')
  43. if match then
  44. return datatypes[match], {arg, arg2}
  45. end
  46. match, arg = code:match('^([^%(]+)%(([^%)]+)%)$')
  47. if match then
  48. return datatypes[match], {arg}
  49. end
  50. return datatypes[code], {}
  51. end
  52. local function verify_datatype(dt, value)
  53. if dt then
  54. local c, args = parse_datatype(dt)
  55. assert(c, "Invalid datatype")
  56. return c(value, unpack(args))
  57. end
  58. return true
  59. end
  60. Node = class()
  61. function Node:__init__(title, description, name)
  62. self.children = {}
  63. self.title = title or ""
  64. self.description = description or ""
  65. self.name = name
  66. self.index = nil
  67. self.parent = nil
  68. self.package = 'gluon-web'
  69. end
  70. function Node:append(obj)
  71. table.insert(self.children, obj)
  72. obj.index = #self.children
  73. obj.parent = self
  74. end
  75. function Node:id_suffix()
  76. return self.name or (self.index and tostring(self.index)) or '_'
  77. end
  78. function Node:id()
  79. local prefix = self.parent and self.parent:id() or "id"
  80. return prefix.."."..self:id_suffix()
  81. end
  82. function Node:parse(http)
  83. for _, child in ipairs(self.children) do
  84. child:parse(http)
  85. end
  86. end
  87. function Node:render(renderer, scope)
  88. if self.template then
  89. local env = setmetatable({
  90. self = self,
  91. id = self:id(),
  92. scope = scope,
  93. }, {__index = scope})
  94. renderer.render(self.template, env, self.package)
  95. end
  96. end
  97. function Node:render_children(renderer, scope)
  98. for _, node in ipairs(self.children) do
  99. node:render(renderer, scope)
  100. end
  101. end
  102. function Node:resolve_depends()
  103. local updated = false
  104. for _, node in ipairs(self.children) do
  105. update = updated or node:resolve_depends()
  106. end
  107. return updated
  108. end
  109. function Node:handle()
  110. for _, node in ipairs(self.children) do
  111. node:handle()
  112. end
  113. end
  114. Template = class(Node)
  115. function Template:__init__(template)
  116. Node.__init__(self)
  117. self.template = template
  118. end
  119. Form = class(Node)
  120. function Form:__init__(...)
  121. Node.__init__(self, ...)
  122. self.template = "model/form"
  123. end
  124. function Form:submitstate(http)
  125. return http:getenv("REQUEST_METHOD") == "POST" and http:formvalue(self:id()) ~= nil
  126. end
  127. function Form:parse(http)
  128. if not self:submitstate(http) then
  129. self.state = FORM_NODATA
  130. return
  131. end
  132. Node.parse(self, http)
  133. while self:resolve_depends() do end
  134. for _, s in ipairs(self.children) do
  135. for _, v in ipairs(s.children) do
  136. if v.state == FORM_INVALID then
  137. self.state = FORM_INVALID
  138. return
  139. end
  140. end
  141. end
  142. self.state = FORM_VALID
  143. end
  144. function Form:handle()
  145. if self.state == FORM_VALID then
  146. Node.handle(self)
  147. self:write()
  148. end
  149. end
  150. function Form:write()
  151. end
  152. function Form:section(t, ...)
  153. assert(instanceof(t, Section), "class must be a descendent of Section")
  154. local obj = t(...)
  155. self:append(obj)
  156. return obj
  157. end
  158. Section = class(Node)
  159. function Section:__init__(...)
  160. Node.__init__(self, ...)
  161. self.fields = {}
  162. self.template = "model/section"
  163. end
  164. function Section:option(t, option, title, description, ...)
  165. assert(instanceof(t, AbstractValue), "class must be a descendant of AbstractValue")
  166. local obj = t(title, description, option, ...)
  167. self:append(obj)
  168. self.fields[option] = obj
  169. return obj
  170. end
  171. AbstractValue = class(Node)
  172. function AbstractValue:__init__(option, ...)
  173. Node.__init__(self, option, ...)
  174. self.deps = {}
  175. self.default = nil
  176. self.size = nil
  177. self.optional = false
  178. self.template = "model/valuewrapper"
  179. self.state = FORM_NODATA
  180. end
  181. function AbstractValue:depends(field, value)
  182. local deps
  183. if instanceof(field, Node) then
  184. deps = { [field] = value }
  185. else
  186. deps = field
  187. end
  188. table.insert(self.deps, deps)
  189. end
  190. function AbstractValue:deplist(section, deplist)
  191. local deps = {}
  192. for _, d in ipairs(deplist or self.deps) do
  193. local a = {}
  194. for k, v in pairs(d) do
  195. a[k:id()] = v
  196. end
  197. table.insert(deps, a)
  198. end
  199. if next(deps) then
  200. return deps
  201. end
  202. end
  203. function AbstractValue:defaultvalue()
  204. return self.default
  205. end
  206. function AbstractValue:formvalue(http)
  207. return http:formvalue(self:id())
  208. end
  209. function AbstractValue:cfgvalue()
  210. if self.state == FORM_NODATA then
  211. return self:defaultvalue()
  212. else
  213. return self.data
  214. end
  215. end
  216. function AbstractValue:add_error(type, msg)
  217. self.error = msg or type
  218. if type == "invalid" then
  219. self.tag_invalid = true
  220. elseif type == "missing" then
  221. self.tag_missing = true
  222. end
  223. self.state = FORM_INVALID
  224. end
  225. function AbstractValue:reset()
  226. self.error = nil
  227. self.tag_invalid = nil
  228. self.tag_missing = nil
  229. self.data = nil
  230. self.state = FORM_NODATA
  231. end
  232. function AbstractValue:parse(http)
  233. self.data = self:formvalue(http)
  234. local ok, err = self:validate()
  235. if not ok then
  236. if type(self.data) ~= "string" or #self.data > 0 then
  237. self:add_error("invalid", err)
  238. else
  239. self:add_error("missing", err)
  240. end
  241. return
  242. end
  243. self.state = FORM_VALID
  244. end
  245. function AbstractValue:resolve_depends()
  246. if self.state == FORM_NODATA or #self.deps == 0 then
  247. return false
  248. end
  249. for _, d in ipairs(self.deps) do
  250. local valid = true
  251. for k, v in pairs(d) do
  252. if k.state ~= FORM_VALID or k.data ~= v then
  253. valid = false
  254. break
  255. end
  256. end
  257. if valid then return false end
  258. end
  259. self:reset()
  260. return true
  261. end
  262. function AbstractValue:validate()
  263. if self.data and verify_datatype(self.datatype, self.data) then
  264. return true
  265. end
  266. if type(self.data) == "string" and #self.data == 0 then
  267. self.data = nil
  268. end
  269. if self.data == nil then
  270. return self.optional
  271. end
  272. return false
  273. end
  274. function AbstractValue:handle()
  275. if self.state == FORM_VALID then
  276. self:write(self.data)
  277. end
  278. end
  279. function AbstractValue:write(value)
  280. end
  281. Value = class(AbstractValue)
  282. function Value:__init__(...)
  283. AbstractValue.__init__(self, ...)
  284. self.subtemplate = "model/value"
  285. end
  286. Flag = class(AbstractValue)
  287. function Flag:__init__(...)
  288. AbstractValue.__init__(self, ...)
  289. self.subtemplate = "model/fvalue"
  290. self.default = false
  291. end
  292. function Flag:formvalue(http)
  293. return http:formvalue(self:id()) ~= nil
  294. end
  295. function Flag:validate()
  296. return true
  297. end
  298. ListValue = class(AbstractValue)
  299. function ListValue:__init__(...)
  300. AbstractValue.__init__(self, ...)
  301. self.subtemplate = "model/lvalue"
  302. self.size = 1
  303. self.widget = "select"
  304. self.keys = {}
  305. self.entry_list = {}
  306. end
  307. function ListValue:value(key, val, ...)
  308. key = tostring(key)
  309. if self.keys[key] then
  310. return
  311. end
  312. self.keys[key] = true
  313. val = val or key
  314. table.insert(self.entry_list, {
  315. key = key,
  316. value = tostring(val),
  317. deps = {...},
  318. })
  319. end
  320. function ListValue:entries()
  321. local ret = {unpack(self.entry_list)}
  322. if self:cfgvalue() == nil or self.optional then
  323. table.insert(ret, 1, {
  324. key = '',
  325. value = '',
  326. deps = {},
  327. })
  328. end
  329. return ret
  330. end
  331. function ListValue:validate()
  332. if self.keys[self.data] then
  333. return true
  334. end
  335. if type(self.data) == "string" and #self.data == 0 then
  336. self.data = nil
  337. end
  338. if self.data == nil then
  339. return self.optional
  340. end
  341. return false
  342. end
  343. DynamicList = class(AbstractValue)
  344. function DynamicList:__init__(...)
  345. AbstractValue.__init__(self, ...)
  346. self.subtemplate = "model/dynlist"
  347. end
  348. function DynamicList:defaultvalue()
  349. local value = self.default
  350. if type(value) == "table" then
  351. return value
  352. else
  353. return { value }
  354. end
  355. end
  356. function DynamicList:formvalue(http)
  357. return http:formvaluetable(self:id())
  358. end
  359. function DynamicList:validate()
  360. if self.data == nil then
  361. self.data = {}
  362. end
  363. if #self.data == 0 then
  364. return self.optional
  365. end
  366. for _, v in ipairs(self.data) do
  367. if not verify_datatype(self.datatype, v) then
  368. return false
  369. end
  370. end
  371. return true
  372. end
  373. TextValue = class(AbstractValue)
  374. function TextValue:__init__(...)
  375. AbstractValue.__init__(self, ...)
  376. self.subtemplate = "model/tvalue"
  377. end