diff options
Diffstat (limited to 'acf2')
-rw-r--r-- | acf2/model/field.lua | 45 | ||||
-rw-r--r-- | acf2/model/init.lua | 56 | ||||
-rw-r--r-- | acf2/model/model.lua | 16 | ||||
-rw-r--r-- | acf2/model/node.lua | 1 | ||||
-rw-r--r-- | acf2/transaction/init.lua | 4 | ||||
-rw-r--r-- | acf2/util.lua | 10 |
6 files changed, 89 insertions, 43 deletions
diff --git a/acf2/model/field.lua b/acf2/model/field.lua index d2470ca..e633a63 100644 --- a/acf2/model/field.lua +++ b/acf2/model/field.lua @@ -41,6 +41,16 @@ function M.Member:meta(context) end +function M.conv_filter(filter) + return filter and map( + function(values) + return type(values) == 'table' and values or {values} + end, + filter + ) or nil +end + + M.Field = class(M.Member) function M.Field:init(params) @@ -49,14 +59,7 @@ function M.Field:init(params) if self.compute then self.addr = node.null_addr end if self.editable == nil then self.editable = not self.compute end - if self.condition then - self.condition = map( - function(values) - return type(values) == 'table' and values or {values} - end, - self.condition - ) - end + self.condition = M.conv_filter(self.condition) if self.choice then self.choice = map( @@ -70,7 +73,8 @@ function M.Field:init(params) end end return util.setdefaults( - choice, {['ui-value']=self:auto_ui_name(choice.value)} + choice, + {enabled=true, ['ui-value']=self:auto_ui_name(choice.value)} ) end, self.choice @@ -82,6 +86,8 @@ function M.Field:init(params) end end +function M.Field:_choice(context) return self.choice end + function M.Field:meta(context) assert(self.dtype) local res = super(self, M.Field):meta(context) @@ -91,7 +97,7 @@ function M.Field:meta(context) res.condition = self.condition res.required = self.required res.default = self.default - res.choice = self.choice + res.choice = self:_choice(context) res.widget = self.widget return res @@ -118,14 +124,25 @@ function M.Field:_validate(context, value) return end - value = self:normalize(context, value) - if self.choice and not util.contains( - map(function(ch) return ch.value end, self.choice), value + local save + value, save = self:normalize(context, value) + + local committing = context.txn:committing() + local choice = self:_choice(context) + if choice and not util.contains( + map( + function(ch) return ch.value end, + util.filter(function(ch) return committing or ch.enabled end, choice) + ), + value ) then raise(context.path, 'Invalid value') end + self:validate(context, value) - return value + + if save == nil then save = value end + return save end function M.Field:normalize(context, value) return value end diff --git a/acf2/model/init.lua b/acf2/model/init.lua index 62f114b..711626b 100644 --- a/acf2/model/init.lua +++ b/acf2/model/init.lua @@ -69,6 +69,7 @@ function M.Reference:init(params) self.dtype = 'reference' self.dereference = true if not self.scope then self.scope = '/' end + self.filter = fld.conv_filter(self.filter) end function M.Reference:topology(context) @@ -81,30 +82,43 @@ function M.Reference:abs_scope(context) return pth.to_absolute(self.scope, node.path(context.parent)) end -function M.Reference:meta(context) - local res = super(self, M.Reference):meta(context) - res.scope = self:abs_scope(context) +-- assume one-level refs for now +function M.Reference:_choice(context) + local res = {} local txn = context.txn - local obj = relabel('system', txn.fetch, txn, res.scope) + local obj = relabel('system', txn.fetch, txn, self:abs_scope(context)) assert(isinstance(obj, node.Collection)) - res.choice = {} + for k, v in node.pairs(obj) do + local ch = {enabled=true} + if isinstance(v, node.TreeNode) then - v = node.path(v) - local name = pth.name(v) - v = not pth.is_subordinate(context.path, v) and { - value=self.dereference and v or pth.escape(name), - ['ui-value']=name, - ref=v - } or nil - else v = {value=pth.escape(v), ['ui-value']=v} end - if v then table.insert(res.choice, v) end + ch.ref = node.path(v) + if pth.is_subordinate(context.path, ch.ref) then ch = nil end + if ch then + ch['ui-value'] = pth.name(ch.ref) + ch.value = self.dereference and ch.ref or pth.escape(ch['ui-value']) + if self.filter then + assert(isinstance(v, model.Model)) + if not node.match(v, self.filter) then ch.enabled = false end + end + end + + else update(ch, {value=pth.escape(v), ['ui-value']=v}) end + + if ch then table.insert(res, ch) end end return res end +function M.Reference:meta(context) + local res = super(self, M.Reference):meta(context) + res.scope = self:abs_scope(context) + return res +end + function M.Reference:follow(context, value) return context.txn:fetch(pth.rawjoin(self:abs_scope(context), value)) end @@ -124,24 +138,26 @@ function M.Reference:normalize(context, value) local path = context.path if type(value) ~= 'string' then raise(path, 'Path name must be string') end - if pth.is_absolute(value) then + local rel = value + + if pth.is_absolute(rel) then local scope = self:abs_scope(context) local prefix = scope..'/' - if not stringy.startswith(value, prefix) then + if not stringy.startswith(rel, prefix) then raise(path, 'Reference out of scope ('..scope..')') end - value = value:sub(prefix:len() + 1, -1) + rel = rel:sub(prefix:len() + 1, -1) end -- assume one-level ref for now - if #pth.split(value) > 1 then + if #pth.split(rel) > 1 then raise(path, 'Subtree references not yet supported') end -- TODO check instance type - relabel(path, self.follow, self, context, value) + relabel(path, self.follow, self, context, rel) - return value + return self.dereference and value or rel, rel end function M.Reference:deleted(context, addr) diff --git a/acf2/model/model.lua b/acf2/model/model.lua index b4f4603..c1c222f 100644 --- a/acf2/model/model.lua +++ b/acf2/model/model.lua @@ -196,17 +196,17 @@ function M.Model:init(context) return map(function(f) return f.name end, mt.meta().fields) end + function mt.match(filter) + for k, v in pairs(filter) do + if not util.contains(v, mt.load(k)) then return false end + end + return true + end + function mt.validate() for _, f in ipairs(_members(Field)) do if f.editable then - local relevant = true - for k, v in pairs(f.condition or {}) do - if not util.contains(v, mt.load(k)) then - relevant = false - break - end - end - if relevant then f:validate_saved() + if mt.match(f.condition or {}) then f:validate_saved() else f:_save() end end end diff --git a/acf2/model/node.lua b/acf2/model/node.lua index 5096787..37a478e 100644 --- a/acf2/model/node.lua +++ b/acf2/model/node.lua @@ -263,6 +263,7 @@ for _, mf in ipairs{ 'contains', 'has_permission', 'insert', + 'match', 'meta', 'mmeta', 'parent', diff --git a/acf2/transaction/init.lua b/acf2/transaction/init.lua index 395059a..627703c 100644 --- a/acf2/transaction/init.lua +++ b/acf2/transaction/init.lua @@ -44,11 +44,12 @@ function Transaction:init(backend, validate) self.validate = validate self.validable = {} - self.commit_val = {} self.root = root.RootModel(self) end +function Transaction:committing() return self.commit_val and true or false end + function Transaction:check() if not self.backend then error('Transaction already committed') end end @@ -169,6 +170,7 @@ function Transaction:commit() end while next(self.commit_val) do validate(next(self.commit_val)) end + self.commit_val = nil errors:raise() end diff --git a/acf2/util.lua b/acf2/util.lua index a40f7fd..f4c47d6 100644 --- a/acf2/util.lua +++ b/acf2/util.lua @@ -90,4 +90,14 @@ function M.map(func, tbl) return res end +--- select array values satisfying a filter. +-- @param func a function with one argument +-- @param list the array +-- @return the filtered array +function M.filter(func, list) + local res = {} + for _, v in ipairs(list) do if func(v) then table.insert(res, v) end end + return res +end + return M |