const idToLink = (id)=>{ let text = id.replace(/\/main(?=\.|::|$)/, '').split(/([\/\.]|::)/); prevId = prevId.replace(/\/main(?=\.|::|$)/, '').split(/([\/\.]|::)/); const len = Math.min(text.length, prevId.length); let i = 0; for(; i < len; ++i) { if (text[i] !== prevId[i]) break; } if (i === 0) { text = text.join(''); } else { switch (text[i]) { case '::': case '.': return {a: text.slice(i).join(''), class: "jsdoc-idLink", $href: '#'+id}; case '/': ++i; default: text = Dom.h({span: [ {class: 'jsdoc-nav-spacer', span: new Array(i).join("\xa0")}, text.slice(i).join('') ]}); } } return Dom.h({a: text, class: "jsdoc-idLink", $href: '#'+id}); };
...calls.map(call =>{ if (Array.isArray(call)) { if (! bodyExample) return Dom.h({div: codeToHtml(newSig(subject.name, call[0]))});; } else { bodyExample = true; return Dom.h({div: codeToHtml(call.body)}); } })
Object.keys(properties).sort().forEach(name => { const property = properties[name]; const value = Dom.h({class: 'jsdoc-value', code: valueToText(property.value)}); const info = property.info ? jsdocToHtml( api, property.info .replace(/\$\{value\}/, '[](#jsdoc-value)'), argMap ) : value; if (property.info) { const vref = findHref(info, '#jsdoc-value', true); if (vref) vref.parentNode.replaceChild(value, vref); } const ap = extractTypes( property.calls ? argProfile(property.calls, call => call[0].length ? call[0][0] : call[1]) : argProfile([[property.value]], value => value[0]) ); rows.push({tr: [ {class: "searchable", td: proto ? '#'+name : name}, {td: ap}, {class: 'jsdoc-info', '$data-env': env(property), td: info } ]}); property.properties && addRows(property.properties); });
beforeEach(()=>{ v.target = Dom.h({ $style: 'position:absolute;top:51px;left:17px;width:400px;height:100px;'}); document.body.appendChild(v.target); stub(v.target, 'setPointerCapture'); stub(v.target, 'releasePointerCapture'); });
const appendNextStage = (data, pn)=>{ if (User.me().isAdmin()) { pn.appendChild(Dom.h( data.isClosed ? {class: 'reopen', div: {button: ['Reopen '+HEAT_NAME[data.heatNumber]], class: 'action'}} : {class: 'nextStage', div: {button: ['Next'], class: 'action'}})); } };
assert.dom('#NotifyBar:not(.on)', elm => { const myElm = Dom.h({id: "myNotification", class: 'show'}); sut.notify(myElm); assert.className(elm, 'on'); Dom.removeClass(myElm, 'show'); sut.change(); refute.className(elm, 'on'); });
list.forEach(([id, linkNav]) => { const link = idToLink(id); (link.nodeType ? parent : nodeModule) .appendChild(nodeModule = Dom.h({class: 'jsdoc-nav-module', div: [ link, linkNav, ]})); prevId = id; });
mdRenderer.link = (href, title, text)=>{ switch (href) { case '#jsdoc-tag': return execInlineTag(api, text).outerHTML; default: const a = {a: text, $href: href}; if (title) a.$title = title; return Dom.h(targetExternal(a)).outerHTML; } };
code: (event)=>{ const range = Dom.getRange(); const editor = Dom.getClosest(range.startContainer, '.input'); if (editor === null) return; const ctx = Dom.myCtx(editor.parentNode); let {startContainer: sc, endContainer: ec, collapsed} = range; if (sc.nodeType === TEXT_NODE && DomNav.rangeIsInline(range)) { if (! collapsed) { if (range.startOffset == sc.nodeValue.length) sc = DomNav.nextNode(sc); if (ec.nodeType === TEXT_NODE && range.endOffset == 0) ec = DomNav.previousNode(ec); } const fn = fontNode(editor, sc), fnFont = (ctx.override && ctx.override.font) || fn.style.getPropertyValue('font-family'); const efnFont = collapsed || ctx.override ? fnFont : fontNode(editor, ec).style.getPropertyValue('font-family'); let font = 'monospace'; if (fnFont === efnFont && fnFont === 'monospace') { let pn = fn; while (pn !== editor) { pn = fontNode(editor, fn.previousSibling || fn.parentNode); font = pn.style.getPropertyValue('font-family'); if (font !== 'monospace') break; } if (font === 'monospace' || font === '') font = RichText.fontIdToFace[0]; } execCommand('fontName', font); notify(ctx, 'force', collapsed && {font}); } else { if (collapsed) { const pre = EMPTY_PRE.cloneNode(true); const cn = DomNav.containingNode(range); if (cn.tagName === 'BR') { range.selectNode(cn); Dom.setRange(range); } insertNode(pre, pre, 0); } else { const pre = RichText.fromToHtml(Dom.h({pre: range.extractContents()})).firstChild; insertNode(pre); range.selectNodeContents(pre); Dom.setRange(range); } ctx.mode = codeMode; ensureLangues(ctx); notify(ctx, 'force'); } },
return ({View}) => { View.$helpers({ clientSuffix() { const {clientScript, loadScript} = this.controller.layoutData; if (clientScript || loadScript) { return Dom.h([ loadScript === undefined ? '' : loadScript, clientScript === undefined ? '' : {script: `define('clientScript', ['client', '${clientScript}'], (_, o)=>{o.start()});`}, ]); } }, }); };
function renderCategories(ctx) { const {results} = ctx; const compareCats = util.compareByFields('type', 'name'); const table = Dom.h({table: '', class: 'categories'}); if (results) { const catMap = {}; for (let {event_id, cats} of results) { for (let {category_id} of cats) { catMap[category_id] || (catMap[category_id] = Category.findById(category_id)); } } let type; for (let cat of util.values(catMap).sort(compareCats)) { if (type !== cat.type) { type = cat.type; table.appendChild(Dom.h({tr: {td: '', $colspan: '2'}, class: 'heading fmt '+type})); } table.appendChild(Tpl.CatList.$autoRender(cat, ctx)); } } return table; }
isClient && define(function (require, exports, module) { const Dom = require('koru/dom'); const TH = require('ui/test-helper'); const sut = require('./notify-bar'); var v; TH.testCase(module, { setUp() { v = {}; }, tearDown() { Dom.removeId('myNotification'); TH.tearDown(); v = null; }, "test notify survives destory"() { const myNotification = Dom.h({id: 'myNotification'}); const ctx = Dom.setCtx(myNotification); document.body.appendChild(sut.$autoRender()); sut.notify(myNotification); assert.dom('#NotifyBar:not(.on) #myNotification'); Dom.removeId('NotifyBar'); Dom.addClass(myNotification, 'show'); document.body.appendChild(sut.$autoRender()); assert.dom('#NotifyBar.on #myNotification'); assert.same(Dom.myCtx(myNotification), ctx); }, "test change"() { document.body.appendChild(sut.$autoRender()); assert.dom('#NotifyBar:not(.on)', elm => { const myElm = Dom.h({id: "myNotification", class: 'show'}); sut.notify(myElm); assert.className(elm, 'on'); Dom.removeClass(myElm, 'show'); sut.change(); refute.className(elm, 'on'); }); }, }); });
const execInlineTag = (api, text)=>{ let [mod, type, method] = text.split(/([.#:]+)/); let href = text; if (mod) { const destMod = mod && api.parent && api.parent[mod]; if (destMod) mod = mod.replace(/\/[^/]*$/, '/'+destMod.subject.name); text = `${mod}${type||''}${method||''}`; } else { href = api.id+href; text = method; } return Dom.h({class: 'jsdoc-link', a: idToText(text), $href: '#'+href}); };
const eachParam = arg => { const am = argMap[arg]; if (am.optNames === undefined) { const types = extractTypes(am); return { class: "jsdoc-arg", tr: [ {td: am.optional ? `[${arg}]` : arg}, {td: types}, {class: 'jsdoc-info', td: jsdocToHtml(api, am.info)} ] }; } else { return Dom.h(Object.keys(am.optNames).map(eachParam)); } };
const timeToElm = (isInput, elm, disp)=>{ if (isInput) { if (elm !== null && elm.nodeType === 1 && elm.tagName === 'INPUT') { elm[orig$] = elm.value = disp; } else { elm = Dom.h({required: 'required', input: [], value: disp}); elm[orig$] = disp; } } else { if (elm !== null && elm.nodeType === document.TEXT_NODE) elm.textContent = disp; else elm = document.createTextNode(disp); } return elm; };
const displayTime = (res, index, isStartlist=! $.ctx.parentCtx.data.showingResults)=>{ const pn = $.element; if (res == null) { Dom.removeChildren(pn); return; } const {round, isClosed} = $.ctx.parentCtx.data; const time = round.getTime(res, index); const isInput = isStartlist && ! isClosed && res.event.canJudge(); let elm = timeToElm(isInput, pn.firstChild, formatTime(time)); if (elm !== pn.firstChild) { pn.firstChild !== null && pn.firstChild.remove(); pn.insertBefore(elm, pn.firstChild); } elm = elm.nextSibling; if (index != 1 || round.stage > 0) { let attempt = 0; for (const score of round.attempts(res)) { let ae = timeToElm(isInput, elm, formatTime(score)); if (ae.nodeType !== 1) { ae = Dom.h({span: ae}); } ae.setAttribute('data-attempt', ++attempt); if (elm !== ae) { pn.insertBefore(ae, elm); elm !== null && elm.remove(); } elm = ae.nextSibling; } while (elm !== null) { const ne = elm.nextSibling; elm.remove(); elm = ne; } } };
].forEach(row => { frag.appendChild(Dom.h({ class: row[0], href: '/'+row[0], tabindex: "0", a: {span: row[1]}, })); });
define(function(require, exports, module) { const koru = require('koru'); const Dom = require('koru/dom'); const session = require('koru/session'); const Dialog = require('koru/ui/dialog'); const Form = require('koru/ui/form'); const Route = require('koru/ui/route'); const util = require('koru/util'); const Category = require('models/category'); const Climber = require('models/climber'); const Event = require('models/event'); const Series = require('models/series'); const Team = require('models/team'); const TeamType = require('models/team-type'); const PrintHelper = require('ui/print-helper'); const TeamHelper = require('ui/team-helper'); const Tpl = Dom.newTemplate(module, require('koru/html!./series')); const $ = Dom.current; const base = Route.root.addBase(module, Tpl, { focus: true, routeVar: 'seriesId', }); const commonPageOptions = { data: seriesFromRouteVar, afterRendered: tabOpened, }; function seriesFromRouteVar(page, pageRoute) { return Series.findById(pageRoute.seriesId); } base.addTemplate(module, Tpl.Events, Object.create(commonPageOptions)); base.addTemplate(module, Tpl.Edit, Object.create(commonPageOptions)); function tabOpened(elm, pageRoute) { const button = document.querySelector(`#Series .tab[name="${this.name}"]`); const ctx = Dom.ctx(elm); ctx.tabButton = button; Dom.addClass(button, 'selected'); } function tabClosed(ctx) { Dom.removeClass(ctx.tabButton, 'selected'); } const ResultsBodyBase = base.addBase(module, Tpl.Results); ResultsBodyBase.async = true; ResultsBodyBase.addTemplate(module, Tpl.CatResult, { data(page, pageRoute) { return pageRoute; }, insertPage(elm) { const parent = Tpl.Results.$ctx(elm).element(); parent.insertBefore(elm, parent.firstChild); }, }); base.addTemplate(module, Tpl.TeamResults, Object.create(commonPageOptions, { defaultPage: {value: true} })); const sortByDate = util.compareByField('date', -1); Tpl.$events({ 'click button.tab'(event) { Dom.stopEvent(); Route.replacePage(Tpl[this.getAttribute('name')]); }, }); Tpl.$extend({ onBaseEntry(page, pageRoute) { const series = Series.findById(pageRoute.seriesId); if (! series) Route.abortPage('/'+pageRoute.orgSN+'/events/#series'); Route.title = series.name; const elm = Tpl.$autoRender(); const ctx = Dom.myCtx(elm); document.body.appendChild(elm); ctx.onDestroy(Series.observeId(series._id, dc => Dom.setTitle(dc.doc.name))); }, onBaseExit() { Dom.removeId('Series'); Route.title = null; }, }); Tpl.Events.$helpers({ list(each) { return { query: Event.where({series_id: this._id}), compare: sortByDate, }; }, }); Tpl.Events.$events({ 'click [name=addEvent]'() { Dom.stopEvent(); const series = $.ctx.data; let event = Event.build({org_id: series.org_id, series_id: series._id, teamType_ids: series.teamType_ids}); Dialog.open(Tpl.AddEvent.$autoRender(event)); }, }); Tpl.AddEvent.$events({ 'click [name=cancel]'() { Dom.stopEvent(); Dialog.close(); }, 'click [type=submit]': Form.submitFunc('AddEvent', () => Dialog.close()), }); Tpl.Events.$extend({ $destroyed: tabClosed, }); Tpl.Events.Row.$events({ 'click'(event) { Dom.stopEvent(); Route.gotoPage(Dom.Event.Show, {eventId: $.ctx.data._id}); }, }); Tpl.Edit.$events({ 'click [name=cancel]': cancel, 'click [type=submit]': Form.submitFunc('Edit', doc => { Route.replacePage(Tpl); }), }); Tpl.Edit.$extend({ $destroyed: tabClosed, }); Tpl.Results.$helpers({ content() { const selected = document.querySelector('#Results>.categories>.cat.selected'); if (selected) return Dom.h([{button: "Print selection", class: 'link printResults'}, {button: "Clear selection", class: 'link clearSelected'}]); return "Select categories to print"; }, }); Tpl.Results.$events({ 'click .select': PrintHelper.clickSelect(function (me, selected, parent) { Dom.forEach(parent, '.heading.selected', elm => Dom.removeClass(elm, 'selected')); const action = parent.getElementsByClassName('action')[0]; $.ctx.updateAllTags(); }), 'click .clearSelected'(event) { Dom.stopEvent(); var parent = event.currentTarget; var selected = parent.getElementsByClassName('selected'); while (selected.length) Dom.removeClass(selected[0], 'selected'); }, 'click .printResults'(event) { Dom.stopEvent(); var parent = event.currentTarget; var selected = parent.getElementsByClassName('selected'); var elm = Dom.h({section: '', class: 'print-only'}); for(var i = 0; i < selected.length; ++i) { elm.appendChild(Tpl.CatResult.$render({append: $.data(selected[i])._id})); } parent.parentNode.appendChild(elm); Dom.addClass(parent, 'no-print'); window.print(); Dom.remove(elm); Dom.removeClass(parent, 'no-print'); }, }); Tpl.Results.$extend({ onBaseEntry(page, pageRoute, callback) { const series = Series.findById(pageRoute.seriesId); const elm = Tpl.Results.$autoRender({}); const parent = document.querySelector('#Series .tabBody'); parent.insertBefore(elm, parent.firstChild); tabOpened.call(Tpl.Results, elm, pageRoute); const ctx = Dom.myCtx(elm); session.rpc("Ranking.seriesResult", series._id, (err, results) => { if (err) { koru.globalErrorCatch(err); return; } Dom.removeClass(elm, 'loading'); ctx.results = results; elm.insertBefore(renderCategories(ctx), elm.firstChild); callback(); }); }, onBaseExit() {Dom.removeId('Results')}, $destroyed: tabClosed, }); Tpl.CatList.$events({ 'click .name'(event) { Dom.stopEvent(); const series = Series.findById(Route.currentPageRoute.seriesId); const cat = $.ctx.data; Route.gotoPage(Tpl.CatResult, {seriesId: series._id, append: cat._id}); }, }); Tpl.CatResult.$helpers({ category() { return Category.findById(this.category_id); }, events(each) { return this.events; }, climbers(each) { return this.climbers; }, }); Tpl.CatResult.$extend({ $created(ctx, elm) { const resultsBody = document.getElementById('Results'); const resultsCtx = Dom.ctx(resultsBody); ctx.parentCtx = resultsCtx; const {results} = resultsCtx; const climberMap = {}; const category_id = ctx.data.append; const climbers = []; const events = []; ctx.data = {climbers, events, category_id}; if (results) for (let ev of results) { events.push(Event.findById(ev.event_id)); for (let cat of ev.cats) { if (cat.category_id === category_id) { for (let [climber_id, points] of cat.results) { let climber = climberMap[climber_id]; if (! climber) climbers.push(climber = climberMap[climber_id] = { _id: climber_id, climber: Climber.findById(climber_id), total: 0, events: {}, }); climber.total += points; climber.events[ev.event_id] = points; } } } } events.sort(sortByDate); climbers.sort(util.compareByField('total', -1)); }, }); Tpl.CatResult.Row.$helpers({ events(each) { const eventMap = this.events; each.clear(); for (let row of $.ctx.parentCtx.data.events) each.append({_id: row._id, points: eventMap[row._id]}); }, }); function renderCategories(ctx) { const {results} = ctx; const compareCats = util.compareByFields('type', 'name'); const table = Dom.h({table: '', class: 'categories'}); if (results) { const catMap = {}; for (let {event_id, cats} of results) { for (let {category_id} of cats) { catMap[category_id] || (catMap[category_id] = Category.findById(category_id)); } } let type; for (let cat of util.values(catMap).sort(compareCats)) { if (type !== cat.type) { type = cat.type; table.appendChild(Dom.h({tr: {td: '', $colspan: '2'}, class: 'heading fmt '+type})); } table.appendChild(Tpl.CatList.$autoRender(cat, ctx)); } } return table; } Tpl.TeamResults.$helpers({ events(each) { return this.events || []; }, teams(each) { each.clear(); if (this.teams) for (let row of this.teams) { if (row.team.teamType_id === TeamHelper.teamType_id) each.append(row); } }, }); Tpl.TeamResults.$events({ 'menustart [name=selectTeamType]': TeamHelper.chooseTeamTypeEvent(teamTypeList), }); function teamTypeList(ctx) { return TeamType.where({_id: ctx.data.series.teamType_ids}).fetch(); } Tpl.TeamResults.$extend({ $created(ctx, elm) { const series = ctx.data; ctx.data = {}; TeamHelper.setSeriesTeamType(series); const parent = document.querySelector('#Series .tabBody'); parent.insertBefore(elm, parent.firstChild); session.rpc("Ranking.teamResults", series._id, (err, results) => { if (err) { koru.globalErrorCatch(err); return; } Dom.removeClass(elm, 'loading'); const teams = []; const teamMap = {}; const events = results.map(row => { for (let ttid in row.scores) { const tScores = row.scores[ttid]; for (let team_id in tScores) { let team = teamMap[team_id]; if (! team) { teams.push(team = teamMap[team_id] = { _id: team_id, team: Team.findById(team_id), total: 0, events: {}, }); } const points = tScores[team_id]; team.total += points; team.events[row.event_id] = points; } } return Event.findById(row.event_id); }); teams.sort(util.compareByField('total', -1)); events.sort(util.compareByField('date', -1)); ctx.updateAllTags({events, teams, series}); }); }, $destroyed: tabClosed, }); Tpl.TeamResults.Header.$events({ 'click th.event'(event) { Dom.stopEvent(); Route.gotoPage(Dom.Event.Show, {eventId: $.ctx.data._id}); }, }); Tpl.TeamResults.Row.$helpers({ events(each) { const eventMap = this.events; const {events} = $.ctx.parentCtx.data; each.clear(); for (let event of events) { each.append({_id: event._id, event, points: eventMap[event._id]}); } }, }); Tpl.Form.$helpers({ teamTypes: TeamHelper.eachTeamTypes, }); function cancel(event) { Dom.stopEvent(); Route.history.back(); } return Tpl; });
FONT_SIZE_LIST.forEach(row => { row[1] = Dom.h({font: row[1], $size: row[0]}); });
define((require, exports, module)=>{ const Dom = require('koru/dom'); const DocChange = require('koru/model/doc-change'); const Val = require('koru/model/validation'); const Observable = require('koru/observable'); const Form = require('koru/ui/form'); const Route = require('koru/ui/route'); const util = require('koru/util'); const Category = require('models/category'); const Event = require('models/event'); const Heat = require('models/heat'); const Result = require('models/result'); const Series = require('models/series'); const TeamType = require('models/team-type'); const EventHelper = require('ui/event-helper'); const PrintHelper = require('ui/print-helper'); const SeriesTpl = require('ui/series'); const SpeedEvent = require('ui/speed-event'); const App = require('./app'); const Tpl = Dom.newTemplate(require('koru/html!./event')); const $ = Dom.current; const {Index} = Tpl; const sortByDate = util.compareByField('date', -1); Route.root.defaultPage = Index; const heatOrderType = (catId)=>{ const type = Tpl.event.heats[catId]; return type === undefined ? undefined : type[0] === 'S' ? 'S' : type; }; const compareCategories = (a, b)=>{ const ai = a._id, bi = b._id; if (ai === bi) return 0; var ac, bc; if (((ac = heatOrderType(ai)) === (bc = heatOrderType(bi))) && ((ac = a.group) === (bc = b.group)) && ((ac = a.name) === (bc = b.name))) { return ai < bi ? -1 : 1; } return ac < bc ? -1 : 1; }; compareCategories.compareKeys = ['group', 'name', '_id']; Dom.registerHelpers({ lazySeriesList: () => () => Series.query.fetch().sort(util.compareByName) }); const base = Route.root.addBase(module, Tpl, 'eventId'); base.async = true; module.onUnload(()=>{ Tpl.event = null; Route.root.defaultPage = null; Route.root.removeBase(Tpl); }); let eventSub, resultOb; const pageChange = (page, pageRoute)=>{ const tabList = Dom('#Event>div>nav.tabbed'); const old = tabList.getElementsByClassName('selected')[0]; Dom.removeClass(old, "selected"); const query = `button[name="${page.name}"]`; let button = page.parent === Tpl ? tabList.querySelector( pageRoute.search ? `${query}[data-search="${pageRoute.search}"]` : query) : null; if (! button) { if (Dom.Event.Register !== undefined && Dom.Event.Register.$contains(page)) button = tabList.querySelector('[name="Register"]'); } Dom.setClass('hide', ! button, tabList); Dom.addClass(button, "selected"); }; const listType = ()=>{ return "type=" + (Tpl.Show.results ? "results" : "startlists"); }; const FORMAT_ROW = Dom.h({class: 'fmt', tr: {td: '', $colspan: 8}}); const buildTable = (table)=>{ const cats = Category.docs; let lastType, lastStyle; Dom.removeChildren(table); Object.keys(Result.eventCatIndex.lookup({event_id: Tpl.event._id})||{}) .map(cat_id => cats[cat_id]).sort(compareCategories) .forEach(doc =>{ if (doc == null) return; const thisType = heatOrderType(doc._id); if (lastType !== thisType) { lastType = thisType; const fr = FORMAT_ROW.cloneNode(true); if (lastStyle !== thisType[0]) { lastStyle = thisType[0]; Dom.addClass(fr, lastStyle); } fr.firstChild.textContent = "Format: " + Event.describeFormat(thisType); table.appendChild(fr); } table.appendChild(Tpl.CatList.$autoRender(doc)); }); }; const cancel = (event)=>{ Dom.stopEvent(); Route.history.back(); }; const observeScores = ()=>{ resultOb && resultOb.stop(); const counts = Tpl.scoreCounts = new Observable(); const calcScores = dc =>{ const {doc, was} = dc; if (doc.event_id !== (Tpl.event && Tpl.event._id)) return; const oldScores = (! dc.isAdd && was.scores) || []; const newScores = (! dc.isDelete && doc.scores) || []; const len = Math.max(oldScores.length, newScores.length); const {category_id} = doc; const scores = counts[category_id] || (counts[category_id] = []); let changed = false; for(let i = 0; i < len; ++i) { const count = (newScores[i] != null ? (oldScores[i] == null ? 1 : 0) : (oldScores[i] != null ? -1 : 0)); if (count != 0) { changed = true; scores[i] = (scores[i] || 0) + count; } } if (changed) { counts.notify(category_id); } }; resultOb = Result.onChange(calcScores); const docs = Result.docs; for (let id in docs) calcScores(DocChange.add(docs[id])); }; Tpl.$extend({ base, onBaseEntry: (page, pageRoute, callback)=>{ const elm = Tpl.$autoRender({}); pageRoute.eventId && Dom.myCtx(elm).onDestroy(Event.observeId( pageRoute.eventId, dc => Dom.setTitle(dc.doc.displayName))); document.body.appendChild(elm); const currentSub = eventSub; const pageTitle = document.getElementById('PageTitle'); if (pageTitle) pageTitle.href = `#${pageRoute.orgSN}/event/${pageRoute.eventId}/show`; if (pageRoute.eventId) { if (! Tpl.event || Tpl.event._id !== pageRoute.eventId) { if (Tpl.event = Event.findById(pageRoute.eventId)) { observeScores(); eventSub && eventSub.stop(); eventSub = App.subscribe('Event', pageRoute.eventId, ()=>{callback()}); } } } if (Tpl.event) { pageRoute.eventId = Tpl.event._id; Route.title = Tpl.event.displayName; } else Dom.addClass(elm, 'noEvent'); if (eventSub === currentSub) callback(); Tpl.stopNotify = Route.onChange(pageChange).stop; pageChange(page, pageRoute); }, onBaseExit: (page, pageRoute)=>{ Tpl.stopNotify && Tpl.stopNotify(); Dom.removeId('Event'); Route.title = null; }, stop: ()=>{ eventSub && eventSub.stop(); Tpl.event = null; }, }); Tpl.$events({ 'click .tabNames>button:not(.selected)'(event) { const tpl = Tpl[this.getAttribute('name')]; if (tpl === undefined) return; Dom.stopEvent(); const opts = {}; const search = this.getAttribute('data-search'); if (search) opts.search = search; Route.gotoPage(tpl, opts); }, }); Index.$helpers({ list(each) { return { query: ($.ctx.tab === 'series' ? Series : Event).query, compare: sortByDate, updateAllTags: true, }; }, addLink() { return Form.pageLink({ class: 'adminAccess action', name: 'add', value: 'Add new '+$.ctx.tab, template: $.ctx.tab === 'series' ? 'Event.AddSeries' : 'Event.Add'}); }, selectedTab(type) { Dom.setClass("selected", type === $.ctx.tab); Dom.addClass($.element, type); }, }); Index.Row.$helpers({ classes() { return this === Tpl.event ? "selected" : ""; }, }); Index.$events({ 'click .list tr'(event) { Dom.stopEvent(); const data = $.data(this); if ($.ctx.tab === 'series') Route.gotoPath('series/'+data._id); else Route.gotoPage(Tpl.Show, {eventId: data._id}); }, 'click button:not(.selected).tab'(event) { Dom.stopEvent(); Route.replacePage(Tpl.Index, {hash: '#'+this.getAttribute('name')}); }, }); Index.$extend({ title: "Calendar", $created(ctx, elm) { ctx.tab = (ctx.data.hash || '#event').slice(1); ctx.data = {}; } }); base.addTemplate(module, Index, { defaultPage: true, path: '', data: (page, pageRoute) => pageRoute, }); base.addTemplate(module, Tpl.Add, { focus: true, data() { return new Event({org_id: App.orgId, teamType_ids: TeamType.where('default', true).fetchIds()}); } }); base.addTemplate(module, Tpl.AddSeries, { focus: true, data() { return new Series({org_id: App.orgId, teamType_ids: TeamType.where('default', true).fetchIds()}); } }); base.addTemplate(module, Tpl.Show, { focus: true, data(page, pageRoute) { if (! Tpl.event) Route.abortPage(); Tpl.Show.results = Route.searchParams(pageRoute).type !== 'startlists'; return Tpl.event; } }); base.addTemplate(module, Tpl.Edit, { focus: true, data(page, pageRoute) { if (! Tpl.event) Route.abortPage(); return Tpl.event; } }); App.restrictAccess(Tpl.Edit); Tpl.Add.$events({ 'click [name=cancel]': cancel, 'click [type=submit]': Form.submitFunc('AddEvent', 'back'), }); Tpl.AddSeries.$events({ 'click [name=cancel]': cancel, 'click [type=submit]': Form.submitFunc('AddSeries', 'back'), }); Tpl.Edit.$events({ 'click [name=cancel]': cancel, 'click [name=delete]'(event) { const doc = $.data(); Dom.stopEvent(); Dom.Dialog.confirm({ data: doc, classes: 'warn', okay: 'Delete', content: Tpl.ConfirmDelete, callback(confirmed) { if (confirmed) { doc.$remove(); Route.gotoPage(Tpl); } }, }); }, 'click [type=submit]': Form.submitFunc('EditEvent', Tpl), 'change [name=changeFormat]'(event) { const cat = $.data(this); const ev = Tpl.event; ev.$change('heats')[cat._id] = cat.type + this.value; if (ev.$save()) { Form.clearErrors(this.parentNode); } else { Dom.stopEvent(); this.focus(); this.select(); Form.renderError(this.parentNode, 'changeFormat', Val.Error.msgFor(ev, 'heats')); ev.$reload(); } }, }); Tpl.Edit.$helpers({ categories(each) { const cats = Category.docs; const {ctx} = $; ctx.onEventChange == null || ctx.onEventChange.stop(); ctx.onEventChange = Event.observeId(this._id, ()=>{ each.list.changeOptions({updateAllTags: true}); }); return { query: { forEach: body =>{ for (const cat_id in Result.eventCatIndex.lookup({event_id: this._id})) { body(cats[cat_id]); } }, onChange: body => { return Result.onChange(dc =>{ if (dc.isChange) return; body(dc.clone()._set(cats[dc.doc.category_id])); }); }, compare: compareCategories, } }; }, }); Tpl.Form.$helpers({ seriesName() { const series = this.series; return series && series.name; }, }); Tpl.Edit.$extend({ $destroyed(ctx, elm) { ctx.onEventChange && ctx.onEventChange.stop(); ctx.onEventChange = null; ctx.data.$clearChanges(); } }); Tpl.Edit.Cat.$helpers({ eventFormat() { const event = $.ctx.parentCtx.data; const format = event.heats[this._id]; if (format) return format.slice(1); else return this.heatFormat; // not yet copied to event } , describeFormat() { const event = $.ctx.parentCtx.data; return Event.describeFormat(event.heats[this._id] || this.type+this.heatFormat); } }); const {SPEED_FINAL_NAME} = EventHelper; const renderSpeedHeats = (cat, type) =>{ const frag = document.createDocumentFragment(); const isResults = Tpl.Show.results; const format = Tpl.event.attributes.heats[cat._id]; const start = format[1] === 'C' ? 2 : 1; frag.appendChild(createHeatTD('Quals', cat, type, 0)); for (let i = start; i < format.length; ++i) { const heat = format[i]; frag.appendChild(createHeatTD(SPEED_FINAL_NAME[heat], cat, type, heat)); } const elm = document.createElement('td'); elm.setAttribute('colspan', 5 - format.length + start); frag.appendChild(elm); return frag; }; const createHeatTD = (name, cat, type, heatNumber)=>{ const elm = document.createElement('td'); elm.appendChild(Form.pageLink({ value: name, template: "Event.Category", append: cat._id, search: type + "&heat="+(heatNumber === 'R' ? 1 : heatNumber), })); return elm; }; Tpl.CatList.$helpers({ catID() { return "cat_"+this._id; }, heats() { const cat = this; const type = listType(); if (cat.type === 'S') { return renderSpeedHeats(cat, type); } const frag = document.createDocumentFragment(); const counts = Tpl.scoreCounts[cat._id] || []; const format = Tpl.event.heats[cat._id] || cat.type + cat.heatFormat; const heat = new Heat(-1, format); let total = heat.total; if (Tpl.Show.results) ++total; const addHeat = (i)=>{frag.appendChild(createHeatTD(heat.getName(i), cat, type, i))}; let i = 1; for(; i < total && counts[i]; ++i) { addHeat(i); } if (! Tpl.Show.results) { addHeat(i); $.data($.element.previousElementSibling.firstChild).search += "&heat="+i; } if (i < 7) { const elm = document.createElement('td'); elm.setAttribute('colspan', 7 - i); frag.appendChild(elm); } return frag; }, listType, }); Tpl.Show.$helpers({ listType() { return Tpl.Show.results ? "results" : "start lists"; }, eachCategory() { if ($.element.tagName === 'TABLE') { buildTable($.element); return; } const table = document.createElement('table'); Dom.addClass(table, 'categories'); buildTable(table); $.ctx.onDestroy(Result.onChange(dc =>{ dc.isChange || buildTable(table); // else already showing this row })); $.ctx.onDestroy(Tpl.scoreCounts.onChange((cat_id)=>{ const elm = document.getElementById('cat_'+cat_id); elm === null || Dom.myCtx(elm).updateAllTags(); })); return table; }, }); Tpl.Show.$events({ 'click .select': PrintHelper.clickSelect((me, selected, parent)=>{ const action = parent.getElementsByClassName('action')[0]; const fmt = me && heatOrderType($.data(me)._id); if (fmt != null) { for(let i = 0; i < selected.length; ) { if (heatOrderType($.data(selected[i])._id) !== fmt) Dom.removeClass(selected[i], 'selected'); else ++i; } } Dom.ctx(action).updateAllTags({fmt, selected}); }), 'click .printResults'(event) { Dom.stopEvent(); const heatNumber = +this.getAttribute('data-heat'); const parent = event.currentTarget; const selected = parent.getElementsByClassName('selected'); const elm = document.createElement('section'); elm.className = 'print-only'; const ev = Tpl.event; const {heats} = ev; for(let i = 0; i < selected.length; ++i) { const category = $.data(selected[i]); if (category.type === 'S' && heatNumber > 0) { const fmt = heats[category._id]; let found = false; for(let i = fmt.length; i > 0; --i) { const code = fmt[i]; if (code === 'R' && heatNumber == 1 || +code == heatNumber) { found = true; break; } } if (! found) continue; } const template = category.type === 'S' ? SpeedEvent : Tpl.Category; elm.appendChild(template.$render({ event_id: ev._id, showingResults: Tpl.Show.results, heatNumber: heatNumber, category})); } parent.parentNode.appendChild(elm); Dom.addClass(parent, 'no-print'); window.print(); Dom.remove(elm); Dom.removeClass(parent, 'no-print'); }, }); Tpl.Show.$extend({ $created(ctx) { Tpl.Show.titleSuffix = Tpl.Show.results ? 'Results' : 'Start lists'; ctx.autoUpdate(); }, }); Tpl.Show.Action.$helpers({ content() { if (this.fmt != null) { const frag = document.createDocumentFragment(); const elm = document.createElement('span'); elm.textContent = "Print "; frag.appendChild(elm); const addButton = (number, name)=>{ if (number < -1 || number === 99) return; const elm = document.createElement('button'); elm.textContent = name; elm.setAttribute('data-heat', number); elm.className = "link printResults"; frag.appendChild(elm); }; Tpl.Show.results && addButton(-1, 'General'); if (this.fmt === 'S') { addButton(0, 'Quals'); const formats = []; for (const elm of this.selected) { const fmt = Tpl.event.heats[$.data(elm)._id]; for(let i = fmt.length - 1; i > 0; --i) { const code = fmt[i] === 'R' ? 1 : +fmt[i]; formats[code] = true; } } for (let i = formats.length; i >= 0 ; --i) { formats[i] && addButton(i, SPEED_FINAL_NAME[i]); } } else { new Heat(-1, this.fmt).headers(addButton); } return frag; } else return "Select categories to print"; }, }); return Tpl; });
define((require, exports, module)=>{ const koru = require('koru'); const Compilers = require('koru/compilers'); const Dom = require('koru/dom'); const DomTemplate = require('koru/dom/template'); const fst = require('koru/fs-tools'); const BaseController = require('koru/server-pages/base-controller'); const util = require('koru/util'); const path = requirejs.nodeRequire('path'); const views$ = Symbol(), defaultLayout$ = Symbol(); const genericLayout = {$render({content}) { return Dom.h({html: {body: content}}); }}; Dom.registerHelpers({ less(file) { const {App} = this.controller; const dir = path.join(App._pageDirPath, path.dirname(file)), base = path.basename(file)+".less"; try { return Compilers.read('less', path.join(dir, base), path.join(dir, '.build', base+".css")); } catch(ex) { if (ex.error === 404) return; } }, css(file) { const {App} = this.controller; return fst.readFile(path.join(App._pageDirPath, file+".css")).toString(); }, controllerId() {return this.controller.constructor.modId}, page() {return this.controller.pathParts[0] || "root"}, }); const addViewController = (sp, name, View, Controller)=>{ Controller.modId = name; View.Controller = Controller; View.Controller.App = sp; sp[views$][name] = View; }; const removeViewController = (sp, name)=>{ sp[views$][name] = undefined; }; const requirePage = (id, {onunload}={})=>{ let viewId = `koru/html-server!${id}`; require(viewId, _=>{}, (err, mod) =>{ if (err.error !== 404) return; mod.unload(); viewId =`koru/html-md!${id}`; require(viewId, _=>{}, err2 =>{ throw err2.error === 404 ? err : err2; }); }); const viewMod = module.get(viewId); let View, Controller = BaseController; require(id, (tplf) => { const controllerMod = module.get(id); View = DomTemplate.newTemplate(viewMod, viewMod.exports); if (typeof tplf === 'function') Controller = tplf({View, Controller: BaseController}) || BaseController; const unload = ()=>{ koru.unload(controllerMod.id); koru.unload(viewMod.id); if (onunload !== undefined) onunload(); }; koru.onunload(viewMod, unload); koru.onunload(controllerMod, unload); }); return {View, Controller}; }; const fetchView = (sp, parts, pos=0, views=sp[views$], root=sp._pageDirPath)=>{ while (pos < parts.length && ! parts[pos]) ++pos; const key = parts[pos] || ''; let view = views[key]; if (view === undefined && key.indexOf(".") === -1) { const fn = path.resolve(root, key) + ".js"; const stfn = fst.stat(fn); if (stfn != null) { const id = sp._pageDir+parts.slice(0, pos+1).join('/'); const {View, Controller} = requirePage(id, {onunload() {removeViewController(sp, key)}}); addViewController(sp, key, View, Controller); view = views[key]; } } return {view, pathParts: parts.slice(pos+1)}; }; const decodePathPart = (i)=>{ try { return util.decodeURIComponent(i) || ''; } catch(ex) { return i; } }; class ServerPages { constructor(WebServer, pageDir='server-pages', pathRoot='DEFAULT') { this._pageDir = pageDir; this._pageDirPath = path.join(koru.appDir, pageDir); this._pathRoot = pathRoot; this.WebServer = WebServer; const defaultLayoutId = path.join(pageDir, "layouts/default"); this[defaultLayout$] = fst.stat(path.join(koru.appDir, defaultLayoutId+".js")) ? requirePage(defaultLayoutId).View : genericLayout; this[views$] = {}; this._handleRequest = (request, response, urlPath, error)=>{ const searchIdx = urlPath.indexOf('?'); const parts = (searchIdx == -1 ? urlPath : urlPath.slice(0, searchIdx)) .split('/').map(i => decodePathPart(i)), plen = parts.length; const suffixIdx = plen == 0 ? '' : parts[plen-1].search(/\.[^.]+$/); if (suffixIdx == -1) { this.suffix = 'html'; } else { this.suffix = parts[plen-1].slice(suffixIdx+1); parts[plen-1] = parts[plen-1].slice(0, suffixIdx); } const {view, pathParts} = fetchView( this, parts); if (view === undefined) return false; const params = searchIdx == -1 ? {} : util.searchStrToMap(urlPath.slice(searchIdx+1)); try { new view.Controller({view, request, response, pathParts, params}); } catch(ex) { if (typeof ex.error === 'number') error(ex.error, ex.reason); else error(ex); } }; WebServer.registerHandler(module, pathRoot, this._handleRequest); } get BaseController() {return BaseController} get defaultLayout() {return this[defaultLayout$]} set defaultLayout(value) {this[defaultLayout$] = value || genericLayout} addViewController(name, View, Controller) { addViewController(this, name, View, Controller); } stop() { this.WebServer.deregisterHandler(this._pathRoot); } } koru.onunload(module, 'reload'); return ServerPages; });
define((require, exports, module)=>{ const koru = require('koru'); const Dom = require('koru/dom'); const localStorage = require('koru/local-storage'); require('koru/ui/helpers'); const Route = require('koru/ui/route'); const KoruUITH = require('koru/ui/test-helper'); const util = require('koru/util'); const BaseTH = require('test-helper'); const Factory = require('test/factory'); const App = require('ui/app'); koru.onunload(module, 'reload'); const TH = { __proto__: BaseTH, setOrg(org) { org = org || Factory.createOrg(); App.orgId = org._id; localStorage.setItem('orgSN', org.shortName); Dom.addClass(document.body, 'inOrg'); }, tearDown(v) { TH.clearDB(); TH.domTearDown(); util.thread.userId = null; }, addStyles(styles) { var style = Dom.h({style: '', class: "testStyle"}); style.innerHTML = styles; document.head.appendChild(style); }, pointer(node, eventName, args) { if (typeof node === 'string') { assert.elideFromStack.dom(node, elm =>{TH.pointer(elm, eventName, args)}); } else { assert.elideFromStack(node,'node not found'); if (typeof eventName === 'object') { args = eventName; eventName = 'pointermove'; } else { eventName = eventName || 'pointermove'; args = args || {}; } var bbox = node.getBoundingClientRect(); args.clientX = bbox.left + bbox.width/2; args.clientY = bbox.top + bbox.height/2; var event = TH.buildEvent(eventName, args); if (document.createEvent) { TH.dispatchEvent(node, event); } else { node.fireEvent("on" + event.__name, event); } return event; } }, stubRAF(v) { let func = null; TH.intercept(window, 'requestAnimationFrame', arg =>{ func = arg; return 123; }); v.nextRaf = ()=>{ if (func !== null) { func(); func = null; } }; }, confirmRemove(func) { const dialog = document.body.querySelector('.Dialog #ConfirmRemove'); assert.elideFromStack.msg('should display Confirm Dialog')(dialog); TH.confirm(func); }, confirm(func) { const dialog = document.body.querySelector('.Dialog'); assert.elideFromStack.msg('should display Confirm Dialog')(dialog); assert.elideFromStack.dom(dialog, elm=>{ const data = Dom.current.data(elm); assert.isFunction(data.onConfirm); func && func(elm); TH.click('[name=okay]'); }); }, }; util.mergeOwnDescriptors(TH, KoruUITH); let onAnimationEnd; TH.Core.onStart(()=>{ onAnimationEnd = Dom.Ctx.prototype.onAnimationEnd; Dom.Ctx.prototype.onAnimationEnd = function (func, repeat) { func(this, this.element()); }; }); TH.Core.onEnd(()=>{Dom.Ctx.prototype.onAnimationEnd = onAnimationEnd}); module.exports = TH; });
}).forEach(id => { const api = json[id]; api.id = id; api.parent = json; const {subject, newInstance, methods, customMethods, protoMethods, innerSubjects} = api; const aside = []; const addModuleList = (heading, list)=>{ if (list) { aside.push({div: [ {h1: heading}, {class: 'jsdoc-list', div: list.map(id => { const m = id.split('!'); if (m.length === 2) { if (m[0] === 'koru/env') { const idc = m[1]+'-client'; const ids = m[1]+'-server'; return {span: [ {class: 'jsdoc-link', a: idc, $href: '#'+idc}, {br: ''}, {class: 'jsdoc-link', a: ids, $href: '#'+ids}, ]}; } return id; } return {class: 'jsdoc-link', a: id, $href: '#'+id}; })}, ]}); } }; // addModuleList('Modules required', api.requires); addModuleList('Modifies modules', api.modifies); addModuleList('Modified by modules', api.modifiedBy); const idParts = /^([^:.]+)([.:]*)(.*)$/.exec(id); const reqParts = [ hl('const', 'kd'), ' ', hl(subject.name, 'no'), ' ', hl('=', 'o'), ' ', hl('require', 'k'), '(', hl(`"${idParts[1]}"`, 's'), ')' ]; switch (idParts[2]) { case '.': reqParts.push('.', hl(idParts[3], 'na')); break; case '::': const ref = json[idParts[1]]; if (ref) reqParts[2].textContent = ref.subject.name; } reqParts.push(';'); const requireLine = Dom.h({class: 'jsdoc-require highlight', div: reqParts}); const functions = newInstance ? [buildConstructor( api, subject, newInstance, requireLine )] : []; util.isObjEmpty(customMethods) || util.append(functions, buildMethods(api, subject, customMethods, requireLine, 'custom')); util.isObjEmpty(methods) || util.append(functions, buildMethods(api, subject, methods, requireLine)); util.isObjEmpty(protoMethods) || util.append(functions, buildMethods(api, subject, protoMethods, requireLine, 'proto')); const linkNav = {nav: functions.map( func => func && Dom.h({a: func.$name, $href: '#'+func.id}) )}; linkModules.push([id, linkNav]); const abstractMap = {}; const abstract = jsdocToHtml(api, subject.abstract, abstractMap); const configMap = abstractMap[':config:']; let config; if (configMap) { config = {class: 'jsdoc-config', table: [ {tr: {$colspan: 2, td: {h1: 'Config'}}}, ...Object.keys(configMap).sort().map(key => Dom.h({ class: 'jsdoc-config-item', tr: [{td: key}, {td: configMap[key]}] })) ]}; } let properties = util.isObjEmpty(api.properties) ? [] : buildProperties(api, subject, api.properties); util.isObjEmpty(api.protoProperties) || (properties = properties.concat(buildProperties(api, subject, api.protoProperties, 'proto'))); pages.appendChild(Dom.h({ id, '$data-env': env(api), class: /::/.test(id) ? "jsdoc-module jsdoc-innerSubject" : "jsdoc-module", section: [ {class: 'jsdoc-module-path', a: id, $href: '#'+id}, {class: 'jsdoc-module-title searchable', h1: subject.name}, {abstract}, {class: 'jsdoc-module-sidebar', aside}, {div: [ config, properties.length && {class: 'jsdoc-properties', div: [ {h1: 'Properties'}, {table: {tbody: properties}} ]}, functions.length && {class: 'jsdoc-methods', div: [ {h1: "Methods"}, {div: functions}, ]}, innerSubjects && buildInnerSubjects(api, innerSubjects, linkNav), ]}, // {pre: JSON.stringify(json, null, 2)} ], })); });
define((require, exports, module)=>{ const Dom = require('koru/dom'); const COUNTER = Dom.h({class: 'ui-charCounter', div: {div: [{span: ''}, {span: ''}]}}); const OB_DataChange = { subtree: true, childList: true, characterData: true, }; const countText = (editor)=>{ let size = 0; for (let node = editor.firstChild; node !== null; node = node.nextSibling) { const {nodeType} = node; if (nodeType === document.TEXT_NODE) size += node.nodeValue.length; else if (nodeType === 1) { if (node.tagName === 'BR') ++size; else size += countText(node); } } return size; }; class CharacterCounter { constructor({maxlength=0, warninglength=Math.floor(maxlength*.9)}) { this.maxlength = maxlength; this.warninglength = warninglength; this.element = COUNTER.cloneNode(true); this.mutationObserver = new window.MutationObserver(muts =>{ if (muts.length == 0) return; this.checkNow(); }); this.editor = null; } attach(editor=null) { if (this.editor !== null) this.mutationObserver.disconnect(); this.editor = editor; if (editor == null) return; const counter = this.element.firstChild; counter.classList.remove('ui-error', 'ui-warn'); if (this.maxlength != 0) counter.lastChild.textContent = ''+this.maxlength; this.prevSize = 0; this.mutationObserver.observe(editor, OB_DataChange); this.checkNow(); } checkNow() { if (this.editor === null || this.editor.parentNode === null) { this.mutationObserver.disconnect(); this.editor = null; return; } const size = countText(this.editor); const counter = this.element.firstChild; counter.firstChild.textContent = ''+size; const {warninglength, maxlength, prevSize} = this; if (warninglength <= size) { warninglength > prevSize && counter.classList.add('ui-warn'); if (maxlength < size) maxlength >= prevSize && counter.classList.add('ui-error'); else maxlength < prevSize && counter.classList.remove('ui-error'); } else { warninglength <= prevSize && counter.classList.remove('ui-warn'); maxlength < prevSize && counter.classList.remove('ui-error'); } this.prevSize = size; } }; return CharacterCounter; });
define((require, exports, module)=>{ const Dom = require('koru/dom'); const DocChange = require('koru/model/doc-change'); const AutoList = require('koru/ui/auto-list'); const Dialog = require('koru/ui/dialog'); const Route = require('koru/ui/route'); const util = require('koru/util'); const Event = require('models/event'); const Result = require('models/result'); const SpeedRound = require('models/speed-round'); const User = require('models/user'); const ClimberCell = require('ui/climber-cell'); const EventHelper = require('ui/event-helper'); const orig$ = Symbol(); const {SPEED_FINAL_NAME} = EventHelper; const {endMarker$} = require('koru/symbols'); const {myCtx} = Dom; const {laneA, laneB, ranking} = SpeedRound; const Tpl = Dom.newTemplate(module, require('koru/html!./speed-event')); const {ABList, ABRow, RankRow, QualResults, GeneralList, GeneralRow} = Tpl; const $ = Dom.current; const appendNextStage = (data, pn)=>{ if (User.me().isAdmin()) { pn.appendChild(Dom.h( data.isClosed ? {class: 'reopen', div: {button: ['Reopen '+HEAT_NAME[data.heatNumber]], class: 'action'}} : {class: 'nextStage', div: {button: ['Next'], class: 'action'}})); } }; const renderQuals = (data, pn, Template, Row, recalc)=>{ const {round} = data; const table = Template.$autoRender(data, Dom.ctx(pn)); const list = data.list = new AutoList({ template: Row, container: table.lastElementChild, compare: round.entries.compare, query: { forEach: add =>{for (const res of round) add(res)} } }); myCtx(table).onDestroy(round.onChange(dc =>{recalc(dc, list)})); pn.appendChild(table); }; const renderFinals = (data, pn, showingResults)=>{ const {round} = data; if (showingResults) round.rankResults(); else round.calcStartList(); const table = ABList.$autoRender(data, Dom.ctx(pn)); const ctx = myCtx(table); const len = 1<<round.stage; const list = new AutoList({ template: ABRow, container: table.lastElementChild, compare: SpeedRound.compareRankingSORT, query: { forEach: add =>{for (const res of round) res && add(res)} } }); ctx.onDestroy(round.onChange(dc =>{ if (showingResults) { round.rankResults(); list.changeOptions({updateAllTags: true}); } else if (dc.isChange) { list.updateEntry(laneA(dc.doc)); } else { round.calcStartList(); list.changeOptions({updateAllTags: true}); } })); pn.appendChild(table); data.showingResults || appendNextStage(data, pn); }; const renderGeneralResults = (data, pn)=>{ const event = Event.findById(data.event_id); data.heatFormat = event.heats[data.category._id]; const table = GeneralList.$autoRender(data, Dom.ctx(pn)); const ctx = myCtx(table); const {round} = data; const len = 1<<round.stage; round.rankResults(); const list = new AutoList({ template: GeneralRow, container: table.lastElementChild, compare: SpeedRound.compareRankingSORT, query: { forEach: add =>{for (const res of round) add(res)} } }); const redraw = ()=>{ round.rankResults(); ctx.updateAllTags(); list.changeOptions({updateAllTags: true}); }; ctx.onDestroy(Event.observeId(data.event_id, ()=>{ data.heatFormat = event.heats[data.category._id]; ctx.updateAllTags(); redraw(); })); ctx.onDestroy(round.onChange(redraw)); pn.appendChild(table); }; const previousStage = (format, stage)=>{ if (stage == 0) return; for(let i = format.length-1; i > 0; --i) { if (+format[i] === stage) { return +format[i-1] || 0; } } return 0; }; Tpl.$extend({ $created(ctx, elm) { const {data} = ctx; util.merge(data, { get selectHeat() {return this.heatNumber}, set selectHeat(value) {return this.heatNumber = value}, }); const format = Event.findById(data.event_id).heats[data.category._id]; const {showingResults} = data; const hasReRun = /R/.test(format); const heatNumber = hasReRun && data.heatNumber == 1 ? -2 : data.heatNumber; data.isClosed = heatNumber == 0 ? format.length != 1 : format.indexOf("C") !== -1 || format.indexOf(""+(heatNumber-1)) !== -1; const round = data.round = new SpeedRound({ stage: heatNumber == -1 && hasReRun ? -2 : heatNumber, previous: previousStage(format, heatNumber), query: Result.query.withIndex(Result.eventCatIndex, { event_id: data.event_id, category_id: data.category._id}) }); if (heatNumber == 0 || heatNumber == -2) { if (showingResults) { round.rankResults(); renderQuals(data, elm, QualResults, RankRow, (dc, list)=>{ round.rankResults(); list.changeOptions({updateAllTags: true}); }); } else { round.calcStartList(); renderQuals(data, elm, ABList, ABRow, (dc,list) =>{ if (dc.isChange) { list.updateEntry(dc.doc); list.updateEntry(laneA(dc.doc)); } if (dc.isDelete) round.entries.delete(dc.doc); else if (dc.isAdd) round.entries.add(dc.doc); round.recalcQualsStartList(); }); appendNextStage(data, elm); } } else if (heatNumber == -1) { elm.classList.add('general'); renderGeneralResults(data, elm); } else { renderFinals(data, elm, showingResults); } }, }); EventHelper.extendResultTemplate(Tpl); const calcSpeedFormat = ({stage}, fmt, nextStage)=>{ let mx, mn; if (stage == -2) { return "SCR"; } if (stage == 0) { if (nextStage == -2) return "SR"; mx = nextStage; mn = mx; fmt.slice(2).split('').forEach(s => { const n = +s; if (n < mn) mn = n; }); } else { const ps = nextStage == -3 ? stage : stage-1; mx = 0; mn = ps; fmt.slice(1).split('').forEach(s => { const n = +s; if (n == ps) return fmt; if (n > mx) mx = n; if (n < mn) mn = n; }); } fmt = nextStage == -3 ? 'SC' : 'S'; for(let i = mx; i >= mn ; --i) fmt += i; return fmt; }; Tpl.$events({ 'click .reopen'(event) { Dom.stopEvent(); const {data} = $.ctx; const {heatNumber} = data; Dialog.confirm({ classes: 'warn', content: Dom.h([{ div: `Are you sure you want to reopen "${HEAT_NAME[heatNumber]}" and later stages?`, }, { div: `If later stage results have been entered then they may be corrupted by changes here. BE CAREFUL.`, }]), okay: 'Yes', onConfirm: ()=>{ const ev = Event.findById(data.event_id); let fmt = ev.heats[data.category._id].replace(/[C]/g, ''); if (heatNumber == 0 || /R/.test(fmt)) fmt = 'S'; else if (heatNumber == 1) { fmt = fmt.replace(/^SC/, 'S'); } else { const idx = fmt.indexOf(""+heatNumber); fmt = fmt.slice(0, idx+1); } ev.changes = {$partial: {heats: [data.category._id, fmt]}}; ev.$$save(); EventHelper.replacePage(data, data.showingResults, heatNumber); } }); }, 'click .nextStage'(event) { Dom.stopEvent(); Dom.remove(this.querySelector('.info')); const {data} = $.ctx; const {round} = $.ctx.data; const {error, nextStage} = round.complete(); if (error !== '') { this.appendChild(Dom.h({ class: 'info', div: error})); } else { Dialog.confirm({ content: `Do you wish to proceed from the "${HEAT_NAME[data.heatNumber] }" to the "${HEAT_NAME[nextStage]}"`, okay: 'Yes', onConfirm: ()=>{ const ev = Event.findById(data.event_id); const fmt = ev.heats[data.category._id]; ev.changes = {$partial: { heats: [data.category._id, calcSpeedFormat(round, fmt, nextStage)]}}; ev.$$save(); Route.gotoPage(Dom.Event.Category, { eventId: ev._id, append: data.category._id, search: `?type=${data.showingResults ? 'startlists' : 'results'}&heat=${ data.heatNumber}`}); } }); } }, 'keydown input'(event) { switch(event.which) { case 27: Dom.stopEvent(); this.value = this[orig$]; return; } }, }); const HEAT_NAME = { '-3': 'Results', '-2': 'Final', 0: 'Qualifiers', 1: 'Final', 2: 'Semi final', 3: 'Quarter final', 4: 'Round of 16', }; const formatTime = (time, full=true)=> typeof time === 'number' ? (full ? util.toDp(time/1000, 3, true) : util.toDp(Math.floor(time/10)/100, 2, true)) : (time === 'tie' ? '' : time || ''); const timeToElm = (isInput, elm, disp)=>{ if (isInput) { if (elm !== null && elm.nodeType === 1 && elm.tagName === 'INPUT') { elm[orig$] = elm.value = disp; } else { elm = Dom.h({required: 'required', input: [], value: disp}); elm[orig$] = disp; } } else { if (elm !== null && elm.nodeType === document.TEXT_NODE) elm.textContent = disp; else elm = document.createTextNode(disp); } return elm; }; const displayTime = (res, index, isStartlist=! $.ctx.parentCtx.data.showingResults)=>{ const pn = $.element; if (res == null) { Dom.removeChildren(pn); return; } const {round, isClosed} = $.ctx.parentCtx.data; const time = round.getTime(res, index); const isInput = isStartlist && ! isClosed && res.event.canJudge(); let elm = timeToElm(isInput, pn.firstChild, formatTime(time)); if (elm !== pn.firstChild) { pn.firstChild !== null && pn.firstChild.remove(); pn.insertBefore(elm, pn.firstChild); } elm = elm.nextSibling; if (index != 1 || round.stage > 0) { let attempt = 0; for (const score of round.attempts(res)) { let ae = timeToElm(isInput, elm, formatTime(score)); if (ae.nodeType !== 1) { ae = Dom.h({span: ae}); } ae.setAttribute('data-attempt', ++attempt); if (elm !== ae) { pn.insertBefore(ae, elm); elm !== null && elm.remove(); } elm = ae.nextSibling; } while (elm !== null) { const ne = elm.nextSibling; elm.remove(); elm = ne; } } }; Tpl.$helpers({ heats() { const event = Event.findById(this.event_id); const heatFormat = event.heats[this.category._id]; const list = [[0, 'Quals']]; for(let i = 1; i < heatFormat.length; ++i) { const code = heatFormat[i]; list.push([code == 'R' ? 1 : +code, SPEED_FINAL_NAME[code]]); } list.push([-1, "General"]); return list; }, heatName() {return this.heatNumber == -1 ? 'General' : HEAT_NAME[this.heatNumber]}, }); RankRow.$helpers({ rank() { const {round} = $.ctx.parentCtx.data; return round.hasClimbed(this) ? ranking(this) : ''; }, climber() { return ClimberCell.$render(this); }, scoreA() {displayTime(this, 0, false)}, scoreB() {displayTime(this, 1, false)}, markWinner() { const {round} = $.ctx.parentCtx.data; if (round.stage != 0) return null; const res = this; Dom.setClass('winner', ranking(res)-1 < round.cutoff); } }); ABList.$events({ 'change .score>input'(event) { const value = this.value.trim() || undefined; const num = Math.floor(+value >= 1000 ? +value : (+value)*1000); const {round} = $.ctx.parentCtx.data; const isLaneA = this.parentNode.nextElementSibling.classList.contains('score'); const time = num == num ? num : value; const attempt = this.getAttribute('data-attempt'); const result = isLaneA ? $.data(this) : laneB($.data(this)); if (attempt) { round.setTime(result, {time, attempt: +attempt}); } else { round.setTime(result, { opponent_id: (isLaneA ? laneB(result) : laneA(result))._id, time, lane: isLaneA ? 0 : 1}); } }, }); ABRow.$helpers({ climberA() { return ClimberCell.$render(this); }, climberB() { return ClimberCell.$render(laneB(this)); }, scoreA() {displayTime(this, 0)}, scoreB() {displayTime(laneB(this), 1)}, markWinner() { const {round} = $.ctx.parentCtx.data; if (round.stage == 0 || round.stage == -2) return null; const resA = this; const resB = laneB(this); const winner = round.whoWonFinals(resA, resB); $.element.setAttribute('winner', winner === resA ? 'a' : winner === resB ? 'b' : ''); } }); GeneralList.$helpers({ finalHeadings() { const {heatFormat} = this; const frag = document.createDocumentFragment(); const start = heatFormat[1] === 'C' ? 1 : 0; for(let i = heatFormat.length - 1; i > start; --i) { frag.appendChild(Dom.h({class: 'score', th: SPEED_FINAL_NAME[heatFormat[i]]})); } return frag; } }); GeneralRow.$helpers({ rank() { const {round} = $.ctx.parentCtx.data; const qualScores = this.scores[1]; return qualScores == null || (qualScores[0] == null && qualScores[1] == null) ? '' : ranking(this); }, finalScores() { const {heatFormat} = $.ctx.parentCtx.data; const frag = document.createDocumentFragment(); const start = heatFormat[1] === 'C' ? 1 : 0; for(let i = heatFormat.length - 1; i > start; --i) { const code = heatFormat[i]; let time; if (code === 'R') { time = SpeedRound.minQual(this, 2); } else { const final = this.scores[+code+1]; time = final && final.time; } frag.appendChild(Dom.h({class: 'score', td: formatTime(time, false)})); } return frag; }, climber() { return ClimberCell.$render(this); }, qualScore() { return formatTime(SpeedRound.minQual(this), false); }, }); return Tpl; });
...Object.keys(configMap).sort().map(key => Dom.h({ class: 'jsdoc-config-item', tr: [{td: key}, {td: configMap[key]}] }))
func => func && Dom.h({a: func.$name, $href: '#'+func.id})
const noContent = (tag)=> opts =>{ const attrs = {[tag]: ''}; for (let attr in opts) attrs['$'+attr] = opts[attr]; return Dom.h(attrs); };
define(function(require, exports, module) { const koru = require('koru'); const Dom = require('koru/dom'); const CompleteList = require('koru/ui/complete-list'); const Dialog = require('koru/ui/dialog'); const Form = require('koru/ui/form'); const Route = require('koru/ui/route'); const SelectMenu = require('koru/ui/select-menu'); const util = require('koru/util'); const Category = require('models/category'); const Climber = require('models/climber'); const Competitor = require('models/competitor'); const Team = require('models/team'); const TeamType = require('models/team-type'); const User = require('models/user'); const TeamTpl = require('ui/team'); const TeamHelper = require('ui/team-helper'); const App = require('./app-base'); require('./climber'); const eventTpl = require('./event'); const Tpl = Dom.newTemplate(require('koru/html!./event-register')); const $ = Dom.current; const { Index, Add, Edit, Category: catTpl, Groups, Teams, AddClimber} = Tpl; let competitor; let sortField = 'name'; let asc = 1; let sortFunc; setSortFunc(); koru.onunload(module, ()=>{eventTpl.route.removeBase(Tpl)}); const base = eventTpl.route.addBase(module, Tpl); base.addTemplate(module, Add, { focus: true, defaultPage: true, }); base.addTemplate(module, Edit, { focus: true, data(page, pageRoute) { return Competitor.findById(pageRoute.append) || Route.abortPage(); } }); Tpl.$helpers({ closedClass() { Dom.setClass('closed', eventTpl.event.closed); }, competitors(each) { return { query: Competitor.query, compare: asc == 1 ? sortFunc : (a,b)=>sortFunc(b,a), compareKeys: sortFunc.compareKeys, }; }, sortOrder() { var parent = $.element.parentNode; var ths = parent.getElementsByTagName('th'); for(var i = 0; i < ths.length; ++i) { Dom.removeClass(ths[i], 'sort'); Dom.removeClass(ths[i], 'desc'); } var elm = parent.querySelector('[data-sort="'+sortField+'"]'); Dom.addClass(elm, 'sort'); asc === -1 && Dom.addClass(elm, 'desc'); }, }); Tpl.$events({ 'click tbody>tr'(event) { Dom.stopEvent(); Route.replacePath(Edit, {append: $.data(this)._id}); }, 'click [name=cancel]'(event) { Dom.stopEvent(); Route.replacePath(Tpl); }, 'menustart [name=selectTeamType]': TeamHelper.chooseTeamTypeEvent(teamTypeList), 'click th'(event) { Dom.stopEvent(); var sort = this.getAttribute('data-sort'); if (sortField === sort) asc = asc * -1; else { sortField = sort; asc = 1; } setSortFunc(); $.ctx.updateAllTags(); }, }); function teamTypeList(ctx) { return TeamType.where({_id: ctx.data.teamType_ids}).fetch(); } function setSortFunc() { switch (sortField) { case 'cat': return sortFunc = util.compareByField('category_ids'); case 'createdAt': return sortFunc = util.compareByField('createdAt'); case 'team': return sortFunc = TeamHelper.sortBy; default: return sortFunc = util.compareByField(sortField); } } Tpl.$extend({ onBaseEntry(page, pageRoute) { if (! eventTpl.event) Route.abortPage(); document.querySelector('#Event>div>.body').appendChild(Tpl.$autoRender(eventTpl.event)); }, onBaseExit(page, pageRoute) { Dom.removeId('Register'); }, $destroyed(ctx, elm) { competitor = null; }, }); Edit.$extend({ $created(ctx, elm) { addGroups(elm, ctx.data); addTeams(elm, ctx.data); }, }); Edit.$events({ 'submit': submit, 'click [name=delete]'(event) { Dom.stopEvent(); var doc = $.data(); Dom.Dialog.confirm({ data: doc, classes: 'warn', okay: 'Deregister', content: Tpl.ConfirmDelete, callback: function(confirmed) { if (confirmed) { doc.$remove(); Route.replacePath(Tpl); } }, }); }, }); Add.$extend({ $created(ctx) { if (eventTpl.event) { ctx.data = new Competitor({event_id: eventTpl.event._id}); } }, }); Add.$events({ 'click [name=cancel]'(event) { Dom.stopEvent(); Route.replacePath(Tpl); }, 'submit': submit, 'input [name=name]'(event) { var input = this; var value = input.value; if (value) value = value.trim(); if (value) { var form = event.currentTarget; var competitor = $.data(); competitor.$clearCache(); var competitors = Competitor.eventIndex.lookup({event_id: competitor.event_id}) || {}; var found = false; var completeList = Climber.search(value, 20, function (doc) { found = true; return ! (doc._id in competitors); }); if (completeList.length === 0) { if (found) { completeList = [{name: "Already registered"}]; } else { completeList = [{name: 'Add "' + value + '"', addNew: true}]; } } } Form.completeList({ input: this, completeList: completeList, callback(ret) { if (ret._id) { input.value = ret.name; addClimber(competitor, ret); addGroups(form, competitor); addTeams(form, competitor); } else if (ret.addNew) { addNew(form, value); } }, }); }, }); Tpl.Row.$helpers({ categories() { var frag = document.createDocumentFragment(); this.category_ids.forEach(id =>{ var abbr = document.createElement('abbr'); var cat = Category.findById(id); if (! cat) return; abbr.setAttribute("title", cat.name); abbr.textContent = cat.shortName; frag.appendChild(abbr); }); return frag; }, team: TeamHelper.teamTD, }); Tpl.Row.$extend({ $created(ctx) { ctx.autoUpdate({subject: ctx.data.climber}); }, }); AddClimber.$events({ 'submit': Form.submitFunc('AddClimber', function (doc) { Dom.Dialog.close(); var form = document.querySelector('#Register form.add'); var competitor = $.data(form); competitor.$clearCache(); addClimber(competitor, doc); addGroups(form, competitor); addTeams(form, competitor); }), 'click [name=cancel]'(event) { Dom.stopEvent(); Dom.Dialog.close(); document.querySelector('#Register [name=name].autoComplete').focus(); }, }); Groups.$events({ 'click [name=editClimber]'(event) { Dom.stopEvent(); Route.gotoPage(Dom.Climber.Edit, {modelId: $.ctx.data.climber._id}); }, }); Teams.$helpers({ teamTypes(each) { return { query: TeamType.where({_id: this.event.teamType_ids}), compare: util.compareByName, }; }, }); Teams.TeamType.$helpers({ teamName() { let competitor = Teams.$data(); let team = competitor.getTeam(this._id); Dom.setClass('none', ! team, $.element.parentNode); if (team) return team.name; return 'Select'; }, }); Teams.TeamType.$events({ 'menustart .select'(event) { Dom.stopEvent(); let ctx = $.ctx; let competitor = Teams.$data(); let list = Team.where('teamType_id', $.ctx.data._id).sort('name'); list = [{_id: null, name: Dom.h({i:'none'})}, ...list, {_id: '$new', name: Dom.h({i: 'add new team'})}]; SelectMenu.popup(this, { list, onSelect(elm) { let id = $.data(elm)._id; if (id === '$new') { let elm = Tpl.AddTeam.$autoRender( new Team({org_id: App.orgId, teamType_id: ctx.data._id})); Dialog.open(elm); Dom.ctx(elm).teamData = {competitor, ctx}; } else { competitor.setTeam(ctx.data._id, id); ctx.updateAllTags(); } return true; } }); }, }); Tpl.AddTeam.$events({ 'click [name=cancel]'(event) { Dom.stopEvent(); Dialog.close(); }, 'click [type=submit]': Form.submitFunc('AddTeam', { success(team) { let {competitor, ctx} = Dom.ctxById('AddTeam').teamData; competitor.setTeam(ctx.data._id, team._id); ctx.updateAllTags(); Dialog.close(); }, }), }); catTpl.$helpers({ selectedCategory() { Dom.setClass('none', ! this.category_id, $.element.parentNode); return this.category_id ? Category.findById(this.category_id).name : '---'; }, }); catTpl.$events({ 'menustart .select'(event) { Dom.stopEvent(); const ctx = $.ctx; const data = ctx.data; SelectMenu.popup(this, { list: data.groupList, onSelect(elm) { data.category_id = $.data(elm)._id; ctx.updateAllTags(); return true; } }); }, }); function submit(event) { Dom.stopEvent(); const competitor = $.data(); const ids = []; const form = event.currentTarget; competitor.number = form.querySelector('[name=number]') || undefined; var groups = form.getElementsByClassName('Category'); for(var i = 0; i < groups.length; ++i) { var row = $.data(groups[i]); if (row.category_id) ids.push(row.category_id); } competitor.category_ids = ids; if (Form.saveDoc(competitor, form)) { Route.replacePath(Add); } } function addNew(form, name) { Dom.Dialog.open(AddClimber.$autoRender(Climber.build({org_id: App.orgId, name: name})), {focus: '[name=dateOfBirth]'}); } function addGroups(form, competitor) { var climber = competitor.climber; var groupsElm = form.querySelector('.Groups'); Dom.remove(groupsElm); groupsElm = Groups.$autoRender(competitor); let list = groupsElm.querySelector('.categoryList'); Category.groupApplicable(climber, function (group, docs) { list.appendChild(catTpl.$autoRender({ groupName: group, groupList: [{_id: null, name: '---'}, ...docs], category_id: competitor.categoryIdForGroup(group), })); }); form.insertBefore(groupsElm, form.querySelector('.actions')); } function addTeams(form, competitor) { var climber = competitor.climber; var teamsElm = form.querySelector('.Teams'); Dom.remove(teamsElm); teamsElm = Teams.$autoRender(competitor); form.insertBefore(teamsElm, form.querySelector('.actions')); } function addClimber(competitor, climber) { competitor.climber_id = climber._id; competitor.team_ids = climber.team_ids; competitor.number = climber.number; } App.restrictAccess(Tpl); return Tpl; });
define((require, exports, module)=>{ const Dom = require('koru/dom'); const Route = require('koru/ui/route'); const util = require('koru/util'); const Category = require('models/category'); const Heat = require('models/heat'); const Result = require('models/result'); const ClimberCell = require('ui/climber-cell'); const EventHelper = require('ui/event-helper'); const SpeedEvent = require('ui/speed-event'); const eventTpl = require('./event'); const orig$ = Symbol(); const Tpl = Dom.newTemplate(require('koru/html!./event-category')); const $ = Dom.current; const {HeatHeader, Score, BoulderScore} = Tpl; const InvalidInput = Tpl.InvalidInput.$render(); let focusField = null; const createAttempts = (name, attempts, number, canInput)=>{ let elm; if (canInput) { elm = document.createElement('input'); elm.tabIndex = number+1; elm[orig$] = elm.value = attempts || ''; } else { elm = document.createElement('span'); if (attempts) elm.textContent = attempts; } elm.className = name; return elm; }; const updateResults = (ctx)=>{ let input, value, start, end; ctx.updateAllTags(); if (focusField !== null) { const elm = getFocusElm(); if (elm) { elm.focus(); if (input) { elm[orig$] = elm.value = value; elm.setSelectionRange(start, end); } else { elm.select(); } return; } } document.activeElement.blur(); }; const getFocusElm = ()=>{ return focusField && document.querySelector( '#' + focusField.id + ' td.score input[tabIndex="'+focusField.tabIndex+'"].'+focusField.name); }; const saveScore = (elm)=>{ if (elm.value === elm[orig$]) return true; const ctx = Dom.ctx(elm); const data = ctx.data; if (! data.result) return true; const heat = Tpl.$ctx(ctx).data.heat; if (Dom.hasClass(elm, 'score')) { const number = heat.scoreToNumber(elm.value, data.heat); if (number !== false) { data.result.setScore(data.heat, data.score = elm.value); return true; } Dom.addClass(elm, 'error'); elm.parentNode.insertBefore(InvalidInput, elm.nextSibling); return false; } const parent = elm.parentNode; let top = parent.querySelector('input.top'); const onTop = top === elm; top = top.value.trim(); const bonusElm = parent.querySelector('input.bonus'); let bonus = bonusElm.value.trim(); if (top === "-") top = "0"; if (bonus === "-") bonus = "0"; const tabIndex = +elm.getAttribute('tabIndex'); if (! top && ! bonus) { data.result.setBoulderScore(data.heat.number, tabIndex); return true; } if (top.match(/nc/i) || bonus.match(/nc/i)) { data.result.setBoulderScore(data.heat.number, tabIndex, "dnc"); return true; } top = +(top || 0); bonus = +(bonus || 0); const isNumber = ! (isNaN(top) || isNaN(bonus)); if (isNumber && top >=0 && bonus >= 0) { if (top && ! bonus) { parent.classList.add('incomplete'); parent.classList.remove('error'); return true; } if (! top || (bonus && bonus <= top)) { parent.classList.remove('error', 'incomplete'); data.result.setBoulderScore(data.heat.number, tabIndex, bonus, top); return true; } } parent.classList.add('error'); return isNumber; }; const setFocusField = (input)=>{ focusField = { id: Dom.getClosest(input, 'tr').id, tabIndex: +input.getAttribute('tabIndex'), name: input.className.replace(/ .*$/, ''), }; };; const nextField = (elm, direction)=>{ let tabIndex = +elm.getAttribute('tabIndex'); let name, row = elm.parentNode.parentNode; if (Dom.hasClass(row, 'BoulderScore')) { // row = current tr row = row.parentNode; name = Dom.hasClass(elm, 'top') ? 'bonus' : 'top'; if (direction > 0 && name === 'top') row = row.nextElementSibling; else if (direction < 0 && name === 'bonus') row = row.previousElementSibling; // if there is no next row, move to next column if (row === null) { elm = document.querySelector('.results tr:'+ (direction > 0 ? 'first' : 'last')+'-child>td.score input'+ '[tabIndex="'+(tabIndex+direction)+'"]'); // if there is no next column if (elm === null) return null; tabIndex = +elm.getAttribute('tabIndex'); // row = tr row = elm.parentNode.parentNode.parentNode; } } else { name = 'score'; // not invoked in boulder? row = direction > 0 ? row.nextElementSibling : row.previousElementSibling; if (row === null) { elm = document.querySelector('.results tr:'+ (direction > 0 ? 'first' : 'last')+'-child>td.score>input'+ ':not([tabIndex="'+tabIndex+'"])'); if (elm === null) return null; tabIndex = +elm.getAttribute('tabIndex'); row = elm.parentNode.parentNode; } } if (row) return {id: row.id, tabIndex, name}; }; const nextHorizField = (elm, direction)=>{ let tabIndex = +elm.getAttribute('tabIndex'); let cell = elm.parentNode; let name, row = cell.parentNode; if (Dom.hasClass(row, 'BoulderScore')) { // row = the current tr row = row.parentNode; // name = the next input field name = Dom.hasClass(elm, 'top') ? 'bonus' : 'top'; if (direction > 0 && name === 'top') { cell = cell.nextElementSibling; tabIndex += direction; } else if (direction < 0 && name === 'bonus') { cell = cell.previousElementSibling; tabIndex += direction; } // if at end of row if (cell === null) { tabIndex = (direction > 0) ? 1 : +row.children[1].lastElementChild.firstElementChild.getAttribute('tabIndex'); row = (direction > 0) ? row.nextElementSibling : row.previousElementSibling; // if at end of table if (row === null) return null; } } if (row) return {id: row.id, tabIndex, name}; }; const saveAndMove = (elm, which) => { Dom.stopEvent(); switch(which) { case 38: // up arrow / shift tab focusField = nextField(document.activeElement, -1); break; case 40: // down arrow / tab focusField = nextField(document.activeElement, 1); break; case 37: // left arrow focusField = nextHorizField(document.activeElement, -1); break; case 39: // right arrow focusField = nextHorizField(document.activeElement, 1); break; } saveScore(elm); const focusElm = getFocusElm() || elm; focusElm.focus(); focusElm.select(); }; eventTpl.route.addTemplate(module, Tpl); Tpl.$helpers({ classes() { return (this.showingResults ? "Category rank " : "Category start ") + this.heat.className() + ' ' + this.heat.type; }, heats() { return this.heat.list(); }, headers() { const frag = document.createDocumentFragment(); this.heat.headers((number, name) => { frag.appendChild(HeatHeader.$render({heat: number, name: name})); }); return frag; }, results() { const frag = document.createDocumentFragment(); const results = Result.query.withIndex(Result.eventCatIndex, { event_id: eventTpl.event._id, category_id: $.data().category._id, }).fetch(); const {heat} = this; if (this.showingResults) heat.sort(results); else heat.sortByStartOrder(results); let prev, row, rank = 0; const compareResults = heat.compareResults(); for(let i = 0; i < results.length; ++i, prev = row) { row = results[i]; if (! prev || compareResults(prev, row) !== 0) rank = i + 1; row.rank = rank; frag.appendChild(Tpl.Result.$render(row)); } return frag; }, }); Tpl.$extend({ onEntry: (page, pageRoute)=>{ if (! eventTpl.event) Route.abortPage(); const params = Route.searchParams(pageRoute); const category = Category.findById(pageRoute.append); const {route} = page; const showingResults = params.type === 'results'; const heatNumber = +(params.heat || -1); if (! showingResults && heatNumber == -1) { return Route.abortPage(page, { eventId: eventTpl.event._id, append: pageRoute.append, search: '?type=results&heat=-1'}); } const data = { showingResults, event_id: eventTpl.event._id, category, heatNumber}; Dom('#Event .body').appendChild( category.type === 'S' ? SpeedEvent.$autoRender(data) : page.$autoRender(data) ); Dom('.Category [name=selectHeat]').focus(); }, onExit: ()=>{ Dom.removeChildren(Dom('#Event .body')); }, $created(ctx, elm) { const {data} = ctx; let {showingResults} = data; util.merge(data, { heat: new Heat(data.heatNumber, eventTpl.event.heats[data.category._id]), get selectHeat() {return this.heat.number}, set selectHeat(value) {return this.heat.number = value}, get showingResults() {return showingResults}, set showingResults(value) { showingResults = value; if (showingResults) focusField = null; this.canInput = ! (value || eventTpl.event.closed) && Dom.hasClass(document.body, 'jAccess'); return value; }, }); data.showingResults = showingResults; // set canInput ctx.autoUpdate({subject: data.category}); ctx.onDestroy(Result.onChange(({doc}) =>{ if (doc.event_id !== eventTpl.event._id || doc.category_id !== ctx.data.category._id) return; updateResults(ctx); })); }, $destroyed(ctx, elm) { focusField = null; }, }); EventHelper.extendResultTemplate(Tpl); Tpl.$events({ 'change td.score input'(event) { if (! saveScore(this)) { Dom.stopEvent(); (getFocusElm()||this).focus(); return; } }, 'focus td.score input'(event) { setFocusField(this); }, 'keydown td.score input'(event) { const elm = event.target; const {which} = event; // arrow keys if (which >= 37 && which <= 40) { saveAndMove(elm, which); return; } switch(which) { case 27: // escape focusField = null; if (Dom.hasClass(this, 'score')) { elm.value = elm[orig$]; Dom.remove(this.parentNode.querySelector('.errorMsg')); } else { Dom.ctx(this).updateAllTags(); } document.activeElement.blur(); break; case 13: // return Dom.stopEvent(); if (saveScore(elm)) { focusField = null; document.activeElement.blur(); } break; case 9: { // tab saveAndMove(elm, event.shiftKey ? 38 : 40); break; } } }, 'pointerdown td.score'(event) { const elm = event.target; if (elm === document.activeElement || ! Dom.hasClass(document.body, 'jAccess')) return; Dom.stopEvent(); let input = elm.tagName === 'INPUT' ? elm : elm.querySelector('input') || this.querySelector('input'); if (input != null) { input.focus(); input.select(); return; } const ctx = $.ctx; const data = ctx.data; const scoreData = $.data(this); let heat = scoreData.heat; if (typeof heat === 'object') heat = heat.number; if (heat < 1) return; input = document.activeElement; if (Dom.hasClass(input, 'score')) { if (! saveScore(input)) return; } if (data.showingResults) { data.showingResults = false; data.selectHeat = heat === 99 ? data.heat.total : heat; } updateResults(ctx); }, }); HeatHeader.$helpers({ heatClass() { Dom.addClass($.element, this.heat > 0 ? (this.name === 'Result' ? 'problem' : 'score') : 'other'); }, }); Tpl.Result.$extend({ $created(ctx) { ctx.autoUpdate({subject: ctx.data.climber}); }, }); Tpl.Result.$helpers({ scores() { const frag = document.createDocumentFragment(); const parentCtx = Dom.ctx($.element.parentNode); const result = parentCtx.data; const scores = result.scores; const heat = $.ctx.parentCtx.data.heat; const {number} = heat; const boulder = heat.type === 'B'; let canInput = parentCtx.parentCtx.data.canInput; const renderScore = (i, canInput, qr)=>{ frag.appendChild(Score.$render( qr ? { result: result, heat: -2, score: scores[i] == null && heat.total !== heat.rankIndex ? '' : heat.numberToScore(Math.pow(result.rankMult, 1/i), -2, result.event.ruleVersion) } : { result: result, canInput: canInput, heat: i, score: heat.numberToScore(scores[i], i, result.event.ruleVersion), rank: scores[i] == null ? '' : result['rank'+i]} )); }; if (boulder && number >= 0) { frag.appendChild(BoulderScore.$render({result, heat, canInput})); } canInput = canInput && ! boulder; if (heat.number <= heat.rankIndex) { if (heat.number >= 0) { renderScore(heat.number, canInput); } else { frag.appendChild(Score.$render({result: result, heat: -2, score: result.rank})); for(let i = heat.total; i > 0; --i) { if (heat.rankIndex === i) renderScore(i, null, -2); renderScore(i); } } } else { if (heat.type === 'L' && heat.isFinalRound()) frag.appendChild(Score.$render({result: result, canInput: canInput, heat: 99, score: result.displayTimeTaken()})); renderScore(heat.number, canInput); renderScore(heat.number - 1, null, heat.rankIndex === heat.number - 1); } return frag; }, }); Score.$helpers({ rank() { if (! this.rank) return; const elm = document.createElement('i'); elm.textContent = this.rank; return elm; }, score() { let elm; if (this.canInput) { elm = document.createElement('input'); elm.setAttribute('placeholder', this.heat === 99 ? "m:ss" : "n+"); elm.tabIndex = this.heat; elm.className = 'score'; if (this.score != null) elm[orig$] = elm.value = this.score.toString(); } else { const {score} = this; const parts = typeof score === 'number' ? null : this.score.split(/([TZA]+)(?!o)/); if (parts === null || parts.length < 4) { elm = document.createElement('span'); elm.textContent = this.score; } else { elm = Dom.h({class: 'tza-score', span: parts.map(p => /^[TZA]+$/.test(p) ? {b: p} : {span: p})}); } } return elm; }, heatClass() { Dom.addClass($.element, 'heat' + this.heat); }, }); BoulderScore.$helpers({ problems() { const len = this.heat.problems; const problems = this.result.problems ? this.result.problems[this.heat.number - 1] || [] : []; const canInput = this.canInput; const frag = document.createDocumentFragment(); for(let i = 0; i < len; ++i) { const prob = problems[i]; const elm = document.createElement('div'); if (prob === -1) { elm.className = "dnc"; elm.appendChild(createAttempts('top', "nc", i, canInput)); elm.appendChild(createAttempts('bonus', "", i, canInput)); } else if (prob == null) { elm.appendChild(createAttempts('top', "", i, canInput)); elm.appendChild(createAttempts('bonus', "", i, canInput)); } else { if (prob > 0) elm.className = prob >= 100 ? 'top' : 'bonus'; else elm.className = "ns"; elm.appendChild(createAttempts('top', Math.floor(prob / 100), i, canInput)); elm.appendChild(createAttempts('bonus', (prob % 100) || "-", i, canInput)); } frag.appendChild(elm); } return frag; }, }); module.onUnload(() => {eventTpl.route.removeTemplate(Tpl)}); return Tpl; });