<commit_before>import {
  All, Each, Flush, Column, X, Y, Row, Middle, End,
  Group, AxisRole, LegendRole,
  RowHeader, RowFooter, RowTitle,
  ColHeader, ColFooter, ColTitle
} from '../constants';
import {tempBounds} from './util';
import {isObject} from 'vega-util';

function gridLayoutGroups(group) {
  var groups = group.items,
      n = groups.length,
      i = 0, mark, items;

  var views = {
    marks:      [],
    rowheaders: [],
    rowfooters: [],
    colheaders: [],
    colfooters: [],
    rowtitle: null,
    coltitle: null
  };

  // layout axes, gather legends, collect bounds
  for (; i<n; ++i) {
    mark = groups[i];
    items = mark.items;
    if (mark.marktype === Group) {
      switch (mark.role) {
        case AxisRole:
        case LegendRole:
          break;
        case RowHeader: views.rowheaders.push(...items); break;
        case RowFooter: views.rowfooters.push(...items); break;
        case ColHeader: views.colheaders.push(...items); break;
        case ColFooter: views.colfooters.push(...items); break;
        case RowTitle:  views.rowtitle = items[0]; break;
        case ColTitle:  views.coltitle = items[0]; break;
        default:        views.marks.push(...items);
      }
    }
  }

  return views;
}

function bboxFlush(item) {
  return {x1: 0, y1: 0, x2: item.width || 0, y2: item.height || 0};
}

function bboxFull(item) {
  var b = item.bounds.clone();
  return b.empty()
    ? b.set(0, 0, 0, 0)
    : b.translate(-(item.x||0), -(item.y||0));
}

function get(opt, key, d) {
  var v = isObject(opt) ? opt[key] : opt;
  return v != null ? v : (d !== undefined ? d : 0);
}

function offsetValue(v) {
  return v < 0 ? Math.ceil(-v) : 0;
}

export function gridLayout(view, groups, opt) {
  var dirty = !opt.nodirty,
      bbox = opt.bounds === Flush ? bboxFlush : bboxFull,
      bounds = tempBounds.set(0, 0, 0, 0),
      alignCol = get(opt.align, Column),
      alignRow = get(opt.align, Row),
      padCol = get(opt.padding, Column),
      padRow = get(opt.padding, Row),
      ncols = opt.columns || groups.length,
      nrows = ncols < 0 ? 1 : Math.ceil(groups.length / ncols),
      boxes = [],
      xOffset = [], xExtent = [], xMax = 0,
      yOffset = [], yExtent = [], yMax = 0,
      n = groups.length,
      m, i, c, r, b, g, px, py, x, y, offset;

  for (i=0; i<ncols; ++i) xExtent[i] = 0;
  for (i=0; i<nrows; ++i) yExtent[i] = 0;

  // determine offsets for each group
  for (i=0; i<n; ++i) {
    b = boxes[i] = bbox(groups[i]);
    c = i % ncols;
    r = ~~(i / ncols);
    px = Math.ceil(bbox(groups[i]).x2);
    py = Math.ceil(bbox(groups[i]).y2);
    xMax = Math.max(xMax, px);
    yMax = Math.max(yMax, py);
    xExtent[c] = Math.max(xExtent[c], px);
    yExtent[r] = Math.max(yExtent[r], py);
    xOffset.push(padCol + offsetValue(b.x1));
    yOffset.push(padRow + offsetValue(b.y1));
    if (dirty) view.dirty(groups[i]);
  }

  // set initial alignment offsets
  for (i=0; i<n; ++i) {
    if (i % ncols === 0) xOffset[i] = 0;
    if (i < ncols) yOffset[i] = 0;
  }

  // enforce column alignment constraints
  if (alignCol === Each) {
    for (c=1; c<ncols; ++c) {
      for (offset=0, i=c; i<n; i += ncols) {
        if (offset < xOffset[i]) offset = xOffset[i];
      }
      for (i=c; i<n; i += ncols) {
        xOffset[i] = offset + xExtent[c-1];
      }
    }
  } else if (alignCol === All) {
    for (offset=0, i=0; i<n; ++i) {
      if (i % ncols && offset < xOffset[i]) offset = xOffset[i];
    }
    for (i=0; i<n; ++i) {
      if (i % ncols) xOffset[i] = offset + xMax;
    }
  } else {
    for (alignCol=false, c=1; c<ncols; ++c) {
      for (i=c; i<n; i += ncols) {
        xOffset[i] += xExtent[c-1];
      }
    }
  }

  // enforce row alignment constraints
  if (alignRow === Each) {
    for (r=1; r<nrows; ++r) {
      for (offset=0, i=r*ncols, m=i+ncols; i<m; ++i) {
        if (offset < yOffset[i]) offset = yOffset[i];
      }
      for (i=r*ncols; i<m; ++i) {
        yOffset[i] = offset + yExtent[r-1];
      }
    }
  } else if (alignRow === All) {
    for (offset=0, i=ncols; i<n; ++i) {
      if (offset < yOffset[i]) offset = yOffset[i];
    }
    for (i=ncols; i<n; ++i) {
      yOffset[i] = offset + yMax;
    }
  } else {
    for (alignRow=false, r=1; r<nrows; ++r) {
      for (i=r*ncols, m=i+ncols; i<m; ++i) {
        yOffset[i] += yExtent[r-1];
      }
    }
  }

  // perform horizontal grid layout
  for (x=0, i=0; i<n; ++i) {
    g = groups[i];
    px = g.x || 0;
    g.x = (x = xOffset[i] + (i % ncols ? x : 0));
    g.bounds.translate(x - px, 0);
  }

  // perform vertical grid layout
  for (c=0; c<ncols; ++c) {
    for (y=0, i=c; i<n; i += ncols) {
      g = groups[i];
      py = g.y || 0;
      g.y = (y += yOffset[i]);
      g.bounds.translate(0, y - py);
    }
  }

  // perform horizontal centering
  // TODO: remove nrows > 1 constraint?
  if (alignCol && get(opt.center, Column) && nrows > 1) {
    for (i=0; i<n; ++i) {
      g = groups[i];
      b = alignCol === All ? xMax : xExtent[i % ncols];
      x = b - boxes[i].x2;
      if (x > 0) {
        g.x += (px = x / 2);
        g.bounds.translate(px, 0);
      }
    }
  }

  // perform vertical centering
  // TODO: remove ncols !== 1 constraint?
  if (alignRow && get(opt.center, Row) && ncols !== 1) {
    for (i=0; i<n; ++i) {
      g = groups[i];
      b = alignRow === All ? yMax : yExtent[~~(i / ncols)];
      y = b - boxes[i].y2;
      if (y > 0) {
        g.y += (py = y / 2);
        g.bounds.translate(0, py);
      }
    }
  }

  // position grid relative to anchor
  for (i=0; i<n; ++i) bounds.union(boxes[i]);
  x = get(opt.anchor, X);
  y = get(opt.anchor, Y);
  switch (get(opt.anchor, Column)) {
    case End:    x -= bounds.width(); break;
    case Middle: x -= bounds.width() / 2;
  }
  switch (get(opt.anchor, Row)) {
    case End:    y -= bounds.height(); break;
    case Middle: y -= bounds.height() / 2;
  }
  x = Math.round(x);
  y = Math.round(y);

  // update mark positions, bounds, dirty
  bounds.clear();
  for (i=0; i<n; ++i) {
    groups[i].mark.bounds.clear();
  }
  for (i=0; i<n; ++i) {
    g = groups[i];
    g.x += x;
    g.y += y;
    g.bounds.translate(x, y);
    bounds.union(g.mark.bounds.union(g.bounds));
    if (dirty) view.dirty(g);
  }

  return bounds;
}

export function trellisLayout(view, group, opt) {
  var views = gridLayoutGroups(group),
      groups = views.marks,
      bbox = opt.bounds === Flush ? boundFlush : boundFull,
      off = opt.offset,
      ncols = opt.columns || groups.length,
      nrows = ncols < 0 ? 1 : Math.ceil(groups.length / ncols),
      cells = nrows * ncols,
      x, y, band, offset;

  // -- initial grid layout
  const bounds = gridLayout(view, groups, opt);

  // -- layout grid headers and footers --

  // perform row header layout
  if (views.rowheaders) {
    band = get(opt.headerBand, Row, null);
    x = layoutHeaders(view, views.rowheaders, groups, ncols, nrows, -get(off, 'rowHeader'), min, 0, bbox, 'x1', 0, ncols, 1, band);
  }

  // perform column header layout
  if (views.colheaders) {
    band = get(opt.headerBand, Column, null);
    y = layoutHeaders(view, views.colheaders, groups, ncols, ncols, -get(off, 'columnHeader'), min, 1, bbox, 'y1', 0, 1, ncols, band);
  }

  // perform row footer layout
  if (views.rowfooters) {
    band = get(opt.footerBand, Row, null);
    layoutHeaders(view, views.rowfooters, groups, ncols, nrows,  get(off, 'rowFooter'), max, 0, bbox, 'x2', ncols-1, ncols, 1, band);
  }

  // perform column footer layout
  if (views.colfooters) {
    band = get(opt.footerBand, Column, null);
    layoutHeaders(view, views.colfooters, groups, ncols, ncols,  get(off, 'columnFooter'), max, 1, bbox, 'y2', cells-ncols, 1, ncols, band);
  }

  // perform row title layout
  if (views.rowtitle) {
    offset = x - get(off, 'rowTitle');
    band = get(opt.titleBand, Row, 0.5);
    layoutTitle(view, views.rowtitle, offset, 0, bounds, band);
  }

  // perform column title layout
  if (views.coltitle) {
    offset = y - get(off, 'columnTitle');
    band = get(opt.titleBand, Column, 0.5);
    layoutTitle(view, views.coltitle, offset, 1, bounds, band);
  }
}

function boundFlush(item, field) {
  return field === 'x1' ? (item.x || 0)
    : field === 'y1' ? (item.y || 0)
    : field === 'x2' ? (item.x || 0) + (item.width || 0)
    : field === 'y2' ? (item.y || 0) + (item.height || 0)
    : undefined;
}

function boundFull(item, field) {
  return item.bounds[field];
}

// aggregation functions for grid margin determination
function min(a, b) { return Math.floor(Math.min(a, b)); }
function max(a, b) { return Math.ceil(Math.max(a, b)); }

function layoutHeaders(view, headers, groups, ncols, limit, offset, agg, isX, bound, bf, start, stride, back, band) {
  var n = groups.length,
      init = 0,
      edge = 0,
      i, j, k, m, b, h, g, x, y;

  // if no groups, early exit and return 0
  if (!n) return init;

  // compute margin
  for (i=start; i<n; i+=stride) {
    if (groups[i]) init = agg(init, bound(groups[i], bf));
  }

  // if no headers, return margin calculation
  if (!headers.length) return init;

  // check if number of headers exceeds number of rows or columns
  if (headers.length > limit) {
    view.warn('Grid headers exceed limit: ' + limit);
    headers = headers.slice(0, limit);
  }

  // apply offset
  init += offset;

  // clear mark bounds for all headers
  for (j=0, m=headers.length; j<m; ++j) {
    view.dirty(headers[j]);
    headers[j].mark.bounds.clear();
  }

  // layout each header
  for (i=start, j=0, m=headers.length; j<m; ++j, i+=stride) {
    h = headers[j];
    b = h.mark.bounds;

    // search for nearest group to align to
    // necessary if table has empty cells
    for (k=i; k >= 0 && (g = groups[k]) == null; k-=back);

    // assign coordinates and update bounds
    if (isX) {
      x = band == null ? g.x : Math.round(g.bounds.x1 + band * g.bounds.width());
      y = init;
    } else {
      x = init;
      y = band == null ? g.y : Math.round(g.bounds.y1 + band * g.bounds.height());
    }
    b.union(h.bounds.translate(x - (h.x || 0), y - (h.y || 0)));
    h.x = x;
    h.y = y;
    view.dirty(h);

    // update current edge of layout bounds
    edge = agg(edge, b[bf]);
  }

  return edge;
}

function layoutTitle(view, g, offset, isX, bounds, band) {
  if (!g) return;
  view.dirty(g);

  // compute title coordinates
  var x = offset, y = offset;
  isX
    ? (x = Math.round(bounds.x1 + band * bounds.width()))
    : (y = Math.round(bounds.y1 + band * bounds.height()));

  // assign coordinates and update bounds
  g.bounds.translate(x - (g.x || 0), y - (g.y || 0));
  g.mark.bounds.clear().union(g.bounds);
  g.x = x;
  g.y = y;

  // queue title for redraw
  view.dirty(g);
}
<commit_msg>Fix grid layout, optimize code.<commit_after>import {
  All, Each, Flush, Column, X, Y, Row, Middle, End,
  Group, AxisRole, LegendRole,
  RowHeader, RowFooter, RowTitle,
  ColHeader, ColFooter, ColTitle
} from '../constants';
import {tempBounds} from './util';
import {Bounds} from 'vega-scenegraph';
import {isObject} from 'vega-util';

function gridLayoutGroups(group) {
  var groups = group.items,
      n = groups.length,
      i = 0, mark, items;

  var views = {
    marks:      [],
    rowheaders: [],
    rowfooters: [],
    colheaders: [],
    colfooters: [],
    rowtitle: null,
    coltitle: null
  };

  // layout axes, gather legends, collect bounds
  for (; i<n; ++i) {
    mark = groups[i];
    items = mark.items;
    if (mark.marktype === Group) {
      switch (mark.role) {
        case AxisRole:
        case LegendRole:
          break;
        case RowHeader: views.rowheaders.push(...items); break;
        case RowFooter: views.rowfooters.push(...items); break;
        case ColHeader: views.colheaders.push(...items); break;
        case ColFooter: views.colfooters.push(...items); break;
        case RowTitle:  views.rowtitle = items[0]; break;
        case ColTitle:  views.coltitle = items[0]; break;
        default:        views.marks.push(...items);
      }
    }
  }

  return views;
}

function bboxFlush(item) {
  return new Bounds().set(0, 0, item.width || 0, item.height || 0);
}

function bboxFull(item) {
  var b = item.bounds.clone();
  return b.empty()
    ? b.set(0, 0, 0, 0)
    : b.translate(-(item.x || 0), -(item.y || 0));
}

function get(opt, key, d) {
  var v = isObject(opt) ? opt[key] : opt;
  return v != null ? v : (d !== undefined ? d : 0);
}

function offsetValue(v) {
  return v < 0 ? Math.ceil(-v) : 0;
}

export function gridLayout(view, groups, opt) {
  var dirty = !opt.nodirty,
      bbox = opt.bounds === Flush ? bboxFlush : bboxFull,
      bounds = tempBounds.set(0, 0, 0, 0),
      alignCol = get(opt.align, Column),
      alignRow = get(opt.align, Row),
      padCol = get(opt.padding, Column),
      padRow = get(opt.padding, Row),
      ncols = opt.columns || groups.length,
      nrows = ncols < 0 ? 1 : Math.ceil(groups.length / ncols),
      n = groups.length,
      xOffset = Array(n), xExtent = Array(ncols), xMax = 0,
      yOffset = Array(n), yExtent = Array(nrows), yMax = 0,
      dx = Array(n), dy = Array(n), boxes = Array(n),
      m, i, c, r, b, g, px, py, x, y, offset;

  for (i=0; i<ncols; ++i) xExtent[i] = 0;
  for (i=0; i<nrows; ++i) yExtent[i] = 0;

  // determine offsets for each group
  for (i=0; i<n; ++i) {
    g = groups[i];
    b = boxes[i] = bbox(g);
    g.x = g.x || 0; dx[i] = 0;
    g.y = g.y || 0; dy[i] = 0;
    c = i % ncols;
    r = ~~(i / ncols);
    xMax = Math.max(xMax, px = Math.ceil(b.x2));
    yMax = Math.max(yMax, py = Math.ceil(b.y2));
    xExtent[c] = Math.max(xExtent[c], px);
    yExtent[r] = Math.max(yExtent[r], py);
    xOffset[i] = padCol + offsetValue(b.x1);
    yOffset[i] = padRow + offsetValue(b.y1);
    if (dirty) view.dirty(groups[i]);
  }

  // set initial alignment offsets
  for (i=0; i<n; ++i) {
    if (i % ncols === 0) xOffset[i] = 0;
    if (i < ncols) yOffset[i] = 0;
  }

  // enforce column alignment constraints
  if (alignCol === Each) {
    for (c=1; c<ncols; ++c) {
      for (offset=0, i=c; i<n; i += ncols) {
        if (offset < xOffset[i]) offset = xOffset[i];
      }
      for (i=c; i<n; i += ncols) {
        xOffset[i] = offset + xExtent[c-1];
      }
    }
  } else if (alignCol === All) {
    for (offset=0, i=0; i<n; ++i) {
      if (i % ncols && offset < xOffset[i]) offset = xOffset[i];
    }
    for (i=0; i<n; ++i) {
      if (i % ncols) xOffset[i] = offset + xMax;
    }
  } else {
    for (alignCol=false, c=1; c<ncols; ++c) {
      for (i=c; i<n; i += ncols) {
        xOffset[i] += xExtent[c-1];
      }
    }
  }

  // enforce row alignment constraints
  if (alignRow === Each) {
    for (r=1; r<nrows; ++r) {
      for (offset=0, i=r*ncols, m=i+ncols; i<m; ++i) {
        if (offset < yOffset[i]) offset = yOffset[i];
      }
      for (i=r*ncols; i<m; ++i) {
        yOffset[i] = offset + yExtent[r-1];
      }
    }
  } else if (alignRow === All) {
    for (offset=0, i=ncols; i<n; ++i) {
      if (offset < yOffset[i]) offset = yOffset[i];
    }
    for (i=ncols; i<n; ++i) {
      yOffset[i] = offset + yMax;
    }
  } else {
    for (alignRow=false, r=1; r<nrows; ++r) {
      for (i=r*ncols, m=i+ncols; i<m; ++i) {
        yOffset[i] += yExtent[r-1];
      }
    }
  }

  // perform horizontal grid layout
  for (x=0, i=0; i<n; ++i) {
    x = xOffset[i] + (i % ncols ? x : 0);
    dx[i] += x - groups[i].x;
  }

  // perform vertical grid layout
  for (c=0; c<ncols; ++c) {
    for (y=0, i=c; i<n; i += ncols) {
      y += yOffset[i];
      dy[i] += y - groups[i].y;
    }
  }

  // perform horizontal centering
  if (alignCol && get(opt.center, Column) && nrows > 1) {
    for (i=0; i<n; ++i) {
      b = alignCol === All ? xMax : xExtent[i % ncols];
      x = b - boxes[i].x2 - groups[i].x - dx[i];
      if (x > 0) dx[i] += x / 2;
    }
  }

  // perform vertical centering
  if (alignRow && get(opt.center, Row) && ncols !== 1) {
    for (i=0; i<n; ++i) {
      b = alignRow === All ? yMax : yExtent[~~(i / ncols)];
      y = b - boxes[i].y2 - groups[i].y - dy[i];
      if (y > 0) dy[i] += y / 2;
    }
  }

  // position grid relative to anchor
  for (i=0; i<n; ++i) {
    bounds.union(boxes[i].translate(dx[i], dy[i]));
  }
  x = get(opt.anchor, X);
  y = get(opt.anchor, Y);
  switch (get(opt.anchor, Column)) {
    case End:    x -= bounds.width(); break;
    case Middle: x -= bounds.width() / 2;
  }
  switch (get(opt.anchor, Row)) {
    case End:    y -= bounds.height(); break;
    case Middle: y -= bounds.height() / 2;
  }
  x = Math.round(x);
  y = Math.round(y);

  // update mark positions, bounds, dirty
  bounds.clear();
  for (i=0; i<n; ++i) {
    groups[i].mark.bounds.clear();
  }
  for (i=0; i<n; ++i) {
    g = groups[i];
    g.x += (dx[i] += x);
    g.y += (dy[i] += y);
    bounds.union(g.mark.bounds.union(g.bounds.translate(dx[i], dy[i])));
    if (dirty) view.dirty(g);
  }

  return bounds;
}

export function trellisLayout(view, group, opt) {
  var views = gridLayoutGroups(group),
      groups = views.marks,
      bbox = opt.bounds === Flush ? boundFlush : boundFull,
      off = opt.offset,
      ncols = opt.columns || groups.length,
      nrows = ncols < 0 ? 1 : Math.ceil(groups.length / ncols),
      cells = nrows * ncols,
      x, y, band, offset;

  // -- initial grid layout
  const bounds = gridLayout(view, groups, opt);

  // -- layout grid headers and footers --

  // perform row header layout
  if (views.rowheaders) {
    band = get(opt.headerBand, Row, null);
    x = layoutHeaders(view, views.rowheaders, groups, ncols, nrows, -get(off, 'rowHeader'), min, 0, bbox, 'x1', 0, ncols, 1, band);
  }

  // perform column header layout
  if (views.colheaders) {
    band = get(opt.headerBand, Column, null);
    y = layoutHeaders(view, views.colheaders, groups, ncols, ncols, -get(off, 'columnHeader'), min, 1, bbox, 'y1', 0, 1, ncols, band);
  }

  // perform row footer layout
  if (views.rowfooters) {
    band = get(opt.footerBand, Row, null);
    layoutHeaders(view, views.rowfooters, groups, ncols, nrows,  get(off, 'rowFooter'), max, 0, bbox, 'x2', ncols-1, ncols, 1, band);
  }

  // perform column footer layout
  if (views.colfooters) {
    band = get(opt.footerBand, Column, null);
    layoutHeaders(view, views.colfooters, groups, ncols, ncols,  get(off, 'columnFooter'), max, 1, bbox, 'y2', cells-ncols, 1, ncols, band);
  }

  // perform row title layout
  if (views.rowtitle) {
    offset = x - get(off, 'rowTitle');
    band = get(opt.titleBand, Row, 0.5);
    layoutTitle(view, views.rowtitle, offset, 0, bounds, band);
  }

  // perform column title layout
  if (views.coltitle) {
    offset = y - get(off, 'columnTitle');
    band = get(opt.titleBand, Column, 0.5);
    layoutTitle(view, views.coltitle, offset, 1, bounds, band);
  }
}

function boundFlush(item, field) {
  return field === 'x1' ? (item.x || 0)
    : field === 'y1' ? (item.y || 0)
    : field === 'x2' ? (item.x || 0) + (item.width || 0)
    : field === 'y2' ? (item.y || 0) + (item.height || 0)
    : undefined;
}

function boundFull(item, field) {
  return item.bounds[field];
}

// aggregation functions for grid margin determination
function min(a, b) { return Math.floor(Math.min(a, b)); }
function max(a, b) { return Math.ceil(Math.max(a, b)); }

function layoutHeaders(view, headers, groups, ncols, limit, offset, agg, isX, bound, bf, start, stride, back, band) {
  var n = groups.length,
      init = 0,
      edge = 0,
      i, j, k, m, b, h, g, x, y;

  // if no groups, early exit and return 0
  if (!n) return init;

  // compute margin
  for (i=start; i<n; i+=stride) {
    if (groups[i]) init = agg(init, bound(groups[i], bf));
  }

  // if no headers, return margin calculation
  if (!headers.length) return init;

  // check if number of headers exceeds number of rows or columns
  if (headers.length > limit) {
    view.warn('Grid headers exceed limit: ' + limit);
    headers = headers.slice(0, limit);
  }

  // apply offset
  init += offset;

  // clear mark bounds for all headers
  for (j=0, m=headers.length; j<m; ++j) {
    view.dirty(headers[j]);
    headers[j].mark.bounds.clear();
  }

  // layout each header
  for (i=start, j=0, m=headers.length; j<m; ++j, i+=stride) {
    h = headers[j];
    b = h.mark.bounds;

    // search for nearest group to align to
    // necessary if table has empty cells
    for (k=i; k >= 0 && (g = groups[k]) == null; k-=back);

    // assign coordinates and update bounds
    if (isX) {
      x = band == null ? g.x : Math.round(g.bounds.x1 + band * g.bounds.width());
      y = init;
    } else {
      x = init;
      y = band == null ? g.y : Math.round(g.bounds.y1 + band * g.bounds.height());
    }
    b.union(h.bounds.translate(x - (h.x || 0), y - (h.y || 0)));
    h.x = x;
    h.y = y;
    view.dirty(h);

    // update current edge of layout bounds
    edge = agg(edge, b[bf]);
  }

  return edge;
}

function layoutTitle(view, g, offset, isX, bounds, band) {
  if (!g) return;
  view.dirty(g);

  // compute title coordinates
  var x = offset, y = offset;
  isX
    ? (x = Math.round(bounds.x1 + band * bounds.width()))
    : (y = Math.round(bounds.y1 + band * bounds.height()));

  // assign coordinates and update bounds
  g.bounds.translate(x - (g.x || 0), y - (g.y || 0));
  g.mark.bounds.clear().union(g.bounds);
  g.x = x;
  g.y = y;

  // queue title for redraw
  view.dirty(g);
}
<|endoftext|>


<commit_before>/**
 * 上传图片输入框
 */

import React, { PureComponent, Component } from 'react';
import Notify from 'notify';
import toArray from 'lodash/toArray';
import forEach from 'lodash/forEach';
import isPromise from 'utils/isPromise';
import { I18nReceiver as Receiver } from 'i18n';
import { Upload as I18nDefault } from 'i18n/default';

import { formatFileSize, base64ToArrayBuffer } from '../utils';
import fileType from '../utils/file-type';
import uploadLocalImage from './UploadLocal';
import { DEFAULT_ACCEPT } from '../constants';

export default class FileInput extends (PureComponent || Component) {
  constructor(props) {
    super(props);

    let { accept, type } = props;
    // 根据type设置accept默认值
    if (!accept) {
      accept = DEFAULT_ACCEPT[type];
    }

    this.state = {
      accept
    };
  }

  onFileChange = localFiles => {
    const { onChange } = this.props;
    if (onChange) {
      onChange(localFiles);
    } else {
      uploadLocalImage(this.props, {
        localFiles
      });
    }
  };

  processFiles = i18n => evt => {
    let files = toArray(evt.target.files);
    const { filterFiles, onError } = this.props;

    let filterResult = filterFiles(files);
    const iterator = this.iteratorFiles(i18n);
    if (isPromise(filterResult)) {
      filterResult.then(iterator, onError);
    } else {
      files = filterResult;
      iterator(files);
    }

    // 清除当前的值，否则选同一张图片不会触发事件
    evt.target.value = null;
  };

  iteratorFiles = i18n => files => {
    const { type, maxSize, silent, maxAmount } = this.props;

    forEach(files, (file, index) => {
      if (maxAmount && index >= maxAmount) {
        !silent && Notify.error(i18n.input.maxAmount({ maxAmount, type }));
        return false;
      }
      if (!maxSize || file.size <= maxSize) {
        this.addFile(file, index, i18n);
      } else {
        !silent &&
          Notify.error(
            i18n.input.maxSize({
              maxSize: formatFileSize(maxSize),
              type
            })
          );
      }
    });
  };

  addFile(file, index, i18n) {
    let fileReader = new FileReader();
    let { silent, type, initIndex } = this.props;
    let { accept } = this.state;
    let localFiles = [];

    fileReader.onload = e => {
      const mimeType = fileType(
        base64ToArrayBuffer(e.target.result.replace(/^(.*?)base64,/, ''))
      );
      if (accept && (!mimeType || accept.indexOf(mimeType.mime) > -1)) {
        localFiles.push({
          src: e.target.result,
          file,
          __uid: initIndex + index
        });
      } else {
        !silent && Notify.error(i18n.input.type({ type }));
      }
      this.onFileChange(localFiles);
    };

    fileReader.readAsDataURL(file);
  }

  render() {
    let { maxAmount } = this.props;
    let { accept } = this.state;

    return (
      <Receiver componentName="Upload" defaultI18n={I18nDefault}>
        {i18n => (
          <input
            type="file"
            placeholder={`${i18n.input.holder} +`}
            multiple={maxAmount !== 1}
            accept={accept}
            onChange={this.processFiles(i18n)}
          />
        )}
      </Receiver>
    );
  }
}
<commit_msg>feat: 修复可以添加超过maxAmount数量的问题<commit_after>/**
 * 上传图片输入框
 */

import React, { PureComponent, Component } from 'react';
import Notify from 'notify';
import toArray from 'lodash/toArray';
import forEach from 'lodash/forEach';
import isPromise from 'utils/isPromise';
import { I18nReceiver as Receiver } from 'i18n';
import { Upload as I18nDefault } from 'i18n/default';

import { formatFileSize, base64ToArrayBuffer } from '../utils';
import fileType from '../utils/file-type';
import uploadLocalImage from './UploadLocal';
import { DEFAULT_ACCEPT } from '../constants';

export default class FileInput extends (PureComponent || Component) {
  constructor(props) {
    super(props);

    let { accept, type } = props;
    // 根据type设置accept默认值
    if (!accept) {
      accept = DEFAULT_ACCEPT[type];
    }

    this.state = {
      accept
    };
  }

  onFileChange = localFiles => {
    const { onChange } = this.props;
    if (onChange) {
      onChange(localFiles);
    } else {
      uploadLocalImage(this.props, {
        localFiles
      });
    }
  };

  processFiles = i18n => evt => {
    let files = toArray(evt.target.files);
    const { filterFiles, onError } = this.props;

    let filterResult = filterFiles(files);
    const iterator = this.iteratorFiles(i18n);
    if (isPromise(filterResult)) {
      filterResult.then(iterator, onError);
    } else {
      files = filterResult;
      iterator(files);
    }

    // 清除当前的值，否则选同一张图片不会触发事件
    evt.target.value = null;
  };

  iteratorFiles = i18n => files => {
    const { type, maxSize, silent, maxAmount, initIndex } = this.props;

    forEach(files, (file, index) => {
      if (maxAmount && index + initIndex >= maxAmount) {
        !silent && Notify.error(i18n.input.maxAmount({ maxAmount, type }));
        return false;
      }
      if (!maxSize || file.size <= maxSize) {
        this.addFile(file, index, i18n);
      } else {
        !silent &&
          Notify.error(
            i18n.input.maxSize({
              maxSize: formatFileSize(maxSize),
              type
            })
          );
      }
    });
  };

  addFile(file, index, i18n) {
    let fileReader = new FileReader();
    let { silent, type, initIndex } = this.props;
    let { accept } = this.state;
    let localFiles = [];

    fileReader.onload = e => {
      const mimeType = fileType(
        base64ToArrayBuffer(e.target.result.replace(/^(.*?)base64,/, ''))
      );
      if (accept && (!mimeType || accept.indexOf(mimeType.mime) > -1)) {
        localFiles.push({
          src: e.target.result,
          file,
          __uid: initIndex + index
        });
      } else {
        !silent && Notify.error(i18n.input.type({ type }));
      }
      this.onFileChange(localFiles);
    };

    fileReader.readAsDataURL(file);
  }

  render() {
    let { maxAmount } = this.props;
    let { accept } = this.state;

    return (
      <Receiver componentName="Upload" defaultI18n={I18nDefault}>
        {i18n => (
          <input
            type="file"
            placeholder={`${i18n.input.holder} +`}
            multiple={maxAmount !== 1}
            accept={accept}
            onChange={this.processFiles(i18n)}
          />
        )}
      </Receiver>
    );
  }
}
<|endoftext|>


<commit_before>function buildFormWithModal($scope, $modal, opts, cb) {
    var formConfig = angular.copy(opts.form);
    formConfig.name = opts.name;
    formConfig.label = opts.label;
    formConfig.actions = opts.actions;
    formConfig.timeout = opts.timeout;
    formConfig.msgs = opts.msgs;
    formConfig.buttonLabels = opts.buttonLabels;
    formConfig.data = opts.data;
    formConfig.ngDataApi = opts.ngDataApi;

    var m = ($modal && $modal !== null) ? true : false;

    buildForm($scope, m, formConfig, function () {
        if (opts.postBuild && (typeof(opts.postBuild) == 'function')) {
            opts.postBuild();
        }
    });

    if ($modal && $modal !== null) {
        var formContext = $scope;
        $scope.form.openForm = function () {
            $modal.open({
                template: "<ngform></ngform>",
                size: 'lg',
                backdrop: false,
                keyboard: false,
                controller: function ($scope, $modalInstance) {
                    $scope.form = formContext.form;
                    formContext.modalInstance = $modalInstance;
                    formContext.modalScope = $scope;

                    if (cb && typeof(cb) === 'function') {
                        cb();
                    }
                }
            });
        };
        $scope.form.openForm();

        $scope.form.closeModal = function () {
            $scope.modalInstance.close();
        };

    }
}

function buildForm(context, modal, configuration, cb) {
    context.form = {
        alerts: [],
        label: configuration.label,
        msgs: configuration.msgs,
        action: configuration.action,
        entries: configuration.entries,
        timeout: configuration.timeout,
        modal: modal,
        actions: configuration.actions,
        labels: {},
        formData: {}
    };

    context.form.closeAlert = function (i) {
        context.form.alerts.splice(i, 1);
    };

    context.form.displayAlert = function (type, msg) {
        context.form.alerts = [];
        context.form.alerts.push({'type': type, 'msg': msg});
        context.form.closeAllAlerts();
    };

    context.form.closeAllAlerts = function (instant) {
        if (instant) {
            context.form.alerts = [];
        }
        else {
            context.form.timeout(function () {
                context.form.alerts = [];
            }, 7000);
        }
    };

    function rebuildData(fieldEntry) {
        var keys = Object.keys(configuration.data);
        keys.forEach(function (inputName) {
            if (fieldEntry.name === inputName) {
                if (Array.isArray(fieldEntry.value)) {
                    fieldEntry.value.forEach(function (oneValue) {
                        if (Array.isArray(configuration.data[inputName])) {
                            if (configuration.data[inputName].indexOf(oneValue.v) !== -1) {
                                oneValue.selected = true;
                            }
                        }
                        else if (oneValue.v === configuration.data[inputName]) {
                            oneValue.selected = true;
                        }
                    });
                }
                else {
                    fieldEntry.value = configuration.data[inputName];
                    context.form.formData[inputName] = configuration.data[inputName];
                }
            }
        });
    }

    function updateFormData(oneEntry) {
        if (oneEntry.value) {
            if (Array.isArray(oneEntry.value)) {
                context.form.formData[oneEntry.name] = [];
                oneEntry.value.forEach(function (oneValue) {
                    if (oneValue.selected === true) {
                        context.form.formData[oneEntry.name].push(oneValue.v);
                    }
                });
            }
            else {
                context.form.formData[oneEntry.name] = oneEntry.value;
            }
        }
        else if(oneEntry.type === 'number'){
	        if(oneEntry.value === 0){
		        context.form.formData[oneEntry.name] = oneEntry.value;
	        }
        }

        if (['document', 'audio', 'image', 'video'].indexOf(oneEntry.type) !== -1) {
            if (oneEntry.limit === undefined) {
                oneEntry.limit = 0;
            }
            else if (oneEntry.limit === 0) {
                oneEntry.addMore = true;
            }

            if(oneEntry.value && Array.isArray(oneEntry.value) && oneEntry.value.length > 0){
                if (oneEntry.limit < oneEntry.value.length) {
                    oneEntry.limit = oneEntry.value.length;
                }
            }
        }

        if (oneEntry.type === 'date-picker') {
            if (typeof(oneEntry.min) === 'object') {
                oneEntry.min = oneEntry.min.getTime();
            }

            oneEntry.openDate = function ($event, index) {
                $event.preventDefault();
                $event.stopPropagation();
                context.form.entries[index].opened = true;
            };
        }

        if (oneEntry.type === 'select') {
            if (oneEntry.onChange && typeof(oneEntry.onChange.action) === 'function') {
                oneEntry.action = oneEntry.onChange;
            }
            else {
                oneEntry.action = {};
            }
        }
    }

    if (configuration.data) {
        for (var i = 0; i < context.form.entries.length; i++) {
            if (context.form.entries[i].type === 'group') {
                context.form.entries[i].entries.forEach(function (oneSubEntry) {
                    rebuildData(oneSubEntry);
                });
            }
            else {
                rebuildData(context.form.entries[i]);
            }
        }
        context.form.refData = configuration.data;
    }

    for (var i = 0; i < context.form.entries.length; i++) {
        if (context.form.entries[i].type === 'group') {
            context.form.entries[i].icon = (context.form.entries[i].collapsed) ? "plus" : "minus";
            context.form.entries[i].entries.forEach(function (oneSubEntry) {
                updateFormData(oneSubEntry);
            });
        }
        else {
            updateFormData(context.form.entries[i]);
        }
    }

    context.form.do = function (functionObj) {
        if (functionObj.type === 'submit') {
            if (context.form.itemsAreValid()) {
                functionObj.action(context.form.formData);
            }
        }
        else {
            functionObj.action();
        }
    };
    context.form.callObj = function (functionObj) {
        if (functionObj) {
            if (functionObj.action) {
                functionObj.action();
            }
        }
    };
    context.form.call = function (action, id, data, form) {
        if (action) {
            if (typeof(action) == 'function') {
                action(id, data, form);
            }
        }
    };

    // testAction
    context.form.itemsAreValid = function () {
        var entries = context.form.entries;
        var data = context.form.formData;
        // for external keys, the form might be empty
        for (var i = 0; i < entries.length; i++) {
            var oneEntry = entries[i];
	        if(oneEntry.type === 'group'){
		        continue;
	        }
            if (oneEntry.required && (!data[oneEntry.name] || data[oneEntry.name] === 'undefined' || data[oneEntry.name] === '')) {
                return false;
            }
        }
        return true;
    };

    context.form.toggleSelection = function (fieldName, value) {
        if (!context.form.formData[fieldName]) {
            context.form.formData[fieldName] = [];
        }

        if (context.form.formData[fieldName].indexOf(value) === -1) {
            context.form.formData[fieldName].push(value);
        }
        else {
            var idx = context.form.formData[fieldName].indexOf(value);
            context.form.formData[fieldName].splice(idx, 1);
        }
    };
    if (cb && (typeof(cb) == 'function')) {
        context.form.timeout(function () {
            cb();
        }, 1000);
    }

    context.form.showHide = function (oneEntry) {
        if (oneEntry.collapsed) {
            oneEntry.collapsed = false;
            oneEntry.icon = "minus";
        }
        else {
            oneEntry.collapsed = true;
            oneEntry.icon = "plus";
        }
    };

    context.form.addNewInput = function (input) {
        if (input.limit === 0) {
            input.limit = 1;
        }
        input.limit++;
        input.addMore = true;
    };

    context.form.downloadFile = function(config, mediaType){
        var options = {
            routeName: config.routeName,
            method: 'get',
            headers: config.headers,
            responseType: 'arraybuffer',
            params: config.params
        };
        getSendDataFromServer(context, configuration.ngDataApi, options, function(error, data){
            switch (mediaType) {
                case 'image':
                    var blob = new Blob([data], {type: config.metadata.mime});
                    var URL = window.URL || window.webkitURL;
                    config.src = URL.createObjectURL(blob);
                    break;
                default:
                    openSaveAsDialog(config.filename, data, config.contentType);
                    break;
            }
        });
    };

    context.form.removeFile = function (entry, i) {
        getSendDataFromServer(context, configuration.ngDataApi, {
            "method": "get",
            "routeName": entry.removeFileUrl + entry.value[i]._id
        }, function (error) {
            if (error) {
                context.form.displayAlert('danger', error.message);
            }
            else {
                context.form.displayAlert('success', 'File Removed Successfully.');
                //remove the html input
                var loc = context.form.formData[entry.name].indexOf(entry.value[i].v);
                context.form.formData[entry.name].splice(loc, 1);
                entry.value.splice(i, 1);
            }
        });
    };

    context.form.uploadFileToUrl = function (Upload, config, cb) {
        var options = {
            url: apiConfiguration.domain + config.uploadUrl,
            params: config.data || null,
            file: config.file,
            headers: {
                key: apiConfiguration.key
            }
        };
        if (config.headers) {
            for (var i in config.headers) {
                options.headers[i] = config.headers[i];
            }
        }

        Upload.upload(options).progress(function (evt) {
            var progressPercentage = parseInt(100.0 * evt.loaded / evt.total);
            config.progress.value = progressPercentage;
        }).success(function (response, status, headers, config) {
            if (!response.result) {
                return cb(new Error(response.errors.details[0].message));
            }
            else {
                return cb(null, response);
            }
        }).error(function (data, status, header, config) {
            return cb(new Error("Error Occured while uploading file: " + config.file));
        });
    };
}

soajsApp.directive('ngform', function () {
    return {
        restrict: 'E',
        templateUrl: 'lib/form/form.tmpl'
    };
});

soajsApp.directive('fileModel', ['$parse', function ($parse) {
    return {
        restrict: 'A',
        link: function (scope, element, attrs) {
            var model = $parse(attrs.fileModel);
            var modelSetter = model.assign;

            element.bind('change', function () {
                scope.$apply(function () {
                    modelSetter(scope, element[0].files[0]);
                });
            });
        }
    };
}]);<commit_msg>added new directive for multiple file upload support using one browse button.<commit_after>function buildFormWithModal($scope, $modal, opts, cb) {
    var formConfig = angular.copy(opts.form);
    formConfig.name = opts.name;
    formConfig.label = opts.label;
    formConfig.actions = opts.actions;
    formConfig.timeout = opts.timeout;
    formConfig.msgs = opts.msgs;
    formConfig.buttonLabels = opts.buttonLabels;
    formConfig.data = opts.data;
    formConfig.ngDataApi = opts.ngDataApi;

    var m = ($modal && $modal !== null) ? true : false;

    buildForm($scope, m, formConfig, function () {
        if (opts.postBuild && (typeof(opts.postBuild) == 'function')) {
            opts.postBuild();
        }
    });

    if ($modal && $modal !== null) {
        var formContext = $scope;
        $scope.form.openForm = function () {
            $modal.open({
                template: "<ngform></ngform>",
                size: 'lg',
                backdrop: false,
                keyboard: false,
                controller: function ($scope, $modalInstance) {
                    $scope.form = formContext.form;
                    formContext.modalInstance = $modalInstance;
                    formContext.modalScope = $scope;

                    if (cb && typeof(cb) === 'function') {
                        cb();
                    }
                }
            });
        };
        $scope.form.openForm();

        $scope.form.closeModal = function () {
            $scope.modalInstance.close();
        };

    }
}

function buildForm(context, modal, configuration, cb) {
    context.form = {
        alerts: [],
        label: configuration.label,
        msgs: configuration.msgs,
        action: configuration.action,
        entries: configuration.entries,
        timeout: configuration.timeout,
        modal: modal,
        actions: configuration.actions,
        labels: {},
        formData: {}
    };

    context.form.closeAlert = function (i) {
        context.form.alerts.splice(i, 1);
    };

    context.form.displayAlert = function (type, msg) {
        context.form.alerts = [];
        context.form.alerts.push({'type': type, 'msg': msg});
        context.form.closeAllAlerts();
    };

    context.form.closeAllAlerts = function (instant) {
        if (instant) {
            context.form.alerts = [];
        }
        else {
            context.form.timeout(function () {
                context.form.alerts = [];
            }, 7000);
        }
    };

    function rebuildData(fieldEntry) {
        var keys = Object.keys(configuration.data);
        keys.forEach(function (inputName) {
            if (fieldEntry.name === inputName) {
                if (Array.isArray(fieldEntry.value)) {
                    fieldEntry.value.forEach(function (oneValue) {
                        if (Array.isArray(configuration.data[inputName])) {
                            if (configuration.data[inputName].indexOf(oneValue.v) !== -1) {
                                oneValue.selected = true;
                            }
                        }
                        else if (oneValue.v === configuration.data[inputName]) {
                            oneValue.selected = true;
                        }
                    });
                }
                else {
                    fieldEntry.value = configuration.data[inputName];
                    context.form.formData[inputName] = configuration.data[inputName];
                }
            }
        });
    }

    function updateFormData(oneEntry) {
        if (oneEntry.value) {
            if (Array.isArray(oneEntry.value)) {
                context.form.formData[oneEntry.name] = [];
                oneEntry.value.forEach(function (oneValue) {
                    if (oneValue.selected === true) {
                        context.form.formData[oneEntry.name].push(oneValue.v);
                    }
                });
            }
            else {
                context.form.formData[oneEntry.name] = oneEntry.value;
            }
        }
        else if(oneEntry.type === 'number'){
	        if(oneEntry.value === 0){
		        context.form.formData[oneEntry.name] = oneEntry.value;
	        }
        }

        if (['document', 'audio', 'image', 'video'].indexOf(oneEntry.type) !== -1) {
            if (oneEntry.limit === undefined) {
                oneEntry.limit = 0;
            }
            else if (oneEntry.limit === 0) {
                oneEntry.addMore = true;
            }

            if(oneEntry.value && Array.isArray(oneEntry.value) && oneEntry.value.length > 0){
                if (oneEntry.limit < oneEntry.value.length) {
                    oneEntry.limit = oneEntry.value.length;
                }
            }
        }

        if (oneEntry.type === 'date-picker') {
            if (typeof(oneEntry.min) === 'object') {
                oneEntry.min = oneEntry.min.getTime();
            }

            oneEntry.openDate = function ($event, index) {
                $event.preventDefault();
                $event.stopPropagation();
                context.form.entries[index].opened = true;
            };
        }

        if (oneEntry.type === 'select') {
            if (oneEntry.onChange && typeof(oneEntry.onChange.action) === 'function') {
                oneEntry.action = oneEntry.onChange;
            }
            else {
                oneEntry.action = {};
            }
        }
    }

    if (configuration.data) {
        for (var i = 0; i < context.form.entries.length; i++) {
            if (context.form.entries[i].type === 'group') {
                context.form.entries[i].entries.forEach(function (oneSubEntry) {
                    rebuildData(oneSubEntry);
                });
            }
            else {
                rebuildData(context.form.entries[i]);
            }
        }
        context.form.refData = configuration.data;
    }

    for (var i = 0; i < context.form.entries.length; i++) {
        if (context.form.entries[i].type === 'group') {
            context.form.entries[i].icon = (context.form.entries[i].collapsed) ? "plus" : "minus";
            context.form.entries[i].entries.forEach(function (oneSubEntry) {
                updateFormData(oneSubEntry);
            });
        }
        else {
            updateFormData(context.form.entries[i]);
        }
    }

    context.form.do = function (functionObj) {
        if (functionObj.type === 'submit') {
            if (context.form.itemsAreValid()) {
                functionObj.action(context.form.formData);
            }
        }
        else {
            functionObj.action();
        }
    };
    context.form.callObj = function (functionObj) {
        if (functionObj) {
            if (functionObj.action) {
                functionObj.action();
            }
        }
    };
    context.form.call = function (action, id, data, form) {
        if (action) {
            if (typeof(action) == 'function') {
                action(id, data, form);
            }
        }
    };

    // testAction
    context.form.itemsAreValid = function () {
        var entries = context.form.entries;
        var data = context.form.formData;
        // for external keys, the form might be empty
        for (var i = 0; i < entries.length; i++) {
            var oneEntry = entries[i];
	        if(oneEntry.type === 'group'){
		        continue;
	        }
            if (oneEntry.required && (!data[oneEntry.name] || data[oneEntry.name] === 'undefined' || data[oneEntry.name] === '')) {
                return false;
            }
        }
        return true;
    };

    context.form.toggleSelection = function (fieldName, value) {
        if (!context.form.formData[fieldName]) {
            context.form.formData[fieldName] = [];
        }

        if (context.form.formData[fieldName].indexOf(value) === -1) {
            context.form.formData[fieldName].push(value);
        }
        else {
            var idx = context.form.formData[fieldName].indexOf(value);
            context.form.formData[fieldName].splice(idx, 1);
        }
    };
    if (cb && (typeof(cb) == 'function')) {
        context.form.timeout(function () {
            cb();
        }, 1000);
    }

    context.form.showHide = function (oneEntry) {
        if (oneEntry.collapsed) {
            oneEntry.collapsed = false;
            oneEntry.icon = "minus";
        }
        else {
            oneEntry.collapsed = true;
            oneEntry.icon = "plus";
        }
    };

    context.form.addNewInput = function (input) {
        if (input.limit === 0) {
            input.limit = 1;
        }
        input.limit++;
        input.addMore = true;
    };

    context.form.downloadFile = function(config, mediaType){
        var options = {
            routeName: config.routeName,
            method: 'get',
            headers: config.headers,
            responseType: 'arraybuffer',
            params: config.params
        };
        getSendDataFromServer(context, configuration.ngDataApi, options, function(error, data){
            switch (mediaType) {
                case 'image':
                    var blob = new Blob([data], {type: config.metadata.mime});
                    var URL = window.URL || window.webkitURL;
                    config.src = URL.createObjectURL(blob);
                    break;
                default:
                    openSaveAsDialog(config.filename, data, config.contentType);
                    break;
            }
        });
    };

    context.form.removeFile = function (entry, i) {
        getSendDataFromServer(context, configuration.ngDataApi, {
            "method": "get",
            "routeName": entry.removeFileUrl + entry.value[i]._id
        }, function (error) {
            if (error) {
                context.form.displayAlert('danger', error.message);
            }
            else {
                context.form.displayAlert('success', 'File Removed Successfully.');
                //remove the html input
                var loc = context.form.formData[entry.name].indexOf(entry.value[i].v);
                context.form.formData[entry.name].splice(loc, 1);
                entry.value.splice(i, 1);
            }
        });
    };

    context.form.uploadFileToUrl = function (Upload, config, cb) {
        var options = {
            url: apiConfiguration.domain + config.uploadUrl,
            params: config.data || null,
            file: config.file,
            headers: {
                key: apiConfiguration.key
            }
        };
        if (config.headers) {
            for (var i in config.headers) {
                options.headers[i] = config.headers[i];
            }
        }

        Upload.upload(options).progress(function (evt) {
            var progressPercentage = parseInt(100.0 * evt.loaded / evt.total);
            config.progress.value = progressPercentage;
        }).success(function (response, status, headers, config) {
            if (!response.result) {
                return cb(new Error(response.errors.details[0].message));
            }
            else {
                return cb(null, response);
            }
        }).error(function (data, status, header, config) {
            return cb(new Error("Error Occured while uploading file: " + config.file));
        });
    };
}

soajsApp.directive('ngform', function () {
    return {
        restrict: 'E',
        templateUrl: 'lib/form/form.tmpl'
    };
});

soajsApp.directive('fileModel', ['$parse', function ($parse) {
    return {
        restrict: 'A',
        link: function (scope, element, attrs) {
            var model = $parse(attrs.fileModel);
            var modelSetter = model.assign;

            element.bind('change', function () {
                scope.$apply(function () {
                    modelSetter(scope, element[0].files[0]);
                });
            });
        }
    };
}]);

soajsApp.directive('fileModelMulti', ['$parse', function ($parse) {
	return {
		restrict: 'A',
		link: function (scope, element, attrs) {
			var model = $parse(attrs.fileModelMulti);
			var modelSetter = model.assign;

			element.bind('change', function () {
				scope.$apply(function () {
					modelSetter(scope, element[0].files);
				});
			});
		}
	};
}]);<|endoftext|>


<commit_before>/*!
 * jQuery UI Mouse @VERSION
 * http://jqueryui.com
 *
 * Copyright jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 */

//>>label: Mouse
//>>group: Widgets
//>>description: Abstracts mouse-based interactions to assist in creating certain widgets.
//>>docs: http://api.jqueryui.com/mouse/

( function( factory ) {
	if ( typeof define === "function" && define.amd ) {

		// AMD. Register as an anonymous module.
		define( [
			"jquery",
			"../ie",
			"../version",
			"../widget"
		], factory );
	} else {

		// Browser globals
		factory( jQuery );
	}
}( function( $ ) {

var mouseHandled = false;
$( document ).on( "mouseup", function() {
	mouseHandled = false;
} );

return $.widget( "ui.mouse", {
	version: "@VERSION",
	options: {
		cancel: "input, textarea, button, select, option",
		distance: 1,
		delay: 0
	},
	_mouseInit: function() {
		var that = this;

		this.element
			.on( "mousedown." + this.widgetName, function( event ) {
				return that._mouseDown( event );
			} )
			.on( "click." + this.widgetName, function( event ) {
				if ( true === $.data( event.target, that.widgetName + ".preventClickEvent" ) ) {
					$.removeData( event.target, that.widgetName + ".preventClickEvent" );
					event.stopImmediatePropagation();
					return false;
				}
			} );

		this.started = false;
	},

	// TODO: make sure destroying one instance of mouse doesn't mess with
	// other instances of mouse
	_mouseDestroy: function() {
		this.element.off( "." + this.widgetName );
		if ( this._mouseMoveDelegate ) {
			this.document
				.off( "mousemove." + this.widgetName, this._mouseMoveDelegate )
				.off( "mouseup." + this.widgetName, this._mouseUpDelegate );
		}
	},

	_mouseDown: function( event ) {

		// don't let more than one widget handle mouseStart
		if ( mouseHandled ) {
			return;
		}

		this._mouseMoved = false;

		// We may have missed mouseup (out of window)
		( this._mouseStarted && this._mouseUp( event ) );

		this._mouseDownEvent = event;

		var that = this,
			btnIsLeft = ( event.which === 1 ),

			// event.target.nodeName works around a bug in IE 8 with
			// disabled inputs (#7620)
			elIsCancel = ( typeof this.options.cancel === "string" && event.target.nodeName ? $( event.target ).closest( this.options.cancel ).length : false );
		if ( !btnIsLeft || elIsCancel || !this._mouseCapture( event ) ) {
			return true;
		}

		this.mouseDelayMet = !this.options.delay;
		if ( !this.mouseDelayMet ) {
			this._mouseDelayTimer = setTimeout( function() {
				that.mouseDelayMet = true;
			}, this.options.delay );
		}

		if ( this._mouseDistanceMet( event ) && this._mouseDelayMet( event ) ) {
			this._mouseStarted = ( this._mouseStart( event ) !== false );
			if ( !this._mouseStarted ) {
				event.preventDefault();
				return true;
			}
		}

		// Click event may never have fired (Gecko & Opera)
		if ( true === $.data( event.target, this.widgetName + ".preventClickEvent" ) ) {
			$.removeData( event.target, this.widgetName + ".preventClickEvent" );
		}

		// These delegates are required to keep context
		this._mouseMoveDelegate = function( event ) {
			return that._mouseMove( event );
		};
		this._mouseUpDelegate = function( event ) {
			return that._mouseUp( event );
		};

		this.document
			.on( "mousemove." + this.widgetName, this._mouseMoveDelegate )
			.on( "mouseup." + this.widgetName, this._mouseUpDelegate );

		event.preventDefault();

		mouseHandled = true;
		return true;
	},

	_mouseMove: function( event ) {

		// Only check for mouseups outside the document if you've moved inside the document
		// at least once. This prevents the firing of mouseup in the case of IE<9, which will
		// fire a mousemove event if content is placed under the cursor. See #7778
		// Support: IE <9
		if ( this._mouseMoved ) {

			// IE mouseup check - mouseup happened when mouse was out of window
			if ( $.ui.ie && ( !document.documentMode || document.documentMode < 9 ) && !event.button ) {
				return this._mouseUp( event );

			// Iframe mouseup check - mouseup occurred in another document
			} else if ( !event.which ) {
				return this._mouseUp( event );
			}
		}

		if ( event.which || event.button ) {
			this._mouseMoved = true;
		}

		if ( this._mouseStarted ) {
			this._mouseDrag( event );
			return event.preventDefault();
		}

		if ( this._mouseDistanceMet( event ) && this._mouseDelayMet( event ) ) {
			this._mouseStarted =
				( this._mouseStart( this._mouseDownEvent, event ) !== false );
			( this._mouseStarted ? this._mouseDrag( event ) : this._mouseUp( event ) );
		}

		return !this._mouseStarted;
	},

	_mouseUp: function( event ) {
		this.document
			.off( "mousemove." + this.widgetName, this._mouseMoveDelegate )
			.off( "mouseup." + this.widgetName, this._mouseUpDelegate );

		if ( this._mouseStarted ) {
			this._mouseStarted = false;

			if ( event.target === this._mouseDownEvent.target ) {
				$.data( event.target, this.widgetName + ".preventClickEvent", true );
			}

			this._mouseStop( event );
		}

		if ( this._mouseDelayTimer ) {
			clearTimeout( this._mouseDelayTimer );
			delete this._mouseDelayTimer;
		}

		mouseHandled = false;
		event.preventDefault();
	},

	_mouseDistanceMet: function( event ) {
		return ( Math.max(
				Math.abs( this._mouseDownEvent.pageX - event.pageX ),
				Math.abs( this._mouseDownEvent.pageY - event.pageY )
			) >= this.options.distance
		);
	},

	_mouseDelayMet: function( /* event */ ) {
		return this.mouseDelayMet;
	},

	// These are placeholder methods, to be overriden by extending plugin
	_mouseStart: function( /* event */ ) {},
	_mouseDrag: function( /* event */ ) {},
	_mouseStop: function( /* event */ ) {},
	_mouseCapture: function( /* event */ ) { return true; }
} );

} ) );
<commit_msg>Mouse: Ignore `mousemove` events triggered by key presses in Safari<commit_after>/*!
 * jQuery UI Mouse @VERSION
 * http://jqueryui.com
 *
 * Copyright jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 */

//>>label: Mouse
//>>group: Widgets
//>>description: Abstracts mouse-based interactions to assist in creating certain widgets.
//>>docs: http://api.jqueryui.com/mouse/

( function( factory ) {
	if ( typeof define === "function" && define.amd ) {

		// AMD. Register as an anonymous module.
		define( [
			"jquery",
			"../ie",
			"../version",
			"../widget"
		], factory );
	} else {

		// Browser globals
		factory( jQuery );
	}
}( function( $ ) {

var mouseHandled = false;
$( document ).on( "mouseup", function() {
	mouseHandled = false;
} );

return $.widget( "ui.mouse", {
	version: "@VERSION",
	options: {
		cancel: "input, textarea, button, select, option",
		distance: 1,
		delay: 0
	},
	_mouseInit: function() {
		var that = this;

		this.element
			.on( "mousedown." + this.widgetName, function( event ) {
				return that._mouseDown( event );
			} )
			.on( "click." + this.widgetName, function( event ) {
				if ( true === $.data( event.target, that.widgetName + ".preventClickEvent" ) ) {
					$.removeData( event.target, that.widgetName + ".preventClickEvent" );
					event.stopImmediatePropagation();
					return false;
				}
			} );

		this.started = false;
	},

	// TODO: make sure destroying one instance of mouse doesn't mess with
	// other instances of mouse
	_mouseDestroy: function() {
		this.element.off( "." + this.widgetName );
		if ( this._mouseMoveDelegate ) {
			this.document
				.off( "mousemove." + this.widgetName, this._mouseMoveDelegate )
				.off( "mouseup." + this.widgetName, this._mouseUpDelegate );
		}
	},

	_mouseDown: function( event ) {

		// don't let more than one widget handle mouseStart
		if ( mouseHandled ) {
			return;
		}

		this._mouseMoved = false;

		// We may have missed mouseup (out of window)
		( this._mouseStarted && this._mouseUp( event ) );

		this._mouseDownEvent = event;

		var that = this,
			btnIsLeft = ( event.which === 1 ),

			// event.target.nodeName works around a bug in IE 8 with
			// disabled inputs (#7620)
			elIsCancel = ( typeof this.options.cancel === "string" && event.target.nodeName ? $( event.target ).closest( this.options.cancel ).length : false );
		if ( !btnIsLeft || elIsCancel || !this._mouseCapture( event ) ) {
			return true;
		}

		this.mouseDelayMet = !this.options.delay;
		if ( !this.mouseDelayMet ) {
			this._mouseDelayTimer = setTimeout( function() {
				that.mouseDelayMet = true;
			}, this.options.delay );
		}

		if ( this._mouseDistanceMet( event ) && this._mouseDelayMet( event ) ) {
			this._mouseStarted = ( this._mouseStart( event ) !== false );
			if ( !this._mouseStarted ) {
				event.preventDefault();
				return true;
			}
		}

		// Click event may never have fired (Gecko & Opera)
		if ( true === $.data( event.target, this.widgetName + ".preventClickEvent" ) ) {
			$.removeData( event.target, this.widgetName + ".preventClickEvent" );
		}

		// These delegates are required to keep context
		this._mouseMoveDelegate = function( event ) {
			return that._mouseMove( event );
		};
		this._mouseUpDelegate = function( event ) {
			return that._mouseUp( event );
		};

		this.document
			.on( "mousemove." + this.widgetName, this._mouseMoveDelegate )
			.on( "mouseup." + this.widgetName, this._mouseUpDelegate );

		event.preventDefault();

		mouseHandled = true;
		return true;
	},

	_mouseMove: function( event ) {

		// Only check for mouseups outside the document if you've moved inside the document
		// at least once. This prevents the firing of mouseup in the case of IE<9, which will
		// fire a mousemove event if content is placed under the cursor. See #7778
		// Support: IE <9
		if ( this._mouseMoved ) {

			// IE mouseup check - mouseup happened when mouse was out of window
			if ( $.ui.ie && ( !document.documentMode || document.documentMode < 9 ) && !event.button ) {
				return this._mouseUp( event );

			// Iframe mouseup check - mouseup occurred in another document
			} else if ( !event.which ) {

				// Support: Safari <=8 - 9
				// Safari sets which to 0 if you press any of the following keys
				// during a drag (#14461)
				if ( event.originalEvent.altKey || event.originalEvent.ctrlKey ||
						event.originalEvent.metaKey || event.originalEvent.shiftKey ) {
					this.ignoreMissingWhich = true;
				} else if ( !this.ignoreMissingWhich ) {
					return this._mouseUp( event );
				}
			}
		}

		if ( event.which || event.button ) {
			this._mouseMoved = true;
		}

		if ( this._mouseStarted ) {
			this._mouseDrag( event );
			return event.preventDefault();
		}

		if ( this._mouseDistanceMet( event ) && this._mouseDelayMet( event ) ) {
			this._mouseStarted =
				( this._mouseStart( this._mouseDownEvent, event ) !== false );
			( this._mouseStarted ? this._mouseDrag( event ) : this._mouseUp( event ) );
		}

		return !this._mouseStarted;
	},

	_mouseUp: function( event ) {
		this.document
			.off( "mousemove." + this.widgetName, this._mouseMoveDelegate )
			.off( "mouseup." + this.widgetName, this._mouseUpDelegate );

		if ( this._mouseStarted ) {
			this._mouseStarted = false;

			if ( event.target === this._mouseDownEvent.target ) {
				$.data( event.target, this.widgetName + ".preventClickEvent", true );
			}

			this._mouseStop( event );
		}

		if ( this._mouseDelayTimer ) {
			clearTimeout( this._mouseDelayTimer );
			delete this._mouseDelayTimer;
		}

		this.ignoreMissingWhich = false;
		mouseHandled = false;
		event.preventDefault();
	},

	_mouseDistanceMet: function( event ) {
		return ( Math.max(
				Math.abs( this._mouseDownEvent.pageX - event.pageX ),
				Math.abs( this._mouseDownEvent.pageY - event.pageY )
			) >= this.options.distance
		);
	},

	_mouseDelayMet: function( /* event */ ) {
		return this.mouseDelayMet;
	},

	// These are placeholder methods, to be overriden by extending plugin
	_mouseStart: function( /* event */ ) {},
	_mouseDrag: function( /* event */ ) {},
	_mouseStop: function( /* event */ ) {},
	_mouseCapture: function( /* event */ ) { return true; }
} );

} ) );
<|endoftext|>


<commit_before>function Pathfinder(world){
	var self = this;
	var INFINITY = 1000000000;
	self.INFINITY = INFINITY;

	function reconstructPath(cameFrom, current){
		var totalPath = [];
		totalPath.push(current)
		console.log(cameFrom);
		world.canvasContext.lineWidth = world.tileSize;
		world.canvasContext.fillStyle = "#00f";
		for(let i in cameFrom){
			try{
				//TODO: find out how why undefined is coming
				if(cameFrom[current.getIndex()] === undefined){
					continue;
				}
				current = cameFrom[current.getIndex()];
				totalPath.push(current);
			} catch(e){
				console.log("ERROR: "+e.message + "\n for index: "+i)
			}
		}
		for(let current of totalPath){
			world.canvasContext.fillRect(current.x*world.tileSize,current.y*world.tileSize,world.tileSize,world.tileSize)
		}
		return totalPath;
	}
	function getMinFscore(openSet, fScore){
		var lowest = INFINITY;
		var index = undefined;
		//TODO remove loop over openset and implement with prioority queue
		for(var i in openSet){
			if(fScore[openSet[i].getIndex()] < lowest){
				lowest = fScore[openSet[i].getIndex()];
				console.log(openSet[i].getIndex())
				index = i;
			}
		}
		return {
			'value': lowest,
			'index': index
		};
	}
	self.heuristicCostEstimate = function (startTile, endTile){
		//euclidian distance
		return Math.sqrt(Math.pow(startTile.x - endTile.x, 2) + Math.pow(startTile.y - endTile.y, 2));
		//diagonal distance
		return Math.max(Math.abs(startTile.x - endTile.x), Math.abs(startTile.y - endTile.y));
		//manhattan distance
		return Math.abs(startTile.x - endTile.x) + Math.abs(startTile.y - endTile.y);
	}

	self.findNeighbours = function(tile) {
		var neighbours = [];
		if(tile.x-1 >= 0){
			var node = world.tiles[ [tile.x-1, tile.y] ];
			neighbours.push(node);
		}
		if(tile.x+1 < world.widthInTiles){
			var node = world.tiles[ [tile.x+1, tile.y] ];
			neighbours.push(node);
		}
		if(tile.y-1 >= 0){
			var node = world.tiles[ [tile.x, tile.y-1] ];
			neighbours.push(node);
		}
		if(tile.y+1 < world.heightInTiles){
			var node = world.tiles[ [tile.x, tile.y+1] ];
			neighbours.push(node);
		}

		if(tile.x-1 >= 0 && tile.y-1 >= 0){
			var node = world.tiles[ [tile.x-1, tile.y-1] ];
			neighbours.push(node);
		}
		if(tile.x+1 < world.widthInTiles && tile.y+1 < world.heightInTiles){
			var node = world.tiles[ [tile.x+1, tile.y+1] ];
			neighbours.push(node);
		}
		if(tile.x-1 >=0 && tile.y+1 < world.heightInTiles){
			var node = world.tiles[ [tile.x-1, tile.y+1] ];
			neighbours.push(node);
		}
		if(tile.x+1 < world.widthInTiles && tile.y-1 >= 0){
			var node = world.tiles[ [tile.x+1, tile.y-1] ];
			neighbours.push(node);
		}
		return neighbours;
	}

	self.findPath = function(startTile, endTile){
		var closedSet = [];
		var openSet = [];
		openSet.push(startTile);
		var cameFrom = [];

		var gScore = [];
		var fScore = [];
		for(var i in world.tiles) {
			gScore[i] = INFINITY;
			fScore[i] = INFINITY;
		}
		gScore[startTile.getIndex()] = 0;
		fScore[startTile.getIndex()] = self.heuristicCostEstimate(startTile, endTile);

		while(openSet.length > 0){
			var current = openSet[getMinFscore(openSet,fScore).index];
			console.log(openSet)
			console.log("min fscore: "+JSON.stringify(getMinFscore(openSet,fScore)));
			if(current === undefined){
				return [];
			}

			if (current === endTile){
				return reconstructPath(cameFrom, current);
			}
			world.canvasContext.lineWidth = world.tileSize;
			world.canvasContext.fillStyle = "#f00";
			console.log(current)
			world.canvasContext.fillRect(current.x*world.tileSize,current.y*world.tileSize,world.tileSize,world.tileSize)

			//remove current from openSet and adding to closedSet
			openSet.splice(openSet.indexOf(current),1);
			closedSet.push(current);

			for(neighbour of self.findNeighbours(current)){
				//check if neighbour has already been visited
				//if not, continue
				if(closedSet.indexOf(neighbour) > -1){
					continue;
				}
				var tempGscore = gScore[current.getIndex()] + neighbour.weight*self.heuristicCostEstimate(current, neighbour);
				if (openSet.indexOf(neighbour) === -1){
					openSet.push(neighbour);
				}
				else if(tempGscore >= gScore[neighbour]){
					continue;
				}

				cameFrom[neighbour.getIndex()] = current;
				gScore[neighbour.getIndex()] = tempGscore;
				fScore[neighbour.getIndex()] = gScore[neighbour.getIndex()] + self.heuristicCostEstimate(neighbour, endTile);
			}
		}
		console.log("failed to find path");
		return "failed to find path";
	}
}
<commit_msg>implemeted fScore using priority queue<commit_after>function Pathfinder(world){
	var self = this;
	var INFINITY = 1000000000;
	self.INFINITY = INFINITY;

	function reconstructPath(cameFrom, current){
		var totalPath = [];
		totalPath.push(current)
		world.canvasContext.lineWidth = world.tileSize;
		world.canvasContext.fillStyle = "#00f";
		for(let i in cameFrom){
			try{
				//TODO: find out how why undefined is coming
				if(cameFrom[current.getIndex()] === undefined){
					continue;
				}
				current = cameFrom[current.getIndex()];
				totalPath.push(current);
			} catch(e){
				console.log("ERROR: "+e.message + "\n for index: "+i)
			}
		}
		for(let current of totalPath){
			world.canvasContext.fillRect(current.x*world.tileSize,current.y*world.tileSize,world.tileSize,world.tileSize)
		}
		return totalPath;
	}
	function getMinFscore(openSet, fScore){
		//index of world.tiles[] with the lowest fScore
		console.log("poped:"+fScore.data[0][0]);
	//	do {
			var lowest = fScore.pop();
			var index = openSet.indexOf(world.tiles[lowest]);
	//	} while (index === -1);

		return {
			'value': lowest,
			'index': index
		};
	}
	self.heuristicCostEstimate = function (startTile, endTile){
		//euclidian distance
		return Math.sqrt(Math.pow(startTile.x - endTile.x, 2) + Math.pow(startTile.y - endTile.y, 2));
		//diagonal distance
		return Math.max(Math.abs(startTile.x - endTile.x), Math.abs(startTile.y - endTile.y));
		//manhattan distance
		return Math.abs(startTile.x - endTile.x) + Math.abs(startTile.y - endTile.y);
	}

	self.findNeighbours = function(tile) {
		var neighbours = [];
		if(tile.x-1 >= 0){
			var node = world.tiles[ [tile.x-1, tile.y] ];
			neighbours.push(node);
		}
		if(tile.x+1 < world.widthInTiles){
			var node = world.tiles[ [tile.x+1, tile.y] ];
			neighbours.push(node);
		}
		if(tile.y-1 >= 0){
			var node = world.tiles[ [tile.x, tile.y-1] ];
			neighbours.push(node);
		}
		if(tile.y+1 < world.heightInTiles){
			var node = world.tiles[ [tile.x, tile.y+1] ];
			neighbours.push(node);
		}

		if(tile.x-1 >= 0 && tile.y-1 >= 0){
			var node = world.tiles[ [tile.x-1, tile.y-1] ];
			neighbours.push(node);
		}
		if(tile.x+1 < world.widthInTiles && tile.y+1 < world.heightInTiles){
			var node = world.tiles[ [tile.x+1, tile.y+1] ];
			neighbours.push(node);
		}
		if(tile.x-1 >=0 && tile.y+1 < world.heightInTiles){
			var node = world.tiles[ [tile.x-1, tile.y+1] ];
			neighbours.push(node);
		}
		if(tile.x+1 < world.widthInTiles && tile.y-1 >= 0){
			var node = world.tiles[ [tile.x+1, tile.y-1] ];
			neighbours.push(node);
		}
		return neighbours;
	}

	self.findPath = function(startTile, endTile){
		var closedSet = [];
		var openSet = [];
		openSet.push(startTile);
		var cameFrom = [];

		var gScore = [];
		var fScore = new PriorityQueue();
		for(var i in world.tiles) {
			gScore[i] = INFINITY;
		}
		gScore[startTile.getIndex()] = 0;
		fScore.push(startTile.getIndex(), self.heuristicCostEstimate(startTile, endTile));

		while(openSet.length > 0){
			var current = openSet[getMinFscore(openSet,fScore).index];

			if (current === endTile){
				return reconstructPath(cameFrom, current);
			}
			world.canvasContext.lineWidth = world.tileSize;
			world.canvasContext.fillStyle = "#f00";
			world.canvasContext.fillRect(current.x*world.tileSize,current.y*world.tileSize,world.tileSize,world.tileSize)

			//remove current from openSet and adding to closedSet
			openSet.splice(openSet.indexOf(current),1);
			closedSet[current.getIndex()] = 1;

			for(neighbour of self.findNeighbours(current)){
				//check if neighbour has already been visited
				//if not, continue
				if(closedSet[neighbour.getIndex()] == 1){
					continue;
				}
				var tempGscore = gScore[current.getIndex()] + neighbour.weight*self.heuristicCostEstimate(current, neighbour);
				if (openSet.indexOf(neighbour) === -1){
					openSet.push(neighbour);
				}
				else if(tempGscore >= gScore[neighbour]){
					continue;
				}
				console.log("pushed"+neighbour.getIndex())

				cameFrom[neighbour.getIndex()] = current;
				gScore[neighbour.getIndex()] = tempGscore;
				fScore.push(neighbour.getIndex(), gScore[neighbour.getIndex()] + self.heuristicCostEstimate(neighbour, endTile) );
			}
		}
		return "failed to find path";
	}
	//only for elements which are arrays of length 2, and whose elements can be compared with ===
	//only one of each element is allowed
	//TODO: find a proper name
	function PriorityQueue() {
		var self = this;
		self.data = [];
		self.push = function(element, priority){
			for(let datum of self.data){
				//checking if element is in self.data[]
				if(datum[0][0] === element[0] && datum[0][1] === element[1]){
					//if element exists in self.data[], update it's priority
					datum[1] = priority;
					return true;
				}
			}
			var priority =  priority;
			for(var i = 0; i < self.data.length && self.data[i][1] < priority;i++);
			self.data.splice(i, 0,[element, priority]);
		}

		self.pop = function(){
			if(self.data.length > 0)
				return self.data.shift()[0];
		}

		self.size = function(){
			return self.data.length;
		}
		self.contains = function(element){
			for(let datum of self.data){
				//comparing the x and y of each thing
				if(datum[0][0] === element[0] && datum[0][1] === element[1]){
					return true;
				}
			}
			return false;
		}
	}
}
<|endoftext|>


<commit_before>//Account Settings Menu View
enyo.kind({
	//* @public
    name: "moon.sample.setting.AccountSettingSample",
    kind: "moon.Panel",
    //* @protected
    title: "ACCOUNT SETTINGS",
    titleBelow: "Description of Settings Category",
    components: [
        {
            kind: "FittableColumns",
            fit: true,
            components: [
                {
                    name: "eMail",
                    classes: "moon-cetner-item"
                },
                {
                    name: "signOutButton",
                    kind: "moon.Button",
                    bindFrom: "signOutButton",
                    classes: "moon-light-gray"
                }
            ]
        },
    	{
    		kind: "FittableColumns",
            fit:true,
    		components: [
    			{name: "autoSign"},
    			{
    				name: "toggleButton",
                    kind: "moon.ToggleButton"
    			}
    		]
    	},
    	{name: "help", classes: "moon-settings-content"},
    	{
    		name: "createAccountButton",
    		kind: "moon.Button",
    		bindFrom: "createAccountButton",
    		classes: "moon-light-gray"
    	}
    ],
    bindings: [
        {from: ".controller.eMail", to: "$.eMail.content"},
        {from: ".controller.signOutButton", to: "$.signOutButton.content"},
        {from: ".controller.autoSign", to: "$.autoSign.content"},
        {from: ".controller.help", to: "$.help.content"},
        {from: ".controller.createAccountButton", to: "$.createAccountButton.content"}
    ]
});

//Account Settings Menu Model
enyo.ready(function(){
	var accountModel = new enyo.Model({
		eMail: "hancuna@naver.com",
		signOutButton: "Sign out",
		autoSign: "Auto Sign in",
		help: "Create your account to: " +
			  "Keep up with your favorite channels" +
			  "Save videos  to watch later, watch recommendations just for you, or" +
			  "subscribe to get updates from your favorite channels",
		createAccountButton: "Create New Account"
	});

//  Application to render sample
	new enyo.Application({
        view: {
            classes: "enyo-unselectable moon",
            components: [
                {kind: "enyo.Spotlight"},
                {
                	kind: "moon.sample.setting.AccountSettingSample",
                    controller: ".app.controllers.settingAccountController",
                    classes: "enyo-fit"
                }
            ]
        },
        controllers: [
            {
                name: "settingAccountController",
                kind: "enyo.ModelController",
                model: accountModel
            }
        ]
    });
});
<commit_msg>GF-5126: remove fit:true option<commit_after>//Account Settings Menu View
enyo.kind({
	//* @public
    name: "moon.sample.setting.AccountSettingSample",
    kind: "moon.Panel",
    //* @protected
    title: "ACCOUNT SETTINGS",
    titleBelow: "Description of Settings Category",
    components: [
        {
            kind: "FittableColumns",

            components: [
                {
                    name: "eMail",
                    classes: "moon-cetner-item"
                },
                {
                    name: "signOutButton",
                    kind: "moon.Button",
                    bindFrom: "signOutButton",
                    classes: "moon-light-gray"
                }
            ]
        },
    	{
    		kind: "FittableColumns",
    		components: [
    			{name: "autoSign"},
    			{
    				name: "toggleButton",
                    kind: "moon.ToggleButton"
    			}
    		]
    	},
    	{name: "help", classes: "moon-settings-content"},
    	{
    		name: "createAccountButton",
    		kind: "moon.Button",
    		bindFrom: "createAccountButton",
    		classes: "moon-light-gray"
    	}
    ],
    bindings: [
        {from: ".controller.eMail", to: "$.eMail.content"},
        {from: ".controller.signOutButton", to: "$.signOutButton.content"},
        {from: ".controller.autoSign", to: "$.autoSign.content"},
        {from: ".controller.help", to: "$.help.content"},
        {from: ".controller.createAccountButton", to: "$.createAccountButton.content"}
    ]
});

//Account Settings Menu Model
enyo.ready(function(){
	var accountModel = new enyo.Model({
		eMail: "hancuna@naver.com",
		signOutButton: "Sign out",
		autoSign: "Auto Sign in",
		help: "Create your account to: " +
			  "Keep up with your favorite channels" +
			  "Save videos  to watch later, watch recommendations just for you, or" +
			  "subscribe to get updates from your favorite channels",
		createAccountButton: "Create New Account"
	});

//  Application to render sample
	new enyo.Application({
        view: {
            classes: "enyo-unselectable moon",
            components: [
                {kind: "enyo.Spotlight"},
                {
                	kind: "moon.sample.setting.AccountSettingSample",
                    controller: ".app.controllers.settingAccountController",
                    classes: "enyo-fit"
                }
            ]
        },
        controllers: [
            {
                name: "settingAccountController",
                kind: "enyo.ModelController",
                model: accountModel
            }
        ]
    });
});
<|endoftext|>


<commit_before>
/**
 * Module dependencies.
 */

var express = require('express')
  , routes = require('./routes')
	, exec = require('child_process').exec

var app = module.exports = express.createServer();

// Configuration

app.configure(function(){
  app.set('views', __dirname + '/views');
  app.set('view engine', 'jade');
  app.use(express.bodyParser());
  app.use(express.methodOverride());
  app.use(app.router);
  app.use(express.static(__dirname + '/public'));
});

app.configure('development', function(){
  app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});

app.configure('production', function(){
  app.use(express.errorHandler());
});

// chromosome lengths for each species
var chromosomes = { at: '[[1,30427671],[2,19698289],[3,23459830],[4,18585056],[5,26975502]]' };

// Routes

app.get('/', routes.index);

app.get('/chromosomes', function(req,res) {
	res.writeHead(200, {'Content-Type': 'application/json'});
	res.end(chromosomes['at']);
});

app.get('/chromosomes/:genome', function(req,res) {
	res.writeHead(200, {'Content-Type': 'application/json'});
	res.end(chromosomes[req.params.genome]);
});

app.get('/histogram/GO', function(req,res) {
	var cmd = '../fastbit/fbsql -s "GO_term,count(*)" -d ../fastbit/data/ath_GO'
	var fbsql = exec(cmd, function (error, stdout, stderr) {
		res.writeHead(200, {'Content-Type': 'application/json'});
		res.end(stdout);
	});
});

app.get('/histogram/GO/GWAS/:study/:where', function(req,res) {
	var cmd = '../fastbit/join_once -d1 ../fastbit/data/GWAS/full_results/'
		+ req.params.study + ' -d2 ../fastbit/data/ath_GO -s1 "gene_id,count(*)"'
		+ ' -s2 "GO_term,count(*)" -j gene_id -w1 "' + req.params.where + '"';
	var join = exec(cmd, function (err, stdout, stderr) {
		res.writeHead(200, {'Content-Type': 'application/json'});
		res.end(stdout);
	});
});

app.get('/scatter/GWAS/:study/:chr/:b1/:b2', function(req,res) {
	var cmd = '../fastbit/scatter -c1 pos -c2 score -d ../fastbit/data/GWAS/full_results/' + req.params.study
		+ '/' + req.params.chr + ' -b1 ' + req.params.b1 + ' -b2 ' + req.params.b2;
		var scatter = exec(cmd, function (error, stdout, stderr) {
			res.writeHead(200, {'Content-Type': 'application/json'});
			res.end(stdout);
		});
});

app.listen(3001);
console.log("Express server listening on port %d in %s mode", app.address().port, app.settings.env);
<commit_msg>adding CORS support<commit_after>
/**
 * Module dependencies.
 */

var express = require('express')
  , routes = require('./routes')
	, exec = require('child_process').exec

var app = module.exports = express.createServer();

//CORS middleware
var allowCrossDomain = function(req, res, next) {
	res.header('Access-Control-Allow-Origin', '*');
	res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
	res.header('Access-Control-Allow-Headers', 'Content-Type');

	next();
}

// Configuration

app.configure(function(){
  app.set('views', __dirname + '/views');
  app.set('view engine', 'jade');
  app.use(express.bodyParser());
  app.use(express.methodOverride());
	app.use(allowCrossDomain);
  app.use(app.router);
  app.use(express.static(__dirname + '/public'));
});

app.configure('development', function(){
  app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});

app.configure('production', function(){
  app.use(express.errorHandler());
});

// chromosome lengths for each species
var chromosomes = { at: '[[1,30427671],[2,19698289],[3,23459830],[4,18585056],[5,26975502]]' };

// Routes

app.get('/', routes.index);

app.get('/chromosomes', function(req,res) {
	res.writeHead(200, {'Content-Type': 'application/json'});
	res.end(chromosomes['at']);
});

app.get('/chromosomes/:genome', function(req,res) {
	res.writeHead(200, {'Content-Type': 'application/json'});
	res.end(chromosomes[req.params.genome]);
});

app.get('/histogram/GO', function(req,res) {
	var cmd = '../fastbit/fbsql -s "GO_term,count(*)" -d ../fastbit/data/ath_GO'
	var fbsql = exec(cmd, function (error, stdout, stderr) {
		res.writeHead(200, {'Content-Type': 'application/json'});
		res.end(stdout);
	});
});

app.get('/histogram/GO/GWAS/:study/:where', function(req,res) {
	var cmd = '../fastbit/join_once -d1 ../fastbit/data/GWAS/full_results/'
		+ req.params.study + ' -d2 ../fastbit/data/ath_GO -s1 "gene_id,count(*)"'
		+ ' -s2 "GO_term,count(*)" -j gene_id -w1 "' + req.params.where + '"';
	var join = exec(cmd, function (err, stdout, stderr) {
		res.writeHead(200, {'Content-Type': 'application/json'});
		res.end(stdout);
	});
});

app.get('/scatter/GWAS/:study/:chr/:b1/:b2', function(req,res) {
	var cmd = '../fastbit/scatter -c1 pos -c2 score -d ../fastbit/data/GWAS/full_results/' + req.params.study
		+ '/' + req.params.chr + ' -b1 ' + req.params.b1 + ' -b2 ' + req.params.b2;
		var scatter = exec(cmd, function (error, stdout, stderr) {
			res.writeHead(200, {'Content-Type': 'application/json'});
			res.end(stdout);
		});
});

app.listen(3001);
console.log("Express server listening on port %d in %s mode", app.address().port, app.settings.env);
<|endoftext|>


<commit_before>var jsdom = require('jsdom');
var Path = require('path');
var URL = require('url');
var EventEmitter = require('events').EventEmitter;
var fs = require('fs');
var async = require('async');
var format = require('util').format;

var notemplate = module.exports = new EventEmitter();

var views = Object.create(null);

// keep that in memory
var jquery = fs.readFileSync(Path.join(Path.dirname(require.resolve('jquery-browser')), 'lib/jquery.js')).toString();

function load(path, cb) {
	var view = views[path] || { path: path };
	fs.stat(path, function(err, result) {
		if (err) return cb(err);
		if (view.mtime && result.mtime <= view.mtime) {
			view.hit = true;
			return cb(null, view);
		}
		fs.readFile(view.path, function(err, str) {
			if (err) return cb(err, view);
			view.window = getWindow(str);
			view.mtime = result.mtime;
			view.hit = false;
			views[view.path] = view;
			return cb(null, view);
		});
	});
}

function getWindow(str) {
	// create window with jquery
	var window = jsdom.jsdom(str, null, {			// default DOM, but eventually will be level2.html
		features: {
			FetchExternalResources: false,				// loaded depending on script[notemplate] attribute
			ProcessExternalResources: false,			// same
			MutationEvents: false,								// not needed
			QuerySelector: false									// not needed, we use jquery's bundled sizzle instead of jsdom's one.
		},
		xhtml: true
	}).createWindow();
	window.console = console;
	window.run(jquery);
	jqueryPatches(window.jQuery);
	return window;
}

function loadScript(root, src, cb) {
	var url = URL.parse(src);
	if (url.hostname) return cb(format("express-notemplate error - cannot load remote script\n%s", src), null);
	var path = Path.join(root, url.pathname);
	Path.exists(path, function(exists) {
		if (exists) fs.readFile(path, cb);
		else cb(format("express-notemplate error - cannot load local script\n%s", path));
	});
}

function outer($nodes) {
	var ret = '';
	$nodes.each(function() {
		ret += this.outerHTML;
	});
	return ret;
}

function jqueryPatches($) {
	// jQuery monkey-patch
	$.buildFragmentOrig = $.buildFragment;
	$.buildFragment = function(args, nodes, scripts) {
		var r = $.buildFragmentOrig(args, nodes, scripts);
		// or else script.contentText will be run, this is a security risk
		if (Array.isArray(scripts)) scripts.length = 0;
		return r;
	};
}

function merge(view, options, callback) {
	var window = view.window;
	var $ = window.$;
	var document = window.document;
	document.replaceChild(view.root.cloneNode(true), document.documentElement);
	// global listeners
	notemplate.emit('data', view, options);
	// listeners from scripts loaded inside view.window
	$(document).triggerHandler('data', options);
	// global listeners
	notemplate.emit('render', view, options);
	var output;
	if (options.fragment) output = outer($(options.fragment)); // output selected nodes
	else output = document.doctype.toString() + "\n" + document.outerHTML; // outputs doctype because of jsdom bug
	// global listeners can modify output (sync)
	var obj = { output : output };
	notemplate.emit('output', obj, options);
	callback(null, obj.output);
}

notemplate.__express = function(filename, options, callback) {
	load(filename, function(err, view) {
		if (err) return callback(err);
		// the first time the DOM is ready is an event
		var window = view.window;
		if (!view.hit) {
			notemplate.emit('ready', view, options);
			async.forEach(window.$('script'), function(script, done) {
				var att = script.attributes.notemplate;
				// default is notemplate="client"
				if (!att) return done();
				att = att.value;
				script.attributes.removeNamedItem('notemplate');
				// any other value is "client"
				if (att != "server" && att != "both") return done();
				var src = script.attributes.src;
				// html5 runs script content only when src is not set
				if (!src && script.textContent) window.run(script.textContent);
				if (att == "server") script.parentNode.remove
				if (!src) return done();
				loadScript(options.settings.statics || process.cwd() + '/public', src.value, function(err, textContent) {
					if (err) done(err);
					else {
						window.run(textContent.toString());
						done();
					}
				});
			}, function(err) {
				if (err) console.error(err); // errors are not fatal
				view.hit = true;
				view.root = window.document.documentElement;
				// all scripts have been loaded
				// now we can deal with data merging
				merge(view, options, callback);
			});
		} else merge(view, options, callback);
	});
};
<commit_msg>Formulation<commit_after>var jsdom = require('jsdom');
var Path = require('path');
var URL = require('url');
var EventEmitter = require('events').EventEmitter;
var fs = require('fs');
var async = require('async');
var format = require('util').format;

var notemplate = module.exports = new EventEmitter();

var views = Object.create(null);

// keep that in memory
var jquery = fs.readFileSync(Path.join(Path.dirname(require.resolve('jquery-browser')), 'lib/jquery.js')).toString();

function load(path, cb) {
	var view = views[path] || { path: path };
	fs.stat(path, function(err, result) {
		if (err) return cb(err);
		if (view.mtime && result.mtime <= view.mtime) {
			view.hit = true;
			return cb(null, view);
		}
		fs.readFile(view.path, function(err, str) {
			if (err) return cb(err, view);
			view.window = getWindow(str);
			view.mtime = result.mtime;
			view.hit = false;
			views[view.path] = view;
			return cb(null, view);
		});
	});
}

function getWindow(str) {
	// create window with jquery
	var window = jsdom.jsdom(str, null, {			// default DOM, but eventually will be level2.html
		features: {
			FetchExternalResources: false,				// loaded depending on script[notemplate] attribute
			ProcessExternalResources: false,			// same
			MutationEvents: false,								// not needed
			QuerySelector: false									// not needed, we use jquery's bundled sizzle instead of jsdom's one.
		},
		xhtml: true
	}).createWindow();
	window.console = console;
	window.run(jquery);
	jqueryPatches(window.jQuery);
	return window;
}

function loadScript(root, src, cb) {
	var url = URL.parse(src);
	if (url.hostname) return cb(format("express-notemplate error - cannot load remote script\n%s", src), null);
	var path = Path.join(root, url.pathname);
	Path.exists(path, function(exists) {
		if (exists) fs.readFile(path, cb);
		else cb(format("express-notemplate error - cannot find local script\n%s", path));
	});
}

function outer($nodes) {
	var ret = '';
	$nodes.each(function() {
		ret += this.outerHTML;
	});
	return ret;
}

function jqueryPatches($) {
	// jQuery monkey-patch
	$.buildFragmentOrig = $.buildFragment;
	$.buildFragment = function(args, nodes, scripts) {
		var r = $.buildFragmentOrig(args, nodes, scripts);
		// or else script.contentText will be run, this is a security risk
		if (Array.isArray(scripts)) scripts.length = 0;
		return r;
	};
}

function merge(view, options, callback) {
	var window = view.window;
	var $ = window.$;
	var document = window.document;
	document.replaceChild(view.root.cloneNode(true), document.documentElement);
	// global listeners
	notemplate.emit('data', view, options);
	// listeners from scripts loaded inside view.window
	$(document).triggerHandler('data', options);
	// global listeners
	notemplate.emit('render', view, options);
	var output;
	if (options.fragment) output = outer($(options.fragment)); // output selected nodes
	else output = document.doctype.toString() + "\n" + document.outerHTML; // outputs doctype because of jsdom bug
	// global listeners can modify output (sync)
	var obj = { output : output };
	notemplate.emit('output', obj, options);
	callback(null, obj.output);
}

notemplate.__express = function(filename, options, callback) {
	load(filename, function(err, view) {
		if (err) return callback(err);
		// the first time the DOM is ready is an event
		var window = view.window;
		if (!view.hit) {
			notemplate.emit('ready', view, options);
			async.forEach(window.$('script'), function(script, done) {
				var att = script.attributes.notemplate;
				// default is notemplate="client"
				if (!att) return done();
				att = att.value;
				script.attributes.removeNamedItem('notemplate');
				// any other value is "client"
				if (att != "server" && att != "both") return done();
				var src = script.attributes.src;
				// html5 runs script content only when src is not set
				if (!src && script.textContent) window.run(script.textContent);
				if (att == "server") script.parentNode.remove
				if (!src) return done();
				loadScript(options.settings.statics || process.cwd() + '/public', src.value, function(err, textContent) {
					if (err) done(err);
					else {
						window.run(textContent.toString());
						done();
					}
				});
			}, function(err) {
				if (err) console.error(err); // errors are not fatal
				view.hit = true;
				view.root = window.document.documentElement;
				// all scripts have been loaded
				// now we can deal with data merging
				merge(view, options, callback);
			});
		} else merge(view, options, callback);
	});
};
<|endoftext|>