Module:SOM.meta.DateBoundedAttribute
From SunshinePPS Wiki
Documentation for this module may be created at Module:SOM.meta.DateBoundedAttribute/doc
require("Module:No globals")
local base = require("Module:SOM.meta.AttributeBase")
local util = require("Module:SOM.util")
--local base = {}
--function base.make_value_property(root)
-- return "SOM:" .. root
--end
local CompositeAttribute = require("Module:SOM.meta.CompositeAttribute")
local ReferencingAttribute = require("Module:SOM.meta.ReferencingAttribute")
local ErrorCapture = require("Module:SOM.util.ErrorCapture")
local StringBuffer = require("Module:SOM.util.StringBuffer")
local KEY_FROM = "from"
local KEY_UNTIL = "until"
local KEY_IS_CURRENT = "is_current"
local DATEBOUNDS_KEYS = { KEY_FROM, KEY_UNTIL, KEY_IS_CURRENT }
local p = {}
p.MISSING_CURRENT_ATTRIBUTE_PROPERTY =
base.make_value_property("Has missing current attribute")
p.DATEBOUNDS_FORM_ROW = { KEY_FROM, KEY_UNTIL, KEY_IS_CURRENT, ReferencingAttribute.KEY_REFERENCES }
local function add_datebounds_elements(elements, class_name)
for _, key in ipairs(DATEBOUNDS_KEYS) do
util.assertf(elements[key] == nil,
"%s already has a %s element!", class_name, key)
end
elements[KEY_FROM] = {
smw = {
property = "Has start date",
},
field = {
label = "From",
input_type = "datepicker",
extra_args = { ["date format"] = "YYYY-MM-DD" },
},
}
elements[KEY_UNTIL] = {
smw = {
property = "Has end date",
},
field = {
label = "Until",
input_type = "datepicker",
extra_args = { ["date format"] = "YYYY-MM-DD" },
},
}
elements[KEY_IS_CURRENT] = {
smw = {
property = "Is current",
},
field = {
label = "Current?",
-- stupid checkbox only produces "Yes" or "No"
-- input_type = "checkbox",
input_type = "radiobutton",
-- extra_args = { "mandatory", default = "yes", values = "yes,no" },
-- map = { yes = "true", no = "false",
-- Yes = "true", No = "false" }
extra_args = { "mandatory", default = "true", values = "true,false" },
},
}
end
-- expect_current: boolean/optional - if true, flag "missing current attribute"
-- if no current instance is known.
function p.generate_composite(class_args)
local class_name = class_args.class_name
local elements = class_args.elements
local lone_key = next(elements, nil)
if next(elements, lone_key) then
-- There is more than one key, hence there is no "lone key".
lone_key = nil
end
-- Augment the args/elements provided by caller.
add_datebounds_elements(class_args.elements, class_args.class_name)
-- Generate a ReferencingAttribute class.
local class = ReferencingAttribute.generate_composite(class_args)
function class.select_current(instances)
local results = {}
for _, i in ipairs(instances) do
if i[KEY_IS_CURRENT] == "true" then
table.insert(results, i)
end
end
return results
end
function class.docs.select_current()
return {
desc = "From a list of instances, select those that are marked current.",
args = { { "instances", "(table of tables) instances to scan" } }
}
end
-- return list of current instances
-- if list is empty, set the "missing current attribute" property
function class.require_current(instances)
local currents = class.select_current(instances)
if #currents == 0 then
local ds = {}
ds[p.MISSING_CURRENT_ATTRIBUTE_PROPERTY] = class.module_name
util.assert_smw_set(ds)
end
return currents
end
function class.docs.require_current()
return {
desc = "From a list of instances, select those that are marked current." ..
"If there are none, set " .. p.MISSING_CURRENT_ATTRIBUTE_PROPERTY .. ".",
args = { { "instances", "(table of tables) instances to scan" } }
}
end
function class.render_current(values, frame, render_args)
local currents = class.require_current(values)
if #currents == 0 then
return util.MISSING_VALUE
end
------- return class.render
if #currents == 1 then
return class.render_current_instance(currents[1], frame, render_args)
end
local buffer = StringBuffer.new()
for _, d in ipairs(currents) do
buffer
-- TODO maddog Should use a class.render method
---- :add_uformat("* %s", util.show_as_link(d.director))
:add_uformat("* %s", class.render_current_instance(d, frame, render_args))
:nl()
end
return buffer:output()
end
function class.render_current_instance(instance, frame, render_args)
local render_with = (render_args or {}).render_with
if render_with then
return render_with(instance, frame)
end
local key = (render_args or {}).key or lone_key
util.assertf(key, "render_current_instance does not know what key to render")
local element = elements[key]
local value = instance[key]
if element.render_formatter then
value = element.render_formatter(value)
end
return util.show_nil_as_missing(value)
end
-- Capture the base attach() function, and wrap it with a new one.
local base_decode_and_attach = class.decode_and_attach
-- TODO maddog Consider that expect_current should be a param to this function
-- rather than a class-wide parameter --- since it may depend on
-- the specific page (e.g., an old/closed school would not be
-- expecting to have a current principal).
-- Even better: get rid of the expect parameter, and just expose
-- the "check_for_current" to pages that want to use it.
function class.decode_and_attach(encoded, override_values)
local instances, any_missing_data, ec = base_decode_and_attach(encoded, override_values)
-- Augment the generated class.
-- - validity checks of datebounds data
return instances, any_missing_data, ec
end
function class.basic_current_first_table_args()
return {
sort = p.sort_current_first,
columns = {
{ label = elements[lone_key].field.label, key = lone_key },
p.COLUMN_FROM,
p.COLUMN_UNTIL,
p.COLUMN_REFERENCES,
}
}
end
return class
end
local function descending_from(a, b)
local a_from = a[KEY_FROM]
local b_from = b[KEY_FROM]
if a_from then
if b_from then
return a_from > b_from
else
return true
end
end
return false
end
-- return true if a should come before b
local function descending(a, b)
local a_until = a[KEY_UNTIL]
local b_until = b[KEY_UNTIL]
if a[KEY_IS_CURRENT] == "true" then
if b[KEY_IS_CURRENT] == "true" then
return descending_from(a, b)
else
return true
end
elseif b[KEY_IS_CURRENT] == "true" then
return false
elseif a_until == b_until then
return descending_from(a, b)
elseif a_until then
if b_until then
return a_until > b_until
else
return true
end
end
return false
end
-- direction in ["ascending", "descending"]
function p.sort_instances_by_until_then_from(instances, direction)
if direction == "descending" then
table.sort(instances, descending)
return
else
table.sort(instances, function (a, b) return descending(b, a) end)
return
end
end
function p.sort_current_first(instances)
return p.sort_instances_by_until_then_from(instances, "descending")
end
p.COLUMN_FROM = {
label = "From",
key = KEY_FROM,
}
p.COLUMN_UNTIL = {
label = "Until",
render = function(instance)
if instance[KEY_IS_CURRENT] == "true" then
-- TODO maddog What if UNTIL is non-nil?
-- (Check for stale "current"?)
return "''current''"
else
return util.show_nil_as_missing(instance[KEY_UNTIL])
end
end
}
p.COLUMN_REFERENCES = ReferencingAttribute.COLUMN_REFERENCES
return p