Module:Message box
Changes to it can cause immediate changes to the MediaWiki user interface. To avoid large-scale disruption, any changes should first be tested in this module's /sandbox or /testcases subpage, or in your own user space.The tested changes can then be added in one single edit to this module. Please discuss any changes on the talk page before implementing them.
This is a meta-module that implements the message box templates {{mbox}}, {{ambox}}, {{cmbox}}, {{fmbox}}, {{imbox}}, {{ombox}} and {{tmbox}}. It is intended to be used from Lua modules, and should not be used directly from wiki pages. If you want to use this module's functionality from a wiki page, please use the individual message box templates instead.
Usage
To use this module from another Lua module, first you need to load it.
localmessageBox=require('Module:Message box')
To create a message box, use the main
function.
It takes two parameters:
- the first is the box type (as a string).
- the second is a table containing the message box parameters.
localbox=messageBox.main(boxType,{ param1=param1, param2=param2, -- More parameters... })
There are seven available box types:
Box type | Template | Purpose |
---|---|---|
mbox
|
{{mbox}} | For message boxes to be used in multiple namespaces |
ambox
|
{{ambox}} | For article message boxes |
cmbox
|
{{cmbox}} | For category message boxes |
fmbox
|
{{fmbox}} | For interface message boxes |
imbox
|
{{imbox}} | For file namespace message boxes |
tmbox
|
{{tmbox}} | For talk page message boxes |
ombox
|
{{ombox}} | For message boxes in other namespaces |
See the template page of each box type for the available parameters.
Usage from #invoke
As well as the main
function, this module has separate functions for each box type.
They are accessed using the code {{#invoke:Message box|mbox|...}}
, {{#invoke:Message box|ambox|...}}
, etc.
These will work when called from other modules, but they access code used to process arguments passed from {{#invoke:...}}
, and so calling them will be less efficient than calling main
.
Technical details
The module uses the same basic code for each of the templates listed above; the differences between each of them are configured using the data at Module:Message box/configuration.
Editors can experiment in this module’s sandbox (edit | diff) and testcases (create) pages.
Subpages of this module.
-- This is a meta-module for producing message box templates, including -- {{mbox}}, {{ambox}}, {{imbox}}, {{tmbox}}, {{ombox}}, {{cmbox}} and {{fmbox}}. -- Load necessary modules. require('strict') localgetArgs localyesno=require('Module:Yesno') -- Get a language object for formatDate and ucfirst. locallang=mw.language.getContentLanguage() -- Define constants localCONFIG_MODULE='Module:Message box/configuration' localDEMOSPACES={talk='tmbox',image='imbox',file='imbox',category='cmbox',article='ambox',main='ambox'} localTEMPLATE_STYLES='Module:Message box/%s.css' -------------------------------------------------------------------------------- -- Helper functions -------------------------------------------------------------------------------- localfunctiongetTitleObject(...) -- Get the title object, passing the function through pcall -- in case we are over the expensive function count limit. localsuccess,title=pcall(mw.title.new,...) ifsuccessthen returntitle end end localfunctionunion(t1,t2) -- Returns the union of two arrays. localvals={} fori,vinipairs(t1)do vals[v]=true end fori,vinipairs(t2)do vals[v]=true end localret={} forkinpairs(vals)do table.insert(ret,k) end table.sort(ret) returnret end localfunctiongetArgNums(args,prefix) localnums={} fork,vinpairs(args)do localnum=mw.ustring.match(tostring(k),'^'..prefix..'([1-9]%d*)$') ifnumthen table.insert(nums,tonumber(num)) end end table.sort(nums) returnnums end -------------------------------------------------------------------------------- -- Box class definition -------------------------------------------------------------------------------- localMessageBox={} MessageBox.__index=MessageBox functionMessageBox.new(boxType,args,cfg) args=argsor{} localobj={} obj.boxType=boxType -- Set the title object and the namespace. obj.title=getTitleObject(args.page)ormw.title.getCurrentTitle() -- Set the config for our box type. obj.cfg=cfg[boxType] ifnotobj.cfgthen localns=obj.title.namespace -- boxType is "mbox" or invalid input ifargs.demospaceandargs.demospace~=''then -- implement demospace parameter of mbox localdemospace=string.lower(args.demospace) ifDEMOSPACES[demospace]then -- use template from DEMOSPACES obj.cfg=cfg[DEMOSPACES[demospace]] obj.boxType=DEMOSPACES[demospace] elseifstring.find(demospace,'talk')then -- demo as a talk page obj.cfg=cfg.tmbox obj.boxType='tmbox' else -- default to ombox obj.cfg=cfg.ombox obj.boxType='ombox' end elseifns==0then obj.cfg=cfg.ambox-- main namespace obj.boxType='ambox' elseifns==6then obj.cfg=cfg.imbox-- file namespace obj.boxType='imbox' elseifns==14then obj.cfg=cfg.cmbox-- category namespace obj.boxType='cmbox' else localnsTable=mw.site.namespaces[ns] ifnsTableandnsTable.isTalkthen obj.cfg=cfg.tmbox-- any talk namespace obj.boxType='tmbox' else obj.cfg=cfg.ombox-- other namespaces or invalid input obj.boxType='ombox' end end end -- Set the arguments, and remove all blank arguments except for the ones -- listed in cfg.allowBlankParams. do localnewArgs={} fork,vinpairs(args)do ifv~=''then newArgs[k]=v end end fori,paraminipairs(obj.cfg.allowBlankParamsor{})do newArgs[param]=args[param] end obj.args=newArgs end -- Define internal data structure. obj.categories={} obj.classes={} -- For lazy loading of [[Module:Category handler]]. obj.hasCategories=false returnsetmetatable(obj,MessageBox) end functionMessageBox:addCat(ns,cat,sort) ifnotcatthen returnnil end ifsortthen cat=string.format('[[Category:%s|%s]]',cat,sort) else cat=string.format('[[Category:%s]]',cat) end self.hasCategories=true self.categories[ns]=self.categories[ns]or{} table.insert(self.categories[ns],cat) end functionMessageBox:addClass(class) ifnotclassthen returnnil end self.classes[class]=1 end functionMessageBox:removeClass(class) ifnotclassthen returnnil end self.classes[class]=nil end functionMessageBox:setParameters() localargs=self.args localcfg=self.cfg -- Get type data. self.type=args.type localtypeData=cfg.types[self.type] self.invalidTypeError=cfg.showInvalidTypeError andself.type andnottypeData typeData=typeDataorcfg.types[cfg.default] self.typeClass=typeData.class self.typeImage=typeData.image -- Find if the box has been wrongly substituted. self.isSubstituted=cfg.substCheckandargs.subst=='SUBST' -- Find whether we are using a small message box. self.isSmall=cfg.allowSmalland( cfg.smallParamandargs.small==cfg.smallParam ornotcfg.smallParamandyesno(args.small) ) -- Add attributes, classes and styles. self.id=args.id self.name=args.name for_,classinipairs(cfg.classesor{})do self:addClass(class) end ifself.namethen self:addClass('box-'..string.gsub(self.name,' ','_')) end localplainlinks=yesno(args.plainlinks) ifplainlinks==truethen self:addClass('plainlinks') elseifplainlinks==falsethen self:removeClass('plainlinks') end ifself.isSmallthen self:addClass(cfg.smallClassor'mbox-small') end self:addClass(self.typeClass) self:addClass(args.class) self.style=args.style self.attrs=args.attrs -- Set text style. self.textstyle=args.textstyle -- Find if we are on the template page or not. This functionality is only -- used if useCollapsibleTextFields is set, or if both cfg.templateCategory -- and cfg.templateCategoryRequireName are set. self.useCollapsibleTextFields=cfg.useCollapsibleTextFields ifself.useCollapsibleTextFields orcfg.templateCategory andcfg.templateCategoryRequireName then ifself.namethen localtemplateName=mw.ustring.match( self.name, '^[tT][eE][mM][pP][lL][aA][tT][eE][%s_]*:[%s_]*(.*)$' )orself.name templateName='Template:'..templateName self.templateTitle=getTitleObject(templateName) end self.isTemplatePage=self.templateTitle andmw.title.equals(self.title,self.templateTitle) end -- Process data for collapsible text fields. At the moment these are only -- used in {{ambox}}. ifself.useCollapsibleTextFieldsthen -- Get the self.issue value. ifself.isSmallandargs.smalltextthen self.issue=args.smalltext else localsect ifargs.sect==''then sect='This '..(cfg.sectionDefaultor'page') elseiftype(args.sect)=='string'then sect='This '..args.sect end localissue=args.issue issue=type(issue)=='string'andissue~=''andissueornil localtext=args.text text=type(text)=='string'andtextornil localissues={} table.insert(issues,sect) table.insert(issues,issue) table.insert(issues,text) self.issue=table.concat(issues,' ') end -- Get the self.talk value. localtalk=args.talk -- Show talk links on the template page or template subpages if the talk -- parameter is blank. iftalk=='' andself.templateTitle and( mw.title.equals(self.templateTitle,self.title) orself.title:isSubpageOf(self.templateTitle) ) then talk='#' elseiftalk==''then talk=nil end iftalkthen -- If the talk value is a talk page, make a link to that page. Else -- assume that it's a section heading, and make a link to the talk -- page of the current page with that section heading. localtalkTitle=getTitleObject(talk) localtalkArgIsTalkPage=true ifnottalkTitleornottalkTitle.isTalkPagethen talkArgIsTalkPage=false talkTitle=getTitleObject( self.title.text, mw.site.namespaces[self.title.namespace].talk.id ) end iftalkTitleandtalkTitle.existsthen localtalkText='Relevant discussion may be found on' iftalkArgIsTalkPagethen talkText=string.format( '%s [[%s|%s]].', talkText, talk, talkTitle.prefixedText ) else talkText=string.format( '%s the [[%s#%s|talk page]].', talkText, talkTitle.prefixedText, talk ) end self.talk=talkText end end -- Get other values. self.fix=args.fix~=''andargs.fixornil localdate ifargs.dateandargs.date~=''then date=args.date elseifargs.date==''andself.isTemplatePagethen date=lang:formatDate('F Y') end ifdatethen self.date=string.format(" <small class='date-container'>''(<span class='date'>%s</span>)''</small>",date) end self.info=args.info ifyesno(args.removalnotice)then self.removalNotice=cfg.removalNotice end end -- Set the non-collapsible text field. At the moment this is used by all box -- types other than ambox, and also by ambox when small=yes. ifself.isSmallthen self.text=args.smalltextorargs.text else self.text=args.text end -- Set the below row. self.below=cfg.belowandargs.below -- General image settings. self.imageCellDiv=notself.isSmallandcfg.imageCellDiv self.imageEmptyCell=cfg.imageEmptyCell ifcfg.imageEmptyCellStylethen self.imageEmptyCellStyle='border:none;padding:0px;width:1px' end -- Left image settings. localimageLeft=self.isSmallandargs.smallimageorargs.image ifcfg.imageCheckBlankandimageLeft~='blank'andimageLeft~='none' ornotcfg.imageCheckBlankandimageLeft~='none' then self.imageLeft=imageLeft ifnotimageLeftthen localimageSize=self.isSmall and(cfg.imageSmallSizeor'30x30px') or'40x40px' self.imageLeft=string.format('[[File:%s|%s|link=|alt=]]',self.typeImage or'Information icon4.svg',imageSize) end end -- Right image settings. localimageRight=self.isSmallandargs.smallimagerightorargs.imageright ifnot(cfg.imageRightNoneandimageRight=='none')then self.imageRight=imageRight end end functionMessageBox:setMainspaceCategories() localargs=self.args localcfg=self.cfg ifnotcfg.allowMainspaceCategoriesthen returnnil end localnums={} for_,prefixinipairs{'cat','category','all'}do args[prefix..'1']=args[prefix] nums=union(nums,getArgNums(args,prefix)) end -- The following is roughly equivalent to the old {{Ambox/category}}. localdate=args.date date=type(date)=='string'anddate localpreposition='from' for_,numinipairs(nums)do localmainCat=args['cat'..tostring(num)] orargs['category'..tostring(num)] localallCat=args['all'..tostring(num)] mainCat=type(mainCat)=='string'andmainCat allCat=type(allCat)=='string'andallCat ifmainCatanddateanddate~=''then localcatTitle=string.format('%s %s %s',mainCat,preposition,date) self:addCat(0,catTitle) catTitle=getTitleObject('Category:'..catTitle) ifnotcatTitleornotcatTitle.existsthen self:addCat(0,'Articles with invalid date parameter in template') end elseifmainCatand(notdateordate=='')then self:addCat(0,mainCat) end ifallCatthen self:addCat(0,allCat) end end end functionMessageBox:setTemplateCategories() localargs=self.args localcfg=self.cfg -- Add template categories. ifcfg.templateCategorythen ifcfg.templateCategoryRequireNamethen ifself.isTemplatePagethen self:addCat(10,cfg.templateCategory) end elseifnotself.title.isSubpagethen self:addCat(10,cfg.templateCategory) end end -- Add template error categories. ifcfg.templateErrorCategorythen localtemplateErrorCategory=cfg.templateErrorCategory localtemplateCat,templateSort ifnotself.nameandnotself.title.isSubpagethen templateCat=templateErrorCategory elseifself.isTemplatePagethen localparamsToCheck=cfg.templateErrorParamsToCheckor{} localcount=0 fori,paraminipairs(paramsToCheck)do ifnotargs[param]then count=count+1 end end ifcount>0then templateCat=templateErrorCategory templateSort=tostring(count) end ifself.categoryNumsand#self.categoryNums>0then templateCat=templateErrorCategory templateSort='C' end end self:addCat(10,templateCat,templateSort) end end functionMessageBox:setAllNamespaceCategories() -- Set categories for all namespaces. ifself.isSubstitutedthen self:addCat('all','Pages with incorrectly substituted templates') end end functionMessageBox:setCategories() ifself.title.namespace==0then self:setMainspaceCategories() elseifself.title.namespace==10then self:setTemplateCategories() end self:setAllNamespaceCategories() end functionMessageBox:renderCategories() ifnotself.hasCategoriesthen -- No categories added, no need to pass them to Category handler so, -- if it was invoked, it would return the empty string. -- So we shortcut and return the empty string. return"" end -- Convert category tables to strings and pass them through -- [[Module:Category handler]]. returnrequire('Module:Category handler')._main{ main=table.concat(self.categories[0]or{}), template=table.concat(self.categories[10]or{}), all=table.concat(self.categories.allor{}), nocat=self.args.nocat, page=self.args.page } end functionMessageBox:export() localroot=mw.html.create() -- Add the subst check error. ifself.isSubstitutedandself.namethen root:tag('b') :addClass('error') :wikitext(string.format( 'Template <code>%s[[Template:%s|%s]]%s</code> has been incorrectly substituted.', mw.text.nowiki('{{'),self.name,self.name,mw.text.nowiki('}}') )) end -- Add TemplateStyles root:wikitext(mw.getCurrentFrame():extensionTag{ name='templatestyles', args={src=TEMPLATE_STYLES:format(self.boxType)}, }) -- Create the box table. localboxTable -- Check for fmbox because not all interface messages have mw-parser-output -- which is necessary for TemplateStyles. Add the wrapper class if it is and -- then start the actual mbox, else start the mbox. ifself.boxType=='fmbox'then boxTable=root:tag('div') :addClass('mw-parser-output') :tag('table') else boxTable=root:tag('table') end boxTable:attr('id',self.idornil) forclass,_inpairs(self.classesor{})do boxTable:addClass(classornil) end boxTable :cssText(self.styleornil) :attr('role','presentation') ifself.attrsthen boxTable:attr(self.attrs) end -- Add the left-hand image. localrow=boxTable:tag('tr') ifself.imageLeftthen localimageLeftCell=row:tag('td'):addClass('mbox-image') ifself.imageCellDivthen -- If we are using a div, redefine imageLeftCell so that the image -- is inside it. Divs use style="width: 52px;", which limits the -- image width to 52px. If any images in a div are wider than that, -- they may overlap with the text or cause other display problems. imageLeftCell=imageLeftCell:tag('div'):css('width','52px') end imageLeftCell:wikitext(self.imageLeftornil) elseifself.imageEmptyCellthen -- Some message boxes define an empty cell if no image is specified, and -- some don't. The old template code in templates where empty cells are -- specified gives the following hint: "No image. Cell with some width -- or padding necessary for text cell to have 100% width." row:tag('td') :addClass('mbox-empty-cell') :cssText(self.imageEmptyCellStyleornil) end -- Add the text. localtextCell=row:tag('td'):addClass('mbox-text') ifself.useCollapsibleTextFieldsthen -- The message box uses advanced text parameters that allow things to be -- collapsible. At the moment, only ambox uses this. textCell:cssText(self.textstyleornil) localtextCellDiv=textCell:tag('div') textCellDiv :addClass('mbox-text-span') :wikitext(self.issueornil) if(self.talkorself.fix)andnotself.isSmallthen textCellDiv:tag('span') :addClass('hide-when-compact') :wikitext(self.talkand(' '..self.talk)ornil) :wikitext(self.fixand(' '..self.fix)ornil) end textCellDiv:wikitext(self.dateand(' '..self.date)ornil) ifself.infoandnotself.isSmallthen textCellDiv :tag('span') :addClass('hide-when-compact') :wikitext(self.infoand(' '..self.info)ornil) end ifself.removalNoticethen textCellDiv:tag('small') :addClass('hide-when-compact') :tag('i') :wikitext(string.format(" (%s)",self.removalNotice)) end else -- Default text formatting - anything goes. textCell :cssText(self.textstyleornil) :wikitext(self.textornil) end -- Add the right-hand image. ifself.imageRightthen localimageRightCell=row:tag('td'):addClass('mbox-imageright') ifself.imageCellDivthen -- If we are using a div, redefine imageRightCell so that the image -- is inside it. imageRightCell=imageRightCell:tag('div'):css('width','52px') end imageRightCell :wikitext(self.imageRightornil) end -- Add the below row. ifself.belowthen boxTable:tag('tr') :tag('td') :attr('colspan',self.imageRightand'3'or'2') :addClass('mbox-text') :cssText(self.textstyleornil) :wikitext(self.belowornil) end -- Add error message for invalid type parameters. ifself.invalidTypeErrorthen root:tag('div') :css('text-align','center') :wikitext(string.format( 'This message box is using an invalid "type=%s" parameter and needs fixing.', self.typeor'' )) end -- Add categories. root:wikitext(self:renderCategories()ornil) returntostring(root) end -------------------------------------------------------------------------------- -- Exports -------------------------------------------------------------------------------- localp,mt={},{} functionp._exportClasses() -- For testing. return{ MessageBox=MessageBox } end functionp.main(boxType,args,cfgTables) localbox=MessageBox.new(boxType,args,cfgTablesormw.loadData(CONFIG_MODULE)) box:setParameters() box:setCategories() returnbox:export() end functionmt.__index(t,k) returnfunction(frame) ifnotgetArgsthen getArgs=require('Module:Arguments').getArgs end returnt.main(k,getArgs(frame,{trim=false,removeBlanks=false})) end end returnsetmetatable(p,mt)