summaryrefslogtreecommitdiffstats
path: root/acf/model/field.lua
diff options
context:
space:
mode:
Diffstat (limited to 'acf/model/field.lua')
-rw-r--r--acf/model/field.lua120
1 files changed, 120 insertions, 0 deletions
diff --git a/acf/model/field.lua b/acf/model/field.lua
new file mode 100644
index 0000000..cfa14b9
--- /dev/null
+++ b/acf/model/field.lua
@@ -0,0 +1,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