Module:Build bracket/Render
Appearance
From Wikipedia, the free encyclopedia
You might want to create a documentation page for this Scribunto module.
Editors can experiment in this module's sandbox (edit | diff) and testcases (create) pages.
Add categories to the /doc subpage. Subpages of this module.
Editors can experiment in this module's sandbox (edit | diff) and testcases (create) pages.
Add categories to the /doc subpage. Subpages of this module.
localRender={} -- ================================ -- 1) MODULE STATE & BIND INJECTS -- ================================ -- Upvalues bound in buildTable(...) localstate,config,Helpers,StateChecks -- Local stdlib aliases localt_insert=table.insert -- Helper/function locals (set during bind) localisempty,notempty,cellBorder,unboldParenthetical localshowSeeds,teamLegs,roundIsEmpty,defaultHeaderText,isBlankEntry -- ========================= -- 2) CORE <td> CONSTRUCTOR -- ========================= localfunctionCell(tbl,j,i,opts) opts=optsor{} localcell=tbl:tag("td") -- classes/attributes ifopts.classesthen for_,cinipairs(opts.classes)do cell:addClass(c) end end ifopts.colspanandopts.colspan~=1then cell:attr("colspan",opts.colspan) end ifopts.rowspanandopts.rowspan~=1then cell:attr("rowspan",opts.rowspan) end -- styling ifopts.borderWidththen cell:css("border-width",cellBorder(opts.borderWidth)) end ifopts.weight=="bold"then cell:css("font-weight","bold") end ifopts.bgthen cell:css("background",opts.bg) end ifopts.colorthen cell:css("color",opts.color) end -- alignment helpers ifopts.align=="center"then cell:addClass("brk-center") elseifopts.align=="right"then cell:css("text-align","right") end ifopts.textthen cell:wikitext(opts.text) end returncell end -- ===================================== -- 3) ENTRY SIZING (computed per-round) -- ===================================== localentryColspan=nil localfunctiongetEntryColspan(j) returnentryColspanandentryColspan[j]or1 end -- ========================== -- 4) TEAM / SCORE CELLS -- ========================== localfunctionteamCell(tbl,k,j,i,l,colspan) localclasses={"brk-td","brk-b",(k=="seed")and"brk-bgD"or"brk-bgL"} ifk=="seed"ork=="score"then classes[#classes+1]="brk-center" end -- strict bolding localweightFlag ifk=="team"then ifstate.entries[j][i].weight=="bold"then weightFlag="bold" end elseifk=="score"andl~=nilthen localsc=state.entries[j][i].score ifscandsc.weightandsc.weight[l]=="bold"then weightFlag="bold" end end locallegs=teamLegs(j,i) localopts={ classes=classes, colspan=colspan, rowspan=2, borderWidth={0,0,1,1}, weight=weightFlag-- 'bold' or nil } -- borders ifk=="team"andlegs==0then opts.borderWidth[2]=1 end ifstate.entries[j][i].position=="top"then opts.borderWidth[1]=1 end ifl==legsorl=="agg"ork=="seed"then opts.borderWidth[2]=1 end -- text localfunctiontostr(x) return(x==nil)and""ortostring(x) end ifl==nilthen opts.text=unboldParenthetical(tostr(state.entries[j][i][k])) else localv=state.entries[j][i][k]andstate.entries[j][i][k][l] opts.text=tostr(v) end -- ensure seeds inherit team bold without affecting score logic ifk=="seed"andstate.entries[j][i]andstate.entries[j][i].weight=="bold"then opts.weight=opts.weightor"bold" end returnCell(tbl,j,i,opts) end -- ====================================== -- 5) NIL/BLANK ENTRY HANDLING PER CELL -- ====================================== localfunctionhandleEmptyOrNilEntry(tbl,j,i,R) localentry_colspan=getEntryColspan(j) localcol=state.entries[j]or{} -- nil entry: optionally emit spanning blank to keep grid intact ifcol[i]==nilthen ifcol[i-1]~=nilori==1then localrowspan,row=0,i repeat rowspan=rowspan+1 row=row+1 untilcol[row]~=nilorrow>R Cell(tbl,j,i,{rowspan=rowspan,colspan=entry_colspan}) returntrue else returntrue-- intentionally omitted cell end end ifcol[i]["ctype"]=="blank"then returntrue end returnfalse end -- ============================ -- 6) ENTRY INSERTORS (ctype) -- ============================ -- 6.1 Header localfunctioninsertHeader(tbl,j,i,entry) localbyesJ=state.byes[j] localhideJ=state.hide[j] localentry_colspan=getEntryColspan(j) if(byesJandbyesJ[entry.headerindex]androundIsEmpty(j,i))or(hideJandhideJ[entry.headerindex])then returnCell(tbl,j,i,{rowspan=2,colspan=entry_colspan}) end ifisempty(entry.header)then entry.header=defaultHeaderText(j,entry.headerindex) end localclasses={"brk-td","brk-b","brk-center"} localuseCustomShade=entry.shade_is_rdandnotisempty(entry.shade) ifnotuseCustomShadethen t_insert(classes,"brk-bgD") end localcellOpts={ rowspan=2, colspan=entry_colspan, text=entry.header, classes=classes, borderWidth={1,1,1,1} } ifuseCustomShadethen cellOpts.bg=entry.shade end returnCell(tbl,j,i,cellOpts) end -- 6.2 Team (+seed/+scores/+agg) localfunctioninsertTeam(tbl,j,i,entry) localbyesJ=state.byes[j] localhideJ=state.hide[j] localentry_colspan=getEntryColspan(j) localmaxlegs=state.maxlegs[j]or1 locallegs=teamLegs(j,i) localteam_colspan=maxlegs-legs+1 -- bye/hidden → reserve footprint if((byesJandbyesJ[entry.headerindex])andisBlankEntry(j,i))or(hideJandhideJ[entry.headerindex])then returnCell(tbl,j,i,{rowspan=2,colspan=entry_colspan}) end ifconfig.aggregateandlegs==1andmaxlegs>1then team_colspan=team_colspan+1 end ifmaxlegs==0then team_colspan=team_colspan+1 end -- seed ifconfig.seedsthen ifshowSeeds(j,i)==truethen teamCell(tbl,"seed",j,i) else team_colspan=team_colspan+1 end end -- team name teamCell(tbl,"team",j,i,nil,team_colspan) -- scores forl=1,legsdo teamCell(tbl,"score",j,i,l) end -- aggregate ifconfig.aggregateandlegs>1then teamCell(tbl,"score",j,i,"agg") end end -- 6.3 Text localfunctioninsertText(tbl,j,i,entry) Cell(tbl,j,i,{rowspan=2,colspan=getEntryColspan(j),text=entry.text}) end -- 6.4 Group (spans columns) localfunctioninsertGroup(tbl,j,i,entry) localspan=state.entries[j][i].colspanor1 localcolspan=0 -- sum entry widths per column form=j,j+span-1do colspan=colspan+state.maxlegs[m]+2 ifnotconfig.seedsthen colspan=colspan-1 end if(config.aggregateandstate.maxlegs[m]>1)orstate.maxlegs[m]==0then colspan=colspan+1 end end -- add path columns between rounds form=j,j+span-2do colspan=colspan+(state.hascross[m]and3or2) end returnCell(tbl,j,i,{rowspan=2,colspan=colspan,classes={"brk-center"},text=entry.groupor""}) end -- 6.5 Line localfunctioninsertLine(tbl,j,i,entry) localentry_colspan=getEntryColspan(j) localborderWidth={0,0,0,0} ifentry.borderWidththen borderWidth=entry.borderWidth else -- derive from left path column localwantTop=entry.border=="top"orentry.border=="both" localwantBottom=(entry.border==nil)orentry.border=="bottom"orentry.border=="both" ifwantBottomandstate.pathCell[j-1]andstate.pathCell[j-1][i+1]then borderWidth[3]=2*(state.pathCell[j-1][i+1][3][1][3]or0) end ifwantTopandstate.pathCell[j-1]andstate.pathCell[j-1][i]then borderWidth[1]=2*(state.pathCell[j-1][i][3][1][1]or0) end end localcell=Cell(tbl,j,i,{rowspan=2,colspan=entry_colspan,text=entry.text,borderWidth=borderWidth}) cell:addClass("brk-line") ifentry.colorthen cell:css("border-color",entry.color) end returncell end localINSERTORS={ header=insertHeader, team=insertTeam, text=insertText, group=insertGroup, line=insertLine } localfunctioninsertEntry(tbl,j,i,R) ifhandleEmptyOrNilEntry(tbl,j,i,R)then return end localentry=state.entries[j][i] ifnotentrythen return end localfn=INSERTORS[entry.ctype] iffnthen returnfn(tbl,j,i,entry) end returnCell(tbl,j,i,{rowspan=2,colspan=getEntryColspan(j)}) end -- =================================== -- 7) PATH CELL EMITTERS (between RDs) -- =================================== -- Always emit the correct number of <td>s: -- - 2 lanes when no cross (k=1,3) -- - 3 lanes when cross (k=1,2,3). The center lane (k=2) is skipped only if no cross. localfunctiongeneratePathCell(tbl,j,i,k,bg,rowspan) ifnotstate.hascross[j]andk==2then return end-- keep table aligned localcolData=state.pathCell[j][i][k] localborders=(colDataandcolData[1])or{0,0,0,0} localcolor=(colDataandcolData.color)or"transparent" localcell=tbl:tag("td") ifrowspanandrowspan~=1then cell:attr("rowspan",rowspan) end ifk==2andstate.hascross[j]andnotempty(bg)then cell:css("background",bg):css("transform","translate(-1px)") end ifborders[1]~=0orborders[2]~=0orborders[3]~=0orborders[4]~=0then cell:css("border","solid "..color):css( "border-width", (2*borders[1]).. "px "..(2*borders[2]).."px "..(2*borders[3]).."px "..(2*borders[4]).."px" ) end returncell end localfunctioninsertPath(tbl,j,i,R) ifstate.skipPath[j][i]then return end localcolspan,rowspan=2,1 localbg="" localcross={"",""} localPj=state.pathCell[j] localXj=state.crossCell[j] localSPj=state.skipPath[j] -- vertical merge: extend rowspan down while borders repeat identically ifi<Rthen localfunctionsameBorders(a) ifa>R-1orSPj[a]then returnfalse end localpi,pa=Pj[i],Pj[a] fork=1,3do localbi,ba=pi[k][1],pa[k][1] ifbi[1]~=ba[1]orbi[2]~=ba[2]orbi[3]~=ba[3]orbi[4]~=ba[4]then returnfalse end end returntrue end ifsameBorders(i)then localrow=i repeat ifrow~=iandsameBorders(row)then SPj[row]=true end rowspan=rowspan+1 row=row+1 untilrow>RornotsameBorders(row) rowspan=rowspan-1 end end -- avoid double-emitting cross rows (previous row already spans) if i>1andXj[i-1]and ((Xj[i-1].leftandXj[i-1].left[1]==1)or(Xj[i-1].rightandXj[i-1].right[1]==1)) then return end -- cross visuals ifstate.hascross[j]then colspan=3 ifXj[i].left[1]==1orXj[i].right[1]==1then rowspan=2 ifXj[i].left[1]==1then cross[1]= "linear-gradient(to top right, transparent calc(50% - 1px),".. Xj[i].left[2].. " calc(50% - 1px),"..Xj[i].left[2].." calc(50% + 1px), transparent calc(50% + 1px))" end ifXj[i].right[1]==1then cross[2]= "linear-gradient(to bottom right, transparent calc(50% - 1px),".. Xj[i].right[2].. " calc(50% - 1px),"..Xj[i].right[2].." calc(50% + 1px), transparent calc(50% + 1px))" end end ifnotempty(cross[1])andnotempty(cross[2])then cross[1]=cross[1].."," end bg=cross[1]..cross[2] end -- emit L | (CENTER) | R cells fork=1,3do generatePathCell(tbl,j,i,k,bg,rowspan) end end -- ========================================= -- 8) INVISIBLE SCAFFOLDING (LEGACY COMPAT) -- ========================================= localfunctionemitRowHeight(tr,rowHeightPx)-- left height cell per data row tr:tag("td"):css("height",rowHeightPx) end -- widths row: fixes widths for seed/team/score(+agg) and path columns localfunctionemitWidthsRow(tbl,MINC,C,seedW,teamW,scoreW,aggW,pathW,leftPad,rightPad,crossW,crossPad) tbl:tag("tr"):css("visibility","collapse")-- spacer localtr=tbl:tag("tr") tr:tag("td"):css("width","1px")-- tiny leading gutter forj=MINC,Cdo ifconfig.seedsthen tr:tag("td"):css("width",seedW) end tr:tag("td"):css("width",teamW) localmaxlegs=(state.maxlegsandstate.maxlegs[j])or1 ifmaxlegs<=0then tr:tag("td"):css("width",scoreW)-- legacy extra column when maxlegs == 0 else for_=1,maxlegsdo tr:tag("td"):css("width",scoreW) end end ifconfig.aggregateandmaxlegs>1then tr:tag("td"):css("width",aggW) end ifj<Cthen ifstate.hascrossandstate.hascross[j]then tr:tag("td"):css("width",pathW):css("padding-left",leftPad)-- L tr:tag("td"):css("width",crossW):css("padding-left",crossPad)-- CENTER tr:tag("td"):css("width",pathW):css("padding-right",rightPad)-- R else tr:tag("td"):css("width",pathW):css("padding-left",leftPad)-- L tr:tag("td"):css("width",pathW):css("padding-right",rightPad)-- R end end end end -- ============================== -- 9) PUBLIC: BUILD THE TABLE -- ============================== functionRender.buildTable(frame,_state,_config,_Helpers,_StateChecks) -- Bind upvalues state,config,Helpers,StateChecks=_state,_config,_Helpers,_StateChecks -- Helpers isempty,notempty=Helpers.isempty,Helpers.notempty cellBorder=Helpers.cellBorder unboldParenthetical=Helpers.unboldParenthetical -- State checks showSeeds=StateChecks.showSeeds teamLegs=StateChecks.teamLegs roundIsEmpty=StateChecks.roundIsEmpty defaultHeaderText=StateChecks.defaultHeaderText isBlankEntry=StateChecks.isBlankEntry -- Hot locals localMINC,C=config.minc,config.c localR0=tonumber(config.r)or0 localentries=state.entries localpathCell=state.pathCell localcrossCell=state.crossCell localhide=state.hideor{} localbyes=state.byesor{} localmaxlegsArr=state.maxlegsor{} localhascross=state.hascrossor{} -- Respect explicit |rows=| localuserRowsArg=config._fargsandconfig._fargs.rows -- Is this entry visibly rendered localfunctionentryIsVisible(j,i) localcol=entries[j] locale=colandcol[i] ifnotethen returnfalse end localct=e.ctype localhidx=e.headerindex localhid=(hide[j]andhide[j][hidx])orfalse ifhidthen returnfalse end ifct=="team"then localbye=(byes[j]andbyes[j][hidx])orfalse ifbyeandisBlankEntry(j,i)then returnfalse end returntrue elseifct=="header"then localbye=(byes[j]andbyes[j][hidx])orfalse ifbyeandroundIsEmpty(j,i)then returnfalse end returntrue-- header shows (defaults applied) unless hidden or bye+empty elseifct=="text"then returnnotempty(e.text) elseifct=="group"then returnnotempty(e.group) elseifct=="line"then -- Count visible borders via path scan, not the placeholder cell itself returnnotempty(e.text)-- only count if it actually prints text end returnfalse end -- Backward scan for last visually-used row (entries or painted paths) localfunctioncomputeBottomUsedRow(R) fori=R,1,-1do -- Any visible entry on this row? forj=MINC,Cdo ifentryIsVisible(j,i)then locale=entries[j][i] -- teams occupy a row pair → include the following row if within bounds ife.ctype=="team"then return(i+1<=R)and(i+1)ori else returni end end end -- Any path/cross on this row? forj=MINC,C-1do localPj=pathCell[j] localXj=crossCell[j] -- Cross uses a row pair localcc=XjandXj[i] ifccand((cc.leftandcc.left[1]==1)or(cc.rightandcc.right[1]==1))then return(i+1<=R)and(i+1)ori end -- Any nonzero border on any lane? localrow=PjandPj[i] ifrowthen fork=1,3do localcell=row[k] localb=cellandcell[1] ifband((b[1]or0)~=0or(b[2]or0)~=0or(b[3]or0)~=0or(b[4]or0)~=0)then returni end end end end end return1-- nothing visible; keep at least one row end -- Effective number of data rows to emit localR_eff=(userRowsArganduserRowsArg~="")andR0orcomputeBottomUsedRow(R0) -- Precompute entryColspan per round entryColspan={} forj=MINC,Cdo localml=maxlegsArr[j]or1 localcol=ml+2 ifnotconfig.seedsthen col=col-1 end if(config.aggregateandml>1)orml==0then col=col+1 end entryColspan[j]=col end -- Table skeleton localtbl=mw.html.create("table"):addClass("brk") ifconfig.nowrapthen tbl:addClass("brk-nw") end -- Fixed internal row height (do NOT use config.height here) localrowHeightPx="11px" -- Column widths (resolve once) localgetWidth=Helpers.getWidth localseedW=(getWidthandgetWidth("seed","25px"))or"25px" localteamW=(getWidthandgetWidth("team","150px"))or"150px" localscoreW=(getWidthandgetWidth("score","25px"))or"25px" localaggRaw=(getWidthandgetWidth("agg",nil))ornil localaggW=Helpers.isempty(aggRaw)andscoreWoraggRaw localpathW="2px" localcrossW=(getWidthandgetWidth("cross","5px"))or"5px" localcrossPad=(getWidthandgetWidth("crosspad","5px"))or"5px" -- between-round spacing split (e.g., 6 → 4px left, 2px right) localspacing=tonumber(config.colspacing)or6 localleftFrac=math.floor(spacing*2/3) localleftPad,rightPad=(leftFrac.."px"),((spacing-leftFrac).."px") -- widths row emitWidthsRow(tbl,MINC,C,seedW,teamW,scoreW,aggW,pathW,leftPad,rightPad,crossW,crossPad) -- Data rows fori=1,R_effdo localtr=tbl:tag("tr") emitRowHeight(tr,rowHeightPx)-- left height cell forj=MINC,Cdo insertEntry(tr,j,i,R_eff) ifj<Cthen insertPath(tr,j,i,R_eff) end end end -- Wrap with a div that loads TemplateStyles and enables scroll overflow localfr=frameormw.getCurrentFrame() localcontainer=mw.html.create("div") -- Height now applies to container (numbers → px; units respected) localcontainerHeight=Helpers.toCssLength(config.height,nil) ifcontainerHeightthen container:css("max-height",containerHeight) end container:css("overflow-x","auto"):css("overflow-y","auto") container:wikitext(fr:extensionTag("templatestyles","",{src="Module:Build bracket/styles.css"})) container:node(tbl) returntostring(container) end -- ============ -- 10) EXPORTS -- ============ returnRender