示例#1
0
    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});
    };
示例#2
0
 ...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)});
   }
 })
示例#3
0
      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);
      });
示例#4
0
 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');
 });
示例#5
0
 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'}}));
   }
 };
示例#6
0
 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');
 });
示例#7
0
 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;
 });
示例#8
0
 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;
   }
 };
示例#9
0
    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');
      }
    },
示例#10
0
文件: layout.js 项目: jacott/koru
 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()});`},
         ]);
       }
     },
   });
 };
示例#11
0
  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;
  }
示例#12
0
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');
      });
    },
  });
});
示例#13
0
  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});
  };
示例#14
0
 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));
   }
 };
示例#15
0
 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;
 };
示例#16
0
  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;
      }
    }
  };
示例#17
0
文件: layout.js 项目: jacott/koru
 ].forEach(row => {
   frag.appendChild(Dom.h({
     class: row[0], href: '/'+row[0], tabindex: "0", a: {span: row[1]},
   }));
 });
示例#18
0
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;
});
示例#19
0
 FONT_SIZE_LIST.forEach(row => {
   row[1] = Dom.h({font: row[1], $size: row[0]});
 });
示例#20
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;
});
示例#21
0
文件: main.js 项目: jacott/koru
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;
});
示例#22
0
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;
});
示例#23
0
    }).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)}
        ],
      }));
    });
示例#24
0
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;
});
示例#25
0
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;
});
示例#26
0
 ...Object.keys(configMap).sort().map(key => Dom.h({
   class: 'jsdoc-config-item',
   tr: [{td: key}, {td: configMap[key]}]
 }))
示例#27
0
 func => func && Dom.h({a: func.$name, $href: '#'+func.id})
示例#28
0
 const noContent = (tag)=> opts =>{
   const attrs = {[tag]: ''};
   for (let attr in opts)
     attrs['$'+attr] = opts[attr];
   return Dom.h(attrs);
 };
示例#29
0
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;
});
示例#30
0
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;
});