Semantic MediaWikiDit bericht verdwijnt nadat alle relevante taken zijn afgerond.

Er is één onvoltooide of openstaande taak, die nodig is om het bijwerken van Semantic MediaWiki te voltooien. Een beheerder of gebruiker met voldoende rechten kan deze uitvoeren. Dit moet worden gedaan voordat nieuwe gegevens worden toegevoegd om tegenstrijdigheden te voorkomen.

Geen bewerkingssamenvatting
add 'free' _type with _content set to free content
 
(58 tussenliggende versies door dezelfde gebruiker niet weergegeven)
Regel 4: Regel 4:
--[[
--[[
Separate handler for base props (currently)
Separate handler for base props (currently)
{{#invoke:FFSchema|class|id= |page= |class=  
{{#invoke:FFSchema|class|id=  
|slot= |fields= |template= }}
|page= // mandatory
|class= // mandatory
|slot= // defaults to ws-base-props
|fields= // defaults to Class
|template= // defaults to Base properties
|mwfollow=true (default)/false
}}
]]--
]]--
p.class = function(frame)
p.class = function(frame)
-- params:
-- params:
local newPage = frame.args.page or nil
local newPage = frame.args.page or ""
local class = frame.args.class or nil
local class = frame.args.class or ""
if newPage == nil or class == nil then return "" end
if newPage == "" or class == "" then return "" end
--  
--  
local id = frame.args.id or "create-class-instance"
local id = frame.args.id or "create-class-instance"
Regel 18: Regel 24:
local fieldStr = frame.args.fields or "Class"
local fieldStr = frame.args.fields or "Class"
local template = frame.args.template or "Base properties"
local template = frame.args.template or "Base properties"
local mwfollow = frame.args.mwfollow or "true"


-- e.g. <_create id="wscreate-base" mwwrite="Titel/{{#tool:randomint|1000000|9999999}}" mwslot="ws-base-props" mwtemplate="Base properties" mwfields="Class" mwfollow="true" />
-- e.g. <_create id="wscreate-base" mwwrite="Titel/{{#tool:randomint|1000000|9999999}}" mwslot="ws-base-props" mwtemplate="Base properties" mwfields="Class" mwfollow="true" />
Regel 26: Regel 34:
mwtemplate = template,
mwtemplate = template,
mwfields = fieldStr,
mwfields = fieldStr,
mwfollow = "true"
mwfollow = mwfollow
}
}
local createTag = mw.text.tag( "_create", createAttrbs, false )
local createTag = mw.text.tag( "_create", createAttrbs, false )
Regel 74: Regel 82:
Field definitions accept two types of parameters:
Field definitions accept two types of parameters:
- those that are native to FlexForm
- those that are native to FlexForm
- additional parameters beginning with an underscore (_) that can assist in some tedious tasks, including _type, _label, _default,_fields _options, _optionsep, _fields, _instance, _note (before), _tooltip, _itemClass, _htmlElement and _content, and _filtertemplate
- additional parameters beginning with an underscore (_) that can assist in some tedious tasks, including _type, _label, _default,_fields _options, _optionsep, _fields, _instance, _note (before), _tooltip, _itemLayout (@todo), _itemClass, _htmlElement and _content, _filtertemplate, _show-on-select-trigger (or _data-wssos-value), _hasVar


with select/token:  
with select/token:  
Regel 92: Regel 100:
-- read-only!
-- read-only!
-- @todo check that schema exists and is valid
-- @todo check that schema exists and is valid
local schemaTbl = mw.loadJsonData( schema )
local schemaTbl = mw.loadJsonData( schema )
-- cannot use getSlotContent because regular user may not have 'read' rights for the JSON page
--local schemaTbl = p.getSlotContent( schema, "main" )
if schemaTbl == nil then return "" end
if schemaTbl == nil then return "" end


Regel 103: Regel 114:
sourceFormat = "wiki"
sourceFormat = "wiki"
end
end
 
-- default values in the format name=value ### name=value ### etc.
-- default values in the format name=value ### name=value ### etc.
-- allows for custom delimiter/sep
-- allows for custom delimiter/sep
local defaults = frame.args.defaults or ""
local defaults = frame.args.defaults or ""
local defaultsSep = frame.args.defaultssep or "###"
local defaultsSep = frame.args.defaultssep or "###"
-- vars for query
local varStr = frame.args.vars or ""
local varsTbl = p.createVarsTable( varStr, "###" )


-- get content from page/slot for autofill
-- get content from page/slot for autofill
local slotContent = nil
local slotContent = nil
if mode == "create" then
local title = nil
-- for now attempt nothing - page does not exist
if page ~= "" then
elseif page ~= "" or page ~= nil then
title = mw.title.new( page )
end
if title ~= nil and title.exists then
if sourceFormat == "wiki" then
if sourceFormat == "wiki" then
slotContent = p.getSlotContent( page, slot, template )
slotContent = p.getSlotContent( page, slot, template )
Regel 120: Regel 136:
end
end
end
end
-- for now, attempt nothing if page does not exist, except for defaults


local prefillTbl = p.createPrefillTable( sourceFormat, slotContent, defaults, defaultsSep )
local prefillTbl = p.createPrefillTable( sourceFormat, slotContent, defaults, defaultsSep )


-- fields = form field definitions
-- fields = form field definitions
local fieldDefinitions = schemaTbl.fields or nil
local fieldDefinitions = nil
if fieldDefinitions == nil then return "" end
if schemaTbl.fields ~= nil then
fieldDefinitions = schemaTbl.fields
else
return ""
end


-- edit/create actions or ""
-- edit/create actions or ""
local actionTags = p.setupActionTags( page, mode, schemaTbl, fieldDefinitions )
local actionDefs = p.getSelectedActionDefs( page, mode, schemaTbl )
local actionTags = p.setupActionTags( page, actionDefs, fieldDefinitions )
-- note: turns fieldDefinitions into proxy
fieldDefinitions = p.maybeSubtractFromFieldDefinitions( actionDefs, fieldDefinitions )


-- fields
-- fields
local fieldStr = p.buildFromFieldDefinitions( fieldDefinitions, sourceFormat, prefillTbl, false )
local fieldStr = p.buildFromFieldDefinitions( fieldDefinitions, sourceFormat, prefillTbl, varsTbl, false )
-- mw.log( "FlexForm result: " .. mw.text.unstrip( str ) )--
 
-- outputformat=debug
local outputFormat = frame.args["outputformat"] or nil
if outputFormat == "debug" then return "<pre>" .. actionTags .. fieldStr .. "</pre>" end


return frame:preprocess( actionTags .. fieldStr )
return frame:preprocess( actionTags .. fieldStr )
Regel 141: Regel 168:
@sourceFormat string
@sourceFormat string
@prefillTbl table
@prefillTbl table
@varsTbl table
@isNested bool
@isNested bool
]]--
]]--
p.buildFromFieldDefinitions = function( fieldDefinitions, sourceFormat, prefillTbl, isNested )
p.buildFromFieldDefinitions = function( fieldDefinitions, sourceFormat, prefillTbl, varsTbl, isNested )
--frame = mw.getCurrentFrame()
--frame = mw.getCurrentFrame()
--str = ""
--str = ""
local res = {}
local res = {}
local fieldDefinitions = fieldDefinitions or {}
local fieldDefinitions = fieldDefinitions or {}
local varsTbl = varsTbl or {}


for _,def in ipairs(fieldDefinitions) do
for _,def in ipairs(fieldDefinitions) do
Regel 156: Regel 185:
f = p.createPopulatedFieldset(  
f = p.createPopulatedFieldset(  
def,
def,
p.buildFromFieldDefinitions( def["_fields"], sourceFormat, prefillTbl, true )
p.buildFromFieldDefinitions( def["_fields"], sourceFormat, prefillTbl, varsTbl, true )
)
)
-- str = str .. fieldset
-- str = str .. fieldset
--table.insert(res, fieldset)
--table.insert(res, fieldset)
else
else
f = p.buildFromFieldDefinition( def, sourceFormat, prefillTbl )
f = p.buildFromFieldDefinition( def, sourceFormat, prefillTbl, varsTbl )
end
end
table.insert(res, f )
table.insert(res, f )
Regel 174: Regel 203:
@prefillTbl
@prefillTbl
]]--
]]--
p.buildFromFieldDefinition = function( def, sourceFormat, prefillTbl )
p.buildFromFieldDefinition = function( definition, sourceFormat, prefillTbl, varsTbl )
local def = p.createProxyPairs( definition )
 
local nameWithoutBrackets = ""
if def.name ~= nil then
if def.name ~= nil then
nameWithoutBrackets = string.gsub( def.name, "%[%]", "" )
nameWithoutBrackets = string.gsub( def.name, "%[%]", "" )
else
nameWithoutBrackets = ""
end
end


Regel 189: Regel 219:
else
else
val = def["_default"] or nil
val = def["_default"] or nil
end
-- replace variables, currently 'query' only
local varsTbl = varsTbl or {}
if def["_hasVar"] ~= nil and varsTbl ~= {} then
-- e.g. "_hasVar": [ "query" ]
for _,keyName in ipairs( def["_hasVar"] ) do
-- keyName, usually "query" - does not work with arrays/ tables
if def[keyName] ~= nil then
def[keyName] = p.replaceVariables( def[keyName], varsTbl )
end
end
end
end


Regel 242: Regel 284:
labelText = labelText .. ' <sup class="fa fa-asterisk text-danger" title="verplicht"></sup>'
labelText = labelText .. ' <sup class="fa fa-asterisk text-danger" title="verplicht"></sup>'
end
end
local labelTag = mw.text.tag( "label", { ["for"] = nameWithoutBrackets }, labelText )
-- @todo make sure unique id is defined upfront
--mw.log( "label: " .. labelTag .. " ... " )
local id = def.id or "ff-field-" .. tostring( math.random(100000,999999) )
--local labelTag = mw.text.tag( "label", { ["for"] = nameWithoutBrackets }, labelText )
local labelTag = mw.text.tag( "label", {}, labelText )
local tooltipText = def['_tooltip'] or nil
local tooltipText = def['_tooltip'] or nil
local itemClass = def["_itemClass"] or "ff-item"
 
local itemLayout = "default"
local defaultItemClass = "ff-item"
if def["_itemLayout"] ~= nil then
itemLayout = def["_itemLayout"]
defaultItemClass = ""
end
local itemClass = def["_itemClass"] or defaultItemClass
if def["_type"] == "instance" then
if def["_type"] == "instance" then
itemClass = itemClass .. " ff-item-instances"
itemClass = itemClass .. " ff-item-instances"
end
end
-- _show-on-select-trigger or _data-wssos-value (with underscore) is for adding to the item not the input
local itemShowOnSelectTrigger = def["_show-on-select-trigger"] or def["_data-wssos-value"] or nil
local itemShowOnSelectTrigger = def["_show-on-select-trigger"] or def["_data-wssos-value"] or nil


Regel 306: Regel 359:
end
end
selectedVal = selectedVal or nil
selectedVal = selectedVal or nil
local attributes = p.removeNonFFAttributes( attributes )
inputField = mFFInput.createTextInput( nameWithoutBrackets, attributes, selectedVal )
inputField = mFFInput.createTextInput( nameWithoutBrackets, attributes, selectedVal )
elseif inputType == "number" then
elseif inputType == "number" then
Regel 313: Regel 367:
end
end
selectedVal = selectedVal or nil
selectedVal = selectedVal or nil
local attributes = p.removeNonFFAttributes( attributes )
inputField = mFFInput.createNumberInput( nameWithoutBrackets, attributes, selectedVal )
inputField = mFFInput.createNumberInput( nameWithoutBrackets, attributes, selectedVal )
elseif inputType == "date" then
elseif inputType == "date" then
Regel 325: Regel 380:
itemClass = "d-none"
itemClass = "d-none"
selectedVal = selectedVal or nil
selectedVal = selectedVal or nil
local attributes = p.removeNonFFAttributes( attributes )
inputField = mFFInput.createTextInput( nameWithoutBrackets, attributes, selectedVal )
inputField = mFFInput.createTextInput( nameWithoutBrackets, attributes, selectedVal )
elseif inputType == "textarea" then
elseif inputType == "textarea" then
Regel 353: Regel 409:
local selectedVal = selectedVal or nil
local selectedVal = selectedVal or nil
inputField = mFFInput.createTokenField( nameWithoutBrackets, attributes, selectedVal, options )
inputField = mFFInput.createTokenField( nameWithoutBrackets, attributes, selectedVal, options )
--mw.log( "FlexForm token: " .. inputField )
elseif inputType == "select" then
elseif inputType == "select" then
if ( attributes['_options'] ~= nil and attributes['_options'] ~= "" ) then
if ( attributes['_options'] ~= nil and attributes['_options'] ~= "" ) then
Regel 360: Regel 415:
local attributes = p.removeNonFFAttributes( attributes )
local attributes = p.removeNonFFAttributes( attributes )
local selectedVal = selectedVal or nil
local selectedVal = selectedVal or nil
--mw.log( selectedVal or "select : selectedVal: none!" )
inputField = mFFInput.createSelectField( nameWithoutBrackets, attributes, selectedVal, options )
inputField = mFFInput.createSelectField( nameWithoutBrackets, attributes, selectedVal, options )
--mw.log( "FlexForm select: " .. inputField )
elseif inputType == "checkboxes" or inputType == "checkbox" then
elseif inputType == "checkboxes" or inputType == "checkbox" then
if ( attributes["_options"] ~= nil and attributes["_options"] ~= "" ) then
if ( attributes["_options"] ~= nil and attributes["_options"] ~= "" ) then
Regel 385: Regel 438:
-- @todo make configurable - starLabel, starsLabel, isCancellable, cancelTitle, static  
-- @todo make configurable - starLabel, starsLabel, isCancellable, cancelTitle, static  
inputField = mFFInput.createRatingField( nameWithoutBrackets, selectedVal, steps, "ster", "sterren", "true", "Annuleren" )
inputField = mFFInput.createRatingField( nameWithoutBrackets, selectedVal, steps, "ster", "sterren", "true", "Annuleren" )
elseif inputType == "free" then
-- can be used with _content
standalone = true
inputField = attributes["_content"] or ""
elseif attributes["_htmlElement"] ~= nil then
elseif attributes["_htmlElement"] ~= nil then
-- method closest to FlexForm's JSON schema
-- method closest to FlexForm's JSON schema
local el = attributes["_htmlElement"]
local el = attributes["_htmlElement"]
local content = attributes["_content"] or ""
local content = attributes["_content"] or ""
local sosTrigger = attributes["show-on-select-trigger"] or attributes["data-wssos-value"] or nil
local sosTrigger = attributes["show-on-select-trigger"] or nil
if sosTrigger ~= nil then
if sosTrigger ~= nil then
-- because Lua somehow throws away this attribute
-- because "show-on-select-trigger" can end up being thrown away (by Lua?)
-- data attributes are safer anyway
attributes["data-wssos-value"] = sosTrigger
attributes["data-wssos-value"] = sosTrigger
end
end
Regel 410: Regel 468:
local res = inputField
local res = inputField
return res
return res
elseif itemLayout == "compact" then
labelTag = nil
return p.buildCompactItemLayout( inputField, itemClass )
else
else
local res = p.buildItemLayout( labelTag, tooltipText, inputField, beforeInputField, itemClass, itemShowOnSelectTrigger )
local res = p.buildItemLayout( labelTag, tooltipText, inputField, beforeInputField, itemClass, itemShowOnSelectTrigger )
-- reset shouldn't be necessary any more:
labelTag = nil
labelTag = nil
return res
return res
Regel 446: Regel 508:
:wikitext( labelItem .. fieldItem )
:wikitext( labelItem .. fieldItem )
if itemShowOnSelectTrigger ~= nil then
if itemShowOnSelectTrigger ~= nil then
-- show-on-select-trigger
-- = show-on-select-trigger
resDiv:attr( "data-wssos-value", itemShowOnSelectTrigger )
resDiv:attr( "data-wssos-value", itemShowOnSelectTrigger )
end
end
return tostring(resDiv)
end
--[[
Sober, compact layout - no labels, tooltips, notes, etc.
especially for avoiding clutter in multiple-instance setups
(where some of the info doesn't need repeating)
]]--
p.buildCompactItemLayout = function( inputField, itemClass )
if itemClass == nil then itemClass = "ff-layout-compact" end
local resDiv = mw.html.create( "div" )
:attr( "class", itemClass )
:wikitext( inputField )
return tostring(resDiv)
return tostring(resDiv)
end
end
Regel 487: Regel 562:


--[[
--[[
For JSON content, use this not mw.loadJsonData()
@page string - full name of page
@page string - full name of page
@slot - name of slot
@slot - name of slot
@template - name of the template (without NS prefix)
@template - name of the template (without NS prefix)
@return table
@return table - note differences between JSON and wikitext templates
because the latter uses the slotData format
]]--
]]--
p.getSlotContent = function( page, slot, template )
p.getSlotContent = function( page, slot, template )
local template = template or nil
local template = template or nil
if ( template == "" or template == nil ) then
 
if ( template == nil or template == "" ) then
-- Assuming JSON
-- Assuming JSON
if slot == "main" then
--loadJsonData does not alway work. Use WSSlots instead
--return mw.loadJsonData( page )
end
local str = mw.slots.slotContent( slot, page )
local str = mw.slots.slotContent( slot, page )
if type(str) == "table" then
if type(str) == "table" then
Regel 505: Regel 580:
return str
return str
end
end
if str ~= nil then
if str ~= nil and str ~= "" then
return mw.text.jsonDecode( str )
return mw.text.jsonDecode( str )
else
else
Regel 515: Regel 590:
local slotData = mw.slots.slotData( slot, page )
local slotData = mw.slots.slotData( slot, page )
if ( slotData == nil ) then
if ( slotData == nil ) then
return type(slotData)
return {}
end
end
return slotData[template]
return slotData[template]
Regel 530: Regel 605:


--[[
--[[
@def table - readonly
@fieldsetContent
if ( content == nil ) then
if ( content == nil ) then
str = mw.slots.slotContent( slot, page )
str = mw.slots.slotContent( slot, page )
Regel 536: Regel 613:
return "<pre>" .. mw.text.jsonEncode( content ) .. "</pre>"
return "<pre>" .. mw.text.jsonEncode( content ) .. "</pre>"
]]--
]]--
p.createPopulatedFieldset = function( def, fieldsetContent )
-- create proxy for def
-- or; local attrbs = p.createProxyPairs( def )
local attrbs = {}
for k,v in pairs( def ) do attrbs[k] = v end


p.createPopulatedFieldset = function( def, fieldsetContent )
local def = def or {}
local fieldsetContent = fieldsetContent or ""
local fieldsetContent = fieldsetContent or ""
local fieldDefs = def["_fields"] or {}
local fieldDefs = attrbs["_fields"] or {}
local itemShowOnSelectTrigger = def["_show-on-select-trigger"] or def["_data-wssos-value"] or nil
if itemShowOnSelectTrigger ~= nil then def["data-wssos-value"] = itemShowOnSelectTrigger end
-- show-on-select-trigger does not appear to work in Lua
local itemShowOnSelectTrigger = attrbs["show-on-select-trigger"] or nil
if itemShowOnSelectTrigger ~= nil then attrbs["data-wssos-value"] = itemShowOnSelectTrigger end
 
return mw.text.tag(
return mw.text.tag(
"fieldset",
"fieldset",
p.removeNonFFAttributes( def ),
p.removeNonFFAttributes( attrbs ),
fieldsetContent or ""
fieldsetContent or ""
)
)
end
end


p.setupActionTags = function( page, mode, schemaTbl, fieldDefinitions )
--[[
if mode ~= "create" and mode ~= "edit"  then return "" end
Get the action definition(s) selected by 'mode'
@return table
]]--
p.getSelectedActionDefs = function( page, mode, schemaTbl )
if mode == nil or mode == "" then return {} end
 
-- modes define which action tag must be used
local modes = schemaTbl.modes or nil
local actions = schemaTbl.actions or nil
if modes == nil or actions == nil then return {} end
local actionIds = modes[mode] or nil
if actionIds == nil then return {} end
local actionDefs = {}
for _,id in ipairs( actionIds ) do
table.insert(actionDefs, actions[id] )
end
return actionDefs
end
 
--local actionTags = p.setupActionTagsNew( page, actionDefs, fieldDefinitions )
p.setupActionTags = function( page, actionDefs, fieldDefinitions )
local actionTags = ""
for _,def in ipairs( actionDefs ) do
actionTags = actionTags .. p.createActionTag( page, def, fieldDefinitions )
end
return actionTags
end
 
--[[
DEPRECATED - set up _create or _edit elements
@page
@mode
@schemaTbl
@fieldDefinitions
]]--
p.setupActionTagsOld = function( page, mode, schemaTbl, fieldDefinitions )
if mode == nil or mode == "" then return "" end
--if mode ~= "create" and mode ~= "edit"  then return "" end


-- modes define which action tag must be used
-- modes define which action tag must be used
Regel 580: Regel 702:


--[[
--[[
work in progress
@actionDefs table
@fieldDefinitions table
@todo table
]]--
p.maybeSubtractFromFieldDefinitions = function( actionDefs, fieldDefs )
-- first get excludable fields from action defs
local excludableFields = {}
for _,def in ipairs( actionDefs ) do
if def["_excludeIds"] ~= nil then
for i,field in ipairs( def["_excludeIds"] ) do
table.insert( excludableFields, field )
end
end
--[[
if def["_excludeFields"] ~= nil then
for i,field in ipairs( def["_excludeFields"] ) do
table.insert( excludableFields, field )
end
end
--]]
end
-- no change
if excludableFields == {} then return fieldDefs end
local newFieldDefs = {}
for _,def in ipairs( fieldDefs ) do
local id = def["_id"] or ""
-- ocal name = def["name"] or ""
--local fieldName = string.gsub( mw.text.trim( name ), "%[%]", "" )
if p.isValueInSequentialTable( id, excludableFields ) then
-- do nothing
else
table.insert( newFieldDefs, def )
end
end
 
return newFieldDefs
end
 
--[[
Set up a _create or _edit element
@page string
@page string
@attrs table (read-only, assoc)
@attrs table (read-only, assoc)
Regel 596: Regel 758:
if attrbs["mwfields"] == nil then
if attrbs["mwfields"] == nil then
local fieldsTbl = p.getFieldNames( fieldDefs )
local fieldsTbl = p.getFieldNames( fieldDefs )
if attrbs["_excludeFields"] ~= nil then
-- new: _excludeFields => _excludeIds
if attrbs["_excludeIds"] ~= nil then
-- when using multiple _creates you may want to exclude specific fields
-- when using multiple _creates you may want to exclude specific fields
local invalidFields = mw.text.split( attrbs["_excludeFields"], "," )
-- local invalidFields = mw.text.split( attrbs["_excludeIds"], "," )
fieldsTbl = p.removeValuesFromSequentialTable( fieldsTbl, invalidFields )
fieldsTbl = p.removeValuesFromSequentialTable( fieldsTbl, attrbs["_excludeIds"] )
end
end
attrbs["mwfields"] = table.concat( fieldsTbl, "," )
attrbs["mwfields"] = table.concat( fieldsTbl, "," )
Regel 608: Regel 771:
if attrbs["_type"] ~= nil then
if attrbs["_type"] ~= nil then
tag = attrbs["_type"]
tag = attrbs["_type"]
end
if attrbs["mwformat"] == "json" and attrbs["mwtemplate"] == nil then
attrbs["mwtemplate"] = "wsnone"
end
end


Regel 617: Regel 784:
--[[
--[[
Get the field names (as a table) so that they can be added to 'mwfields'
Get the field names (as a table) so that they can be added to 'mwfields'
includes field names nested in a fieldset
@fields table|nil
@fields table|nil
@return table
@return table
Regel 624: Regel 792:
local fieldNames = {}
local fieldNames = {}
for _,field in ipairs(fields) do
for _,field in ipairs(fields) do
if field.name ~= nil then
if field["_type"] == "fieldset" then
--recursive
local fieldsetNames = p.getFieldNames( field["_fields"] )
for _,fieldsetName in ipairs( fieldsetNames ) do
table.insert(fieldNames, fieldsetName)
end
elseif field.name ~= nil then
-- remove [] if any
-- remove [] if any
local fieldName = string.gsub( mw.text.trim(field.name), "%[%]", "" )
local fieldName = string.gsub( mw.text.trim(field.name), "%[%]", "" )
table.insert(fieldNames, fieldName)
table.insert(fieldNames, fieldName)
end
end
Regel 647: Regel 821:
end
end


--[[
revise!
]]
p.removeValuesFromSequentialTable = function( tbl, invalidFields )
p.removeValuesFromSequentialTable = function( tbl, invalidFields )
--[[ DO NOT DO THIS:
for _,nonValid in ipairs(invalidFields) do
for _,nonValid in ipairs(invalidFields) do
for i,v in ipairs(tbl) do
for i,v in ipairs(tbl) do
Regel 656: Regel 834:
end
end
return tbl
return tbl
]]--
local newTable = {}
for _,field in ipairs( tbl ) do
if p.isValueInSequentialTable( field, invalidFields ) then
--do nothing
else
table.insert( newTable, field )
end
end
return newTable
--[[
-- compare: remove select fieldDefinitions in reverse order
for i = #fieldDefs, 1, -1 do
local name = fieldDefs[i]["name"] or ""
local fieldName = string.gsub( mw.text.trim( name ), "%[%]", "" )
mw.log( "fieldName: " .. fieldName )
for _,field in ipairs( excludableFields ) do
if fieldName == field then
mw.log( "equal to excludable field: " .. field )
fieldDefs[i] = nil
end
end
end
]]--
end
p.isValueInSequentialTable = function( val, tbl )
for _,v in ipairs( tbl ) do
if v == val then return true end
end
return false
end
end


Regel 667: Regel 878:


--[[
--[[
Create standardised prefill table as k/v pairs
Create standardised prefill table as k/v pairs from
from either pre-existing content (JSON, wikitext) when editing
either pre-existing content (JSON, wikitext) when editing
or defaults when creating a page
or defaults when creating a page
@defaults
For structural differences between JSON/wiki template, see getSlotContent()
 
@sourceFormat
@slotContent table|nil
@defaultStr
@defaultSep
@return table
]]--
]]--
p.createPrefillTable = function( sourceFormat, slotContent, defaultStr, defaultsSep )
p.createPrefillTable = function( sourceFormat, slotContent, defaultStr, defaultsSep )
--mw.log( "createPrefillTable. Type of slotContent = " .. type(slotContent) )
local slotContent = slotContent or nil
local slotContent = slotContent or nil
local defaults = defaults or nil
local defaults = defaults or nil
Regel 682: Regel 900:
-- note ["_text"]
-- note ["_text"]
for k,v in pairs( slotContent[1] ) do
for k,v in pairs( slotContent[1] ) do
-- slotData format
prefillTbl[k] = v["_text"] or nil
prefillTbl[k] = v["_text"] or nil
end
end
elseif defaultStr ~= nil then
elseif defaultStr ~= nil and defaultStr ~= "" then
-- new page
-- new page
mw.log( "defaults: " .. defaultStr )
local defaults = mw.text.split( defaultStr, defaultsSep )
local defaults = mw.text.split( defaultStr, defaultsSep )
for _,v in ipairs( defaults ) do
for _,v in ipairs( defaults ) do
Regel 695: Regel 915:
end
end
return prefillTbl
return prefillTbl
end
--[[
Transform user-provided string into a table (k/v)
@varStr string
@sep string
@return table
]]--
p.createVarsTable = function( varStr, sep )
if varStr == "" then return {} end
local sep = sep or "###"
local vars = mw.text.split( varStr, sep )
local varTbl = {}
for _,v in ipairs( vars ) do
local pair = mw.text.split( v, "=" )
if pair[2] ~= nil then
varTbl[ mw.text.trim( pair[1] ) ] = mw.text.trim( pair[2] )
end
end
return varTbl
end
p.replaceVariables = function( str, varsTbl )
if str == "" or varsTbl == {} then return str end
--def["query"]
-- vars def["_vars"]
-- string.gsub( def.name, "%[%]", "" )
local newStr = str
for k,v in pairs(varsTbl) do
newStr = string.gsub( newStr, k, v )
end
mw.log( "variables replaced: " .. newStr )
return newStr
end
--[[ Create proxy for read-only indexed table ]]--
p.createProxyIpairs = function( tbl )
local newTbl = {}
for _,v in ipairs( tbl ) do table.insert( newTbl, v ) end
return newTbl
end
--[[ Create proxy for read-only associative table ]]--
p.createProxyPairs = function( tbl )
local newTbl = {}
for k,v in pairs( tbl ) do newTbl[k] = v end
return newTbl
end
end


return p
return p

Huidige versie van 8 okt 2025 10:22

Module:FFSchema

Summary
Allows you to use JSON schemas to create forms.



local p = {}
local mFFInput = require('Module:FFInput')

--[[
Separate handler for base props (currently)
{{#invoke:FFSchema|class|id= 
|page=  // mandatory
|class= // mandatory
|slot= // defaults to ws-base-props
|fields= // defaults to Class
|template=  // defaults to Base properties
|mwfollow=true (default)/false
}}
]]--
p.class = function(frame)
	-- params:
	local newPage = frame.args.page or ""
	local class = frame.args.class or ""
	if newPage == "" or class == "" then return "" end
	-- 
	local id = frame.args.id or "create-class-instance"
	local slot = frame.args.slot or "ws-base-props"
	-- currently one val only
	local fieldStr = frame.args.fields or "Class"
	local template = frame.args.template or "Base properties"
	
	local mwfollow = frame.args.mwfollow or "true"

	-- e.g. <_create id="wscreate-base" mwwrite="Titel/{{#tool:randomint|1000000|9999999}}" mwslot="ws-base-props" mwtemplate="Base properties" mwfields="Class" mwfollow="true" />
	local createAttrbs = {
		id = id,
		mwwrite = newPage,
		mwslot = slot,
		mwtemplate = template,
		mwfields = fieldStr,
		mwfollow = mwfollow
	}
	local createTag = mw.text.tag( "_create", createAttrbs, false )

	-- e.g. <_input type="hidden" name="Class" value="Titles" />
	local classAttrbs = {
		["type"] = "hidden",
		name = fieldStr,
		value = class
	}
	local classInput = mw.text.tag( "_input", classAttrbs, false )

	return frame:preprocess( createTag .. classInput )
end

--[[
main entry point - @todo rename wiki

@frame table
- schema (olim json) - pagename of JSON schema
- page - title of existing page, or title of new page to be created
- slot
- template - wiki template if any. Without it, we're assuming the content is JSON (detect content model with WSSlots)? @todo: what if we are in interested in the raw content?

create
{{#invoke:FFSchema |wiki
|schema=Json:FFSchema23
|mode=create
|page=Titel/{{#tool:randomint|1000000|9999999}}
|slot=ws-page-props
|template=Text
|defaults=...
}}

edit
{{#invoke:FFSchema |wiki
|schema=Json:FFSchema23
|mode=edit
|page={{FULLPAGENAME}}
...etc
}}

mode (optional)
The schema should define a couple of possible 'modes', currently 'create' and 'edit'. They can be used to add the necessary _create or _edit elements. For each of these modes, the corresponding 'actions' are defined.
@todo Consider custom actions other than edit and create

Field definitions accept two types of parameters:
- those that are native to FlexForm
- additional parameters beginning with an underscore (_) that can assist in some tedious tasks, including _type, _label, _default,_fields _options, _optionsep, _fields, _instance, _note (before), _tooltip, _itemLayout (@todo), _itemClass, _htmlElement and _content, _filtertemplate, _show-on-select-trigger (or _data-wssos-value), _hasVar

with select/token: 
- with _options you can set 'arrays' of either single values or pairs of 'label' and 'value'

At the time of writing, FlexForm is not stable with regard to its JavaScript implementation of instances 

]]--

p.wiki = function(frame)
	-- 'wiki' = legacy name
	return p.page(frame)
end

p.page = function(frame)
	local schema = frame.args.schema or frame.args.json or ""
	-- read-only!
	-- @todo check that schema exists and is valid

	local schemaTbl = mw.loadJsonData( schema )
	-- cannot use getSlotContent because regular user may not have 'read' rights for the JSON page
	--local schemaTbl = p.getSlotContent( schema, "main" )
	if schemaTbl == nil then return "" end

	local mode = frame.args.mode or "fields"
	local page = frame.args.page or ""
	local slot = frame.args.slot or "main"
	local template = frame.args.template or nil
	local sourceFormat = "json"
	if ( template ~= nil and template ~= "" ) then
		sourceFormat = "wiki"
	end

	-- default values in the format name=value ### name=value ### etc.
	-- allows for custom delimiter/sep
	local defaults = frame.args.defaults or ""
	local defaultsSep = frame.args.defaultssep or "###"
	-- vars for query
	local varStr = frame.args.vars or ""
	local varsTbl = p.createVarsTable( varStr, "###" )

	-- get content from page/slot for autofill
	local slotContent = nil
	local title = nil
	if page ~= "" then
		title = mw.title.new( page )
	end
	if title ~= nil and title.exists then
		if sourceFormat == "wiki" then
			slotContent = p.getSlotContent( page, slot, template )
		else
			slotContent = p.getSlotContent( page, slot, nil )
		end
	end
	-- for now, attempt nothing if page does not exist, except for defaults

	local prefillTbl = p.createPrefillTable( sourceFormat, slotContent, defaults, defaultsSep )

	-- fields = form field definitions
	local fieldDefinitions = nil
	if schemaTbl.fields ~= nil then
		fieldDefinitions = schemaTbl.fields
	else
		return ""
	end

	-- edit/create actions or ""
	local actionDefs = p.getSelectedActionDefs( page, mode, schemaTbl )
	local actionTags = p.setupActionTags( page, actionDefs, fieldDefinitions )
	-- note: turns fieldDefinitions into proxy 
	fieldDefinitions = p.maybeSubtractFromFieldDefinitions( actionDefs, fieldDefinitions )

	-- fields
	local fieldStr = p.buildFromFieldDefinitions( fieldDefinitions, sourceFormat, prefillTbl, varsTbl, false )

	-- outputformat=debug
	local outputFormat = frame.args["outputformat"] or nil
	if outputFormat == "debug" then return "<pre>" ..  actionTags .. fieldStr .. "</pre>" end

	return frame:preprocess( actionTags .. fieldStr )
end

--[[
@fieldDefinitions table
@sourceFormat string
@prefillTbl table
@varsTbl table
@isNested bool
]]--
p.buildFromFieldDefinitions = function( fieldDefinitions, sourceFormat, prefillTbl, varsTbl, isNested )
	--frame = mw.getCurrentFrame()
	--str = ""
	local res = {}
	local fieldDefinitions = fieldDefinitions or {}
	local varsTbl = varsTbl or {}

	for _,def in ipairs(fieldDefinitions) do
		local f
		if def["_type"] == "fieldset" and def["_fields"] ~= nil then
			-- ! needs to be tested -- inputType = def["_type"] or nil
			-- @todo allow other custom definitions
			f = p.createPopulatedFieldset( 
				def,
				p.buildFromFieldDefinitions( def["_fields"], sourceFormat, prefillTbl, varsTbl, true )
			)
			-- str = str .. fieldset
			--table.insert(res, fieldset)
		else
			f = p.buildFromFieldDefinition( def, sourceFormat, prefillTbl, varsTbl )
		end
		table.insert(res, f )
	end
	return table.concat(res,"")
	--return str
end

--[[
@def table
@sourceFormat string
@prefillTbl
]]--
p.buildFromFieldDefinition = function( definition, sourceFormat, prefillTbl, varsTbl )
	local def = p.createProxyPairs( definition )

	local nameWithoutBrackets = ""
	if def.name ~= nil then
		nameWithoutBrackets = string.gsub( def.name, "%[%]", "" )
	end

	local val = nil
	if nameWithoutBrackets == "" then
		--do nothing
	elseif prefillTbl ~= nil then
		val = prefillTbl[nameWithoutBrackets] or nil
		if val == "" or val == nil then val = def["_default"] or nil end
	else
		val = def["_default"] or nil
	end

	-- replace variables, currently 'query' only
	local varsTbl = varsTbl or {}
	if def["_hasVar"] ~= nil and varsTbl ~= {} then
		-- e.g. "_hasVar": [ "query" ] 
		for _,keyName in ipairs( def["_hasVar"] ) do
			-- keyName, usually "query" - does not work with arrays/ tables
			if def[keyName] ~= nil then
				def[keyName] = p.replaceVariables( def[keyName], varsTbl )
			end
		end
	end

	--[[	
	if sourceFormat == "wiki" then
		-- todo could more efficient
		--mw.log( "wiki content " )
		if ( slotContent ~= nil and slotContent[1] ~= nil and slotContent[1][nameWithoutBrackets] ~= nil ) then
			--[param]["_text"]
			val = slotContent[1][nameWithoutBrackets]["_text"] or nil
			if val == "" or val == nil then 
				val = def["_default"] or nil end
		else
			val = def["_default"] or nil
		end
	elseif sourceFormat == "json" and slotContent ~= nil then
		--mw.log( "JSON content " )
		--mw.log( "type: " .. type(slotContent[nameWithoutBrackets]) .. "<br>" )
		--val may be string, table or nil
		--mw.log( slotContent[nameWithoutBrackets] )
		val = slotContent[nameWithoutBrackets] or nil
		if val == "" or val == nil then
			val = def["_default"] or nil
		end
	else
		mw.log( "No content detected " )
	end

	if type(val) ~= "table" then
		mw.log( "val: " .. ( val or "(none)" ) )
	end

	]]--

	return p.parseFieldDefinition( def, val )
end

--[[
@def
@selectedVal string, table (JSON instance only) or nil
@boolean
]]--
p.parseFieldDefinition = function( def, selectedVal )
	--name = def.name or ""
	local nameWithoutBrackets = string.gsub( def.name or "", "%[%]", "" )
	local inputType = def["_type"] or nil
	selectedVal = selectedVal or nil

	-- @todo revise 'for'
	local labelText = def['_label'] or ""
	if def["required"] ~= nil and def["required"] == "required" then
		-- @todo localisation, make choice configurable, not dependent on Bootstrap
		labelText = labelText .. ' <sup class="fa fa-asterisk text-danger" title="verplicht"></sup>'
	end
	-- @todo make sure unique id is defined upfront
	local id = def.id or "ff-field-" .. tostring( math.random(100000,999999) )
	--local labelTag = mw.text.tag( "label", { ["for"] = nameWithoutBrackets }, labelText )
	local labelTag = mw.text.tag( "label", {}, labelText )
	local tooltipText = def['_tooltip'] or nil

	local itemLayout = "default"
	local defaultItemClass = "ff-item"
	if def["_itemLayout"] ~= nil then 
		itemLayout = def["_itemLayout"]
		defaultItemClass = ""
	end
	local itemClass = def["_itemClass"] or defaultItemClass
	if def["_type"] == "instance" then
		itemClass = itemClass .. " ff-item-instances"
	end

	-- _show-on-select-trigger or _data-wssos-value (with underscore) is for adding to the item not the input
	local itemShowOnSelectTrigger = def["_show-on-select-trigger"] or def["_data-wssos-value"] or nil

	-- note before input
	local beforeInputField = ""
	local note = def['_note'] or nil
	if note ~= nil and note ~= "" then
		local currentFrame = mw.getCurrentFrame()
		beforeInputField = "<div class='ff-note'>" .. currentFrame:preprocess(note) .. "</div>"
	end
	
	-- whether to ignore item layout:
	local standalone = false

	local attributes = {}
	for k,v in pairs( def ) do
		attributes[k] = v or ""
	end

	-- optional pre-filter for selected values (string only)
	if attributes["_filtertemplate"] ~= nil and selectedVal ~= nil then
		local currentFrame = mw.getCurrentFrame()
		selectedVal = currentFrame:expandTemplate{
			title = attributes["_filtertemplate"], 
			args = { selectedVal }
		}
	end

	local inputField = ""
	if inputType == "instance" or inputType == "instances" then
		local instanceLabelTag = labelTag
		if attributes.template ~= nil then
			-- wiki template version
			if selectedVal ~= nil then
				attributes["default-content"] = mw.text.nowiki( selectedVal )
			end
			inputField = p.createTemplateInstance( attributes )
		else
			-- JSON version
			selectedVal = selectedVal or nil
			if type(selectedVal) == "table" then
				attributes["default-content"] = mw.text.nowiki( mw.text.jsonEncode( selectedVal ) )
			elseif type(selectedVal) == "string" then
				-- shouldn't happen but..
				attributes["default-content"] = mw.text.nowiki( selectedVal )
			end
			inputField = p.createJsonInstance( attributes )
		end
		-- todo
		-- attributes.fields
		--??
		labelTag = instanceLabelTag
	elseif inputType == "text" then
		-- @todo default class
		if ( attributes.class == nil ) then
			attributes.class = "form-control"
		end
		selectedVal = selectedVal or nil
		local attributes = p.removeNonFFAttributes( attributes )
		inputField = mFFInput.createTextInput( nameWithoutBrackets, attributes, selectedVal )
	elseif inputType == "number" then
		-- @todo default class
		if ( attributes.class == nil ) then
			attributes.class = "form-control"
		end
		selectedVal = selectedVal or nil
		local attributes = p.removeNonFFAttributes( attributes )
		inputField = mFFInput.createNumberInput( nameWithoutBrackets, attributes, selectedVal )
	elseif inputType == "date" then
		if ( attributes.class == nil ) then
			attributes.class = "form-control"
		end
		selectedVal = selectedVal or nil
		local attributes = p.removeNonFFAttributes( attributes )
		inputField = mFFInput.createDateInput( nameWithoutBrackets, attributes, selectedVal )
	elseif inputType == "hidden" then
		--todo - temporary solutipn
		itemClass = "d-none"
		selectedVal = selectedVal or nil
		local attributes = p.removeNonFFAttributes( attributes )
		inputField = mFFInput.createTextInput( nameWithoutBrackets, attributes, selectedVal )
	elseif inputType == "textarea" then
		-- @todo default class
		if attributes.class == nil then
			attributes.class = "form-control"
		end
		local attributes = p.removeNonFFAttributes( attributes )
		local selectedVal = selectedVal or ""
		inputField = mFFInput.createTextarea( nameWithoutBrackets, attributes, selectedVal)
	elseif ( inputType == "token" or inputType == "tokens" ) then
		-- @todo just an example
		-- @todo remove stuff from attributes that does not belong 
		-- run attributes through a validator
		local options = {}
		if ( attributes['_options'] ~= nil and attributes['_options'] ~= "" ) then
			options = attributes['_options']
			if type(options) == "string" then
				-- expand wikitext
				local currentFrame = mw.getCurrentFrame()
				local optionText = currentFrame:preprocess( options )
				-- @todo what's a good for default for _optionsep (semi-colon?)
				local optionSep = attributes['_optionsep'] or "\n"
				options = mw.text.split( optionText, optionSep )
			end
		end
		local attributes = p.removeNonFFAttributes( attributes )
		local selectedVal = selectedVal or nil
		inputField = mFFInput.createTokenField( nameWithoutBrackets, attributes, selectedVal, options )
	elseif inputType == "select" then
		if ( attributes['_options'] ~= nil and attributes['_options'] ~= "" ) then
			options = attributes['_options']
		end
		local attributes = p.removeNonFFAttributes( attributes )
		local selectedVal = selectedVal or nil
		inputField = mFFInput.createSelectField( nameWithoutBrackets, attributes, selectedVal, options )
	elseif inputType == "checkboxes" or inputType == "checkbox" then
		if ( attributes["_options"] ~= nil and attributes["_options"] ~= "" ) then
			options = attributes["_options"]
		end
		local attributes = p.removeNonFFAttributes( attributes )
		local selectedVal = selectedVal or nil
		inputField = mFFInput.createCheckInputField( "checkbox", nameWithoutBrackets, attributes, selectedVal, options )
	elseif inputType == "radio" then
		if ( attributes["_options"] ~= nil and attributes["_options"] ~= "" ) then
			options = attributes["_options"]
		end
		local attributes = p.removeNonFFAttributes( attributes )
		local selectedVal = selectedVal or nil
		inputField = mFFInput.createCheckInputField( "radio", nameWithoutBrackets, attributes, selectedVal, options )
	elseif inputType == "rating" then
		local steps = nil
		if attributes["_options"] ~= nil then
			-- semi-colon separated list from 0.5;
			steps = attributes["_options"]
		end
		-- @todo make configurable - starLabel, starsLabel, isCancellable, cancelTitle, static 
		inputField = mFFInput.createRatingField( nameWithoutBrackets, selectedVal, steps, "ster", "sterren", "true", "Annuleren" )
	elseif inputType == "free" then
		-- can be used with _content
		standalone = true
		inputField = attributes["_content"] or ""
	elseif attributes["_htmlElement"] ~= nil then
		-- method closest to FlexForm's JSON schema
		local el = attributes["_htmlElement"]
		local content = attributes["_content"] or ""
		local sosTrigger = attributes["show-on-select-trigger"] or nil
		if sosTrigger ~= nil then
			-- because "show-on-select-trigger" can end up being thrown away (by Lua?)
			-- data attributes are safer anyway
			attributes["data-wssos-value"] = sosTrigger
		end
		local attributes = p.removeNonFFAttributes( attributes )
		inputField = mw.text.tag( el, attributes, content )
		--[[
		inputFieldTbl = mw.html.create( el )
			:attr( attributes )
			:wikitext( content )
		inputField = tostring(inputFieldTbl)
		]]--
		standalone = true
	end

	-- item	
	-- @todo id and show-on-select id on 
	if standalone then
		local res = inputField
		return res
	elseif itemLayout == "compact" then
		labelTag = nil
		return p.buildCompactItemLayout( inputField, itemClass )
	else
		local res = p.buildItemLayout( labelTag, tooltipText, inputField, beforeInputField, itemClass, itemShowOnSelectTrigger )
		-- reset shouldn't be necessary any more:
		labelTag = nil
		return res
	end
end

--[[
@labelTag
@tooltipText string|nil
@inputField
@beforeInputField - optional short message to be inserted just before the input
@itemClass string
@itemShowOnSelectTrigger nil|string
]]--
p.buildItemLayout = function( labelTag, tooltipText, inputField, beforeInputField, itemClass, itemShowOnSelectTrigger )
	local tooltip = ""
	if tooltipText ~= nil then
		-- smw tooltip
		local frame = mw.getCurrentFrame()
		-- @todo create tooltip which does not depend on SMW's info
		tooltip = frame:callParserFunction( '#info', { tooltipText } )
	end
	local labelDiv = mw.html.create( "div" )
		:attr( "class", "item-label" )
		:wikitext( labelTag .. tooltip )
	--labelItem = "<div class='item-label'>" .. labelTag .. "</div>"
	local labelItem = tostring(labelDiv)
	local fieldDiv = mw.html.create( "div" )
		:attr( "class", "item-field" )
		:wikitext( beforeInputField .. inputField )
	local fieldItem = tostring(fieldDiv)
	local resDiv = mw.html.create( "div" )
		:attr( "class", itemClass )
		:wikitext( labelItem .. fieldItem )
	if itemShowOnSelectTrigger ~= nil then
		-- = show-on-select-trigger
		resDiv:attr( "data-wssos-value", itemShowOnSelectTrigger )
	end
	return tostring(resDiv)
end

--[[
Sober, compact layout - no labels, tooltips, notes, etc.
especially for avoiding clutter in multiple-instance setups 
(where some of the info doesn't need repeating)
]]--
p.buildCompactItemLayout = function( inputField, itemClass )
	if itemClass == nil then itemClass = "ff-layout-compact" end
	local resDiv = mw.html.create( "div" )
		:attr( "class", itemClass )
		:wikitext( inputField )
	return tostring(resDiv)
end

p.createTemplateInstance = function( attributes )
	local itemStr = ""
	--frame = mw.getCurrentFrame()
	for _,def in ipairs( attributes['_fields'] or {} ) do
		-- @todo defaults?
		--val = def["_default"] or nil
		--don't use 'val' arg because the instance takes care of this
		itemStr = itemStr .. p.parseFieldDefinition( def, nil )
	end
	local attributes = p.removeNonFFAttributes( attributes )

	--content = "<div class='item-instance'>" .. itemStr .. "</div>"
	local contentDiv = mw.html.create("div")
		:attr( "class", "item-instance" )
		:wikitext( itemStr )
	local content = tostring(contentDiv)
	return mw.text.tag( "_instance", attributes, content )
end

-- Identical to createTemplateInstance, currently
p.createJsonInstance = function( attributes )
	local itemStr = ""
	--name="Items" format="json"
	for _,def in ipairs( attributes['_fields'] or {} ) do
		itemStr = itemStr .. p.parseFieldDefinition( def, nil )
	end
	local attributes = p.removeNonFFAttributes( attributes )
	local contentDiv = mw.html.create("div")
		:attr( "class", "item-instance" )
		:wikitext( itemStr )
	local content = tostring(contentDiv)
	return mw.text.tag( "_instance", attributes, content )
end

--[[
For JSON content, use this not mw.loadJsonData()

@page string - full name of page
@slot - name of slot
@template - name of the template (without NS prefix)
@return table - note differences between JSON and wikitext templates
because the latter uses the slotData format
]]--
p.getSlotContent = function( page, slot, template )
	local template = template or nil

	if ( template == nil or template == "" ) then
		-- Assuming JSON
		local str = mw.slots.slotContent( slot, page )
		if type(str) == "table" then
			-- @todo VERY unlikely
			return str
		end
		if str ~= nil and str ~= "" then
			return mw.text.jsonDecode( str )
		else
			return {}
		end
	end

	-- template
	local slotData = mw.slots.slotData( slot, page )
	if ( slotData == nil ) then
		return {}
	end
	return slotData[template]
end

p.showSlotContent = function( frame )
	local page = frame.args.page
	local slot = frame.args.slot or "main"
	local template = frame.args.template or nil
	local content = p.getSlotContent( page, slot, template )
	local str = mw.text.jsonEncode( content ) 
	return str
end

--[[
@def table - readonly
@fieldsetContent
	if ( content == nil ) then
		str = mw.slots.slotContent( slot, page )
		return "content seems to be nil . " .. st
	end
	return "<pre>" .. mw.text.jsonEncode( content ) .. "</pre>"
]]--
p.createPopulatedFieldset = function( def, fieldsetContent )
	-- create proxy for def
	-- or; local attrbs = p.createProxyPairs( def )
	local attrbs = {}
	for k,v in pairs( def ) do attrbs[k] = v end

	local fieldsetContent = fieldsetContent or ""
	local fieldDefs = attrbs["_fields"] or {}
	
	-- show-on-select-trigger does not appear to work in Lua
	local itemShowOnSelectTrigger = attrbs["show-on-select-trigger"] or nil
	if itemShowOnSelectTrigger ~= nil then attrbs["data-wssos-value"] = itemShowOnSelectTrigger end

	return mw.text.tag(
		"fieldset",
		p.removeNonFFAttributes( attrbs ),
		fieldsetContent or ""
	)
end

--[[
Get the action definition(s) selected by 'mode'
@return table
]]--
p.getSelectedActionDefs = function( page, mode, schemaTbl )
	if mode == nil or mode == "" then return {} end

	-- modes define which action tag must be used
	local modes = schemaTbl.modes or nil
	local actions = schemaTbl.actions or nil
	if modes == nil or actions == nil then return {} end
	
	local actionIds = modes[mode] or nil
	if actionIds == nil then return {} end
	
	local actionDefs = {}
	for _,id in ipairs( actionIds ) do
		table.insert(actionDefs, actions[id] )
	end
	return actionDefs
end

--local actionTags = p.setupActionTagsNew( page, actionDefs, fieldDefinitions )
p.setupActionTags = function( page, actionDefs, fieldDefinitions )
	local actionTags = ""
	for _,def in ipairs( actionDefs ) do
		actionTags = actionTags .. p.createActionTag( page, def, fieldDefinitions )
	end
	return actionTags
end

--[[
DEPRECATED - set up _create or _edit elements
@page
@mode
@schemaTbl
@fieldDefinitions
]]--
p.setupActionTagsOld = function( page, mode, schemaTbl, fieldDefinitions )
	if mode == nil or mode == "" then return "" end
	--if mode ~= "create" and mode ~= "edit"  then return "" end

	-- modes define which action tag must be used
	local modes = schemaTbl.modes or nil
	local actions = schemaTbl.actions or nil
	if modes == nil or actions == nil then return "" end
	
	local actionIds = modes[mode] or nil
	if actionIds == nil then return "" end

	local actionTags = ""
	for _,id in ipairs( actionIds ) do
		local def = actions[id]
		actionTags = actionTags .. p.createActionTag( page, def, fieldDefinitions )
	end
	
	return actionTags

	--[[ older approach
	if mode == "new" then
		local actionDefs = schemaTbl.new or {}
		for _,def in ipairs(actionDefs) do
			actionTags = actionTags .. p.createActionTag( page, def, fieldDefinitions )
		end
	end
	]]--
end

--[[
@actionDefs table
@fieldDefinitions table
@todo table
]]--
p.maybeSubtractFromFieldDefinitions = function( actionDefs, fieldDefs )
	-- first get excludable fields from action defs
	local excludableFields = {}
	for _,def in ipairs( actionDefs ) do
		if def["_excludeIds"] ~= nil then
			for i,field in ipairs( def["_excludeIds"] ) do
				table.insert( excludableFields, field )
			end
		end
		--[[
		if def["_excludeFields"] ~= nil then 
			for i,field in ipairs( def["_excludeFields"] ) do
				table.insert( excludableFields, field )
			end
		end
		--]]
	end
	-- no change
	if excludableFields == {} then return fieldDefs end
	
	local newFieldDefs = {}
	for _,def in ipairs( fieldDefs ) do
		local id = def["_id"] or ""
		-- ocal name = def["name"] or ""
		--local fieldName = string.gsub( mw.text.trim( name ), "%[%]", "" )
		if p.isValueInSequentialTable( id, excludableFields ) then
			-- do nothing
		else
			table.insert( newFieldDefs, def )
		end
	end

	return newFieldDefs
end

--[[
Set up a _create or _edit element
@page string
@attrs table (read-only, assoc)
@fieldDefs table (read-only)
]]--
p.createActionTag = function( page, attributes, fieldDefs )
	-- create proxy for read-only attributes
	local attrbs = {}
	for k,v in pairs( attributes ) do attrbs[k] = v end

	if attrbs["mwwrite"] == nil then
		attrbs["mwwrite"] = page
	end

	if attrbs["mwfields"] == nil then
		local fieldsTbl = p.getFieldNames( fieldDefs )
		-- new: _excludeFields => _excludeIds
		if attrbs["_excludeIds"] ~= nil then
			-- when using multiple _creates you may want to exclude specific fields
			-- local invalidFields = mw.text.split( attrbs["_excludeIds"], "," )
			fieldsTbl = p.removeValuesFromSequentialTable( fieldsTbl, attrbs["_excludeIds"] )
		end
		attrbs["mwfields"] = table.concat( fieldsTbl, "," )
	end

	-- default tag is _create
	local tag = "_create"
	if attrbs["_type"] ~= nil then
		tag = attrbs["_type"]
	end
	
	if attrbs["mwformat"] == "json" and attrbs["mwtemplate"] == nil then
		attrbs["mwtemplate"] = "wsnone"
	end

	local attrbs = p.removeNonFFAttributes(attrbs)

	return mw.text.tag( tag, attrbs, false )
end

--[[
Get the field names (as a table) so that they can be added to 'mwfields'
includes field names nested in a fieldset
@fields table|nil
@return table
]]--
p.getFieldNames = function( fields )
	if fields == nil then return {} end
	local fieldNames = {}
	for _,field in ipairs(fields) do
		if field["_type"] == "fieldset" then
			--recursive
			local fieldsetNames = p.getFieldNames( field["_fields"] )
			for _,fieldsetName in ipairs( fieldsetNames ) do
				table.insert(fieldNames, fieldsetName)
			end
		elseif field.name ~= nil then
			-- remove [] if any
			local fieldName = string.gsub( mw.text.trim(field.name), "%[%]", "" )
			table.insert(fieldNames, fieldName)
		end
	end
	return fieldNames
end

--[[
Remove parameters that are not FlexForm attributes
i.e. removes each parameter whose key begins with an underscore
]]--
p.removeNonFFAttributes = function( attributes )
	local newAttributes = {}
	for k,v in pairs(attributes) do
		if ( string.sub( k, 1, 1 ) ~= "_" ) then
			newAttributes[k] = v
		end
	end
	return newAttributes
end

--[[
revise!
]]
p.removeValuesFromSequentialTable = function( tbl, invalidFields )
	--[[ DO NOT DO THIS:
	for _,nonValid in ipairs(invalidFields) do
		for i,v in ipairs(tbl) do
			if v == nonValid then
				table.remove( tbl, i ) 
			end
		end
	end
	return tbl
	]]--
	
	local newTable = {}
	for _,field in ipairs( tbl ) do
		if p.isValueInSequentialTable( field, invalidFields ) then
			--do nothing
		else
			table.insert( newTable, field )
		end
	end
	
	return newTable
	--[[
	-- compare: remove select fieldDefinitions in reverse order
	for i = #fieldDefs, 1, -1 do
		local name = fieldDefs[i]["name"] or ""
		local fieldName = string.gsub( mw.text.trim( name ), "%[%]", "" )
		mw.log( "fieldName: " .. fieldName )
		for _,field in ipairs( excludableFields ) do
			if fieldName == field then
				mw.log( "equal to excludable field: " .. field )
				fieldDefs[i] = nil
			end
		end
	end
]]--
end

p.isValueInSequentialTable = function( val, tbl )
	for _,v in ipairs( tbl ) do
		if v == val then return true end
	end
	return false
end

p.printAssocTable = function( tbl )
	local str = ""
	for k,v in pairs(tbl) do
		str = str .. k .. " = " .. v .. ". "
	end
	return str
end

--[[
Create standardised prefill table as k/v pairs from 
either pre-existing content (JSON, wikitext) when editing
or defaults when creating a page
For structural differences between JSON/wiki template, see getSlotContent()

@sourceFormat
@slotContent table|nil
@defaultStr
@defaultSep
@return table
]]--
p.createPrefillTable = function( sourceFormat, slotContent, defaultStr, defaultsSep )
	--mw.log( "createPrefillTable. Type of slotContent = " .. type(slotContent) )
	local slotContent = slotContent or nil
	local defaults = defaults or nil

	local prefillTbl = {}
	if slotContent ~= nil and sourceFormat == "json" then
		prefillTbl = slotContent
	elseif slotContent ~= nil and slotContent[1] ~= nil and sourceFormat == "wiki" then
		-- note ["_text"]
		for k,v in pairs( slotContent[1] ) do
			-- slotData format
			prefillTbl[k] = v["_text"] or nil
		end
	elseif defaultStr ~= nil and defaultStr ~= "" then
		-- new page
		mw.log( "defaults: " .. defaultStr )
		local defaults = mw.text.split( defaultStr, defaultsSep )
		for _,v in ipairs( defaults ) do
			local pair = mw.text.split( v, "=" )
			if pair[2] ~= nil then
				prefillTbl[ mw.text.trim( pair[1] ) ] = mw.text.trim( pair[2] )
			end
		end
	end
	return prefillTbl
end

--[[
Transform user-provided string into a table (k/v)
@varStr string
@sep string
@return table
]]--
p.createVarsTable = function( varStr, sep )
	if varStr == "" then return {} end
	local sep = sep or "###"
	local vars = mw.text.split( varStr, sep )
	local varTbl = {}
	for _,v in ipairs( vars ) do
		local pair = mw.text.split( v, "=" )
		if pair[2] ~= nil then
			varTbl[ mw.text.trim( pair[1] ) ] = mw.text.trim( pair[2] )
		end
	end
	return varTbl
end

p.replaceVariables = function( str, varsTbl )
	if str == "" or varsTbl == {} then return str end
	--def["query"]
	-- vars def["_vars"]
	-- string.gsub( def.name, "%[%]", "" )
	local newStr = str
	for k,v in pairs(varsTbl) do
		newStr = string.gsub( newStr, k, v )
	end
	mw.log( "variables replaced: " .. newStr )
	return newStr
end

--[[ Create proxy for read-only indexed table ]]--
p.createProxyIpairs = function( tbl ) 
	local newTbl = {}
	for _,v in ipairs( tbl ) do table.insert( newTbl, v ) end
	return newTbl
end

--[[ Create proxy for read-only associative table ]]--
p.createProxyPairs = function( tbl ) 
	local newTbl = {}
	for k,v in pairs( tbl ) do newTbl[k] = v end
	return newTbl
end

return p