summaryrefslogtreecommitdiffstats
path: root/acf/model/field.lua
blob: cfa14b998e481ddfc3c7e69a3d53308d368a69c5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
--[[
Copyright (c) 2012 Kaarle Ritvanen
See LICENSE file for license details
--]]

module(..., package.seeall)

local node = require('acf.model.node')

local object = require('acf.object')
local class = object.class
local super = object.super

local map = require('acf.util').map


local function contains(list, value)
   for k, v in ipairs(list) do if v == value then return true end end
   return false
end

local function auto_ui_name(name)
   if not name then return end
   return string.gsub(string.upper(string.sub(name, 1, 1))..string.sub(name, 2),
		      '_', ' ')
end


Field = class()

function Field:init(params)
   for k, v in pairs(params or {}) do if not self[k] then self[k] = v end end

   if self.choice and not self['ui-choice'] then
      self['ui-choice'] = map(auto_ui_name, self.choice)
   end

   if not self.widget then
      self.widget = self.choice and 'combobox' or 'field'
   end
end

function Field:meta(txn, path, addr)
   assert(self.dtype)
   return {
      name=self.name,
      type=self.dtype,
      required=self.required,
      choice=self.choice,
      description=self.description,
      ['ui-name']=self['ui-name'] or auto_ui_name(self.name),
      widget=self.widget,
      ['ui-choice']=self['ui-choice']
   }
end

function Field:load(txn, path, addr)
   local value = txn:get(addr)
   if value == nil then return self.default end
   return value
end

function Field:_validate(txn, path, value)
   if self.required and value == nil then
      error('Required value not set: '..path)
   end
   if self.choice and value ~= nil and not contains(self.choice, value) then
      error('Invalid value for '..path..': '..value)
   end
   if value ~= nil then self:validate(txn, path, value) end
   return value
end

function Field:validate(txn, path, value) end

function Field:save(txn, path, addr, value)
   -- 2nd argument currenly not much used by backends
   txn:set(addr, self.dtype, self:_validate(txn, path, value))
end

function Field:validate_saved(txn, path, addr)
   self:save(txn, path, addr, self:load(txn, path, addr))
end


TreeNode = class(Field)

function TreeNode:save(txn, path, addr, value)
   -- TODO hack, allow preserving old instance on parent update
   if value == path then return end

   if object.isinstance(value, node.TreeNode) then
      -- TODO clone if TreeNode has wrong path
      assert(node.path(value) == path)
      return
   end

   txn:set(addr)
   if value then
      assert(type(value) == 'table')
      txn:set(addr, 'table')
      local new = self:load(txn, path, addr, true)
      for k, v in pairs(value) do new[k] = v end
   end
end


Model = class(TreeNode)

function Model:init(params)
   super(self, Model):init(params)
   assert(self.model)
   self.dtype = 'model'
   self.widget = self.dtype
end

function Model:load(txn, path, addr, create)
   if not create and not txn:get(addr) then return end
   return self.model(txn, path, addr)
end