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