Source: volume/spreadsheet.js

// Generated by CoffeeScript 1.12.7

/**
 * Data spreadsheet
 * @module
 */
'use strict';
var indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };

app.directive('spreadsheet', [
  'constantService', 'displayService', 'messageService', 'tooltipService', 'styleService', '$compile', '$templateCache', '$timeout', '$document', '$location', '$filter', '$route', 'routerService', 'storageService', 'Segment', function(constants, display, messages, tooltips, styles, $compile, $templateCache, $timeout, $document, $location, $filter, $route, router, storage, Segment) {
    return {
      restrict: 'E',
      scope: true,
      templateUrl: 'volume/spreadsheet.html',
      controller: [
        '$scope', '$element', '$attrs', function($scope, $element, $attrs) {
          var Birthdate, Cats, Cols, Editing, Expanded, Groups, ID, Info, Key, Limit, Order, Row, Rows, SlotCount, TBody, TFoot, addRow, arr, base, base1, byDefault, byMagic, byNumber, bySortId, byType, clickGlobal, clickRemove, collapse, createNew, createRecord, createSlot, currentSort, currentSortDirection, doneEdit, edit, editCell, editCellTemplate, editInput, editScope, editSelect, expand, fill, generateAdd, generateFoot, generateMultiple, generateRecord, generateRow, generateText, getCategory, getMetric, inc, maybeInt, obj, optionCompletions, parseId, parseIntish, pivotOpts, populateAssetData, populateCols, populateRecord, populateRecordData, populateRecords, populateSlot, populateSlotData, populateSlots, pseudoCategory, pseudoMetric, recordDescription, ref, removeRecord, removeRow, removeSlot, save, saveDatum, saveRun, select, setFilter, setFocus, setKey, setRecord, sort, sortBy, stripPrefix, unedit, unselect, updateDatum, volume;
          volume = $scope.volume;
          Editing = $scope.editing = $attrs.edit !== void 0;
          ID = $scope.id = (ref = $attrs.id) != null ? ref : 'ss';
          $scope.showGlobal = false;
          Limit = $attrs.limit || 2e308;
          Key = void 0;
          maybeInt = function(s) {
            var i;
            if (isNaN(i = parseInt(s, 10))) {
              return s;
            } else {
              return i;
            }
          };
          byDefault = function(a, b) {
            return +(a > b) || +(a === b) - 1;
          };
          byNumber = function(a, b) {
            return a - b;
          };
          byType = function(a, b) {
            var ta, tb;
            ta = typeof a;
            tb = typeof b;
            if (ta !== tb) {
              a = ta;
              b = tb;
            }
            return byDefault(a, b);
          };
          byMagic = function(a, b) {
            var d;
            if (isNaN(d = a - b)) {
              return byType(a, b);
            } else {
              return d;
            }
          };
          bySortId = function(a, b) {
            return (a.sort || a.id) - (b.sort || b.id);
          };
          parseIntish = function(c) {
            var i;
            if (isNaN(i = parseInt(c, 10))) {
              return c;
            } else {
              return i;
            }
          };
          stripPrefix = function(s, prefix) {
            if (s.startsWith(prefix)) {
              return s.substr(prefix.length);
            }
          };
          arr = function(a, f) {
            if (f in a) {
              return a[f];
            } else {
              return a[f] = [];
            }
          };
          obj = function(a, f) {
            if (f in a) {
              return a[f];
            } else {
              return a[f] = {};
            }
          };
          inc = function(a, f) {
            if (f in a) {
              return a[f]++;
            } else {
              a[f] = 1;
              return 0;
            }
          };
          Birthdate = constants.categoryName.participant.metricName.birthdate;
          pseudoMetric = {
            indicator: {
              id: 'indicator',
              name: 'indicator',
              type: 'void',
              sort: 0,
              assumed: true
            },
            top: {
              id: 'top',
              category: 'slot',
              name: 'type',
              type: 'top',
              sort: 1,
              options: {
                "false": 'session',
                "true": 'materials'
              }
            },
            name: {
              id: 'name',
              name: 'name',
              type: 'text',
              sort: 2
            },
            date: {
              id: 'date',
              category: 'slot',
              name: 'test date',
              type: 'date',
              description: 'Date on which this session was acquired',
              sort: 3
            },
            release: {
              id: 'release',
              category: 'slot',
              name: 'release',
              type: 'release',
              description: 'Level of data release to which depicted participant(s) consented',
              sort: 4
            },
            age: {
              id: 'age',
              category: Birthdate.category.id,
              name: 'age',
              type: 'age',
              release: constants.release.EXCERPTS,
              sort: Birthdate.id + 0.5,
              description: 'Time between birthdate and test date',
              readonly: true
            },
            summary: {
              id: 'summary',
              category: 'slot',
              name: 'summary',
              type: 'text',
              sort: 2e308,
              readonly: true
            }
          };
          constants.deepFreeze(pseudoMetric);
          getMetric = function(m) {
            return pseudoMetric[m] || constants.metric[m];
          };
          pseudoCategory = {
            slot: {
              id: 'slot',
              name: 'folder',
              not: 'No folders',
              template: ['top', 'name', 'date', 'release'],
              fixed: true
            },
            asset: {
              id: 'asset',
              name: 'file',
              not: 'No files',
              template: ['name'],
              description: 'Files included in this folder',
              fixed: true
            }
          };
          constants.deepFreeze(pseudoCategory);
          getCategory = function(c) {
            return pseudoCategory[c] || constants.category[c];
          };

          /*
           * We use the following types of data structures:
           *   Row = index of slot in slots and rows (i)
           *   Slot_id = Database id of container
           *   Record_id = Database id of record
           *   Category_id = Database id of record category (c)
           *   Count = index of record within category for slot (n)
           *   Metric_id = Database id of metric, or "id" for Record_id, or "age" (m)
           */
          TBody = $element[0].getElementsByTagName("tbody")[0];
          TFoot = $element[0].getElementsByTagName("tfoot")[0];
          Groups = [];
          Cols = [];
          Rows = [];
          Cats = {};
          Order = [];
          Expanded = void 0;
          SlotCount = 0;

          /**
           * Row in spreadsheet with the following properties: <br />
           * <blockquote>  i: Row index <br />
           *   tr: DOM Element tr <br />
           *   [Category_id]: (array of) metric data for category </blockquote>
           * @interface spreadsheet/Row
           */
          Row = (function() {
            function Row(i) {
              this.i = i != null ? i : Rows.length;
              Rows[this.i] = this;
              this.filt = true;
              return;
            }

            Row.prototype.add = function(c, d) {
              var n, v;
              Cats[c] = true;
              if (v = this[c]) {
                if (Array.isArray(v)) {
                  n = v.push(d) - 1;
                } else {
                  this[c] = [v, d];
                  n = 1;
                }
              } else {
                this[c] = d;
                n = 0;
              }
              return n;
            };

            Row.prototype.list = function(c) {
              var v;
              if (v = this[c]) {
                if (Array.isArray(v)) {
                  return v;
                } else {
                  return [v];
                }
              } else {
                return [];
              }
            };

            Row.prototype.count = function(c) {
              var v;
              if (v = this[c]) {
                if (Array.isArray(v)) {
                  return v.length;
                } else {
                  return 1;
                }
              } else {
                return 0;
              }
            };

            Row.prototype.get = function(c, n) {
              var v;
              if (v = this[c]) {
                if (Array.isArray(v)) {
                  return v[n || 0];
                } else if (!n) {
                  return v;
                }
              }
            };

            Row.prototype.set = function(c, n, d) {
              n || (n = 0);
              if (n || Array.isArray(this[c])) {
                if (d != null) {
                  this[c].splice(n, 1, d);
                } else {
                  this[c].splice(n, 1);
                }
              } else {
                this[c] = d;
              }
            };

            Object.defineProperty(Row.prototype, 'key', {
              get: function() {
                return this.get(Key.id);
              }
            });

            return Row;

          })();

          /**
           * Cell in spreadsheet with the following properties: <br />
           * <blockquote>cell: target TD element <br />
           *   id: cell.id <br />
           *   i: Row <br />
           *   n: Count (index of count), optional [0] <br />
           *   m: index into Cols <br />
           *   cols: Groups element <br />
           *   col: Cols element <br />
           *   category: Category <br />
           *   c: Category_id <br />
           *   count: Count[i][c] <br />
           *   metric: Metric <br />
           *   row: Rows[i] <br />
           *   slot: Container <br />
           *   d: Data <br />
           *   record: Record <br />
           *   asset: Asset <br />
           *   v: Data value </blockquote>
           * @interface spreadsheet/Info
           */
          Info = (function() {
            var f, properties, property, v;

            function Info(x) {
              if (typeof x === 'number') {
                this.i = x;
              } else if (x) {
                this.cell = x;
              }
              this.parseId();
              return;
            }

            Info.prototype.parseId = function(i) {
              var s;
              if (!((i != null ? this.id = i : i = this.id) && (i = stripPrefix(i, ID + '-')))) {
                return;
              }
              s = i.split('_');
              switch (s[0]) {
                case 'add':
                case 'more':
                case 'no':
                  this.t = s[0];
                  this.i = parseInt(s[1], 10);
                  this.c = parseIntish(s[2]);
                  break;
                case 'metric':
                  this.t = s[0];
                  this.m = parseInt(s[1], 10);
                  break;
                case 'category':
                  this.t = s[0];
                  this.c = parseIntish(s[1]);
                  break;
                default:
                  this.i = parseInt(s[0], 10);
                  this.m = parseInt(s[1], 10);
                  if (2 in s) {
                    this.n = parseInt(s[2], 10);
                  }
              }
              return true;
            };

            properties = {
              n: function() {
                return 0;
              },
              id: function() {
                var ref1;
                return (ref1 = this.cell) != null ? ref1.id : void 0;
              },
              cols: function() {
                var c;
                c = this.c;
                return this.cols = Groups.find(function(col) {
                  return col.category.id == c;
                });
              },
              col: function() {
                return Cols[this.m];
              },
              category: function() {
                var c;
                if ((c = this.col) != null) {
                  return c.category;
                } else if (this.hasOwnProperty('c')) {
                  return this.category = getCategory(this.c);
                }
              },
              c: function() {
                var ref1;
                return (ref1 = this.category) != null ? ref1.id : void 0;
              },
              metric: function() {
                var ref1;
                return (ref1 = this.col) != null ? ref1.metric : void 0;
              },
              row: function() {
                return Rows[this.i];
              },
              count: function() {
                return this.row.count(this.c);
              },
              tr: function() {
                return this.row.tr;
              },
              d: function() {
                return this.row.get(this.c, this.n);
              },
              p: function() {
                var c, cls;
                cls = 'ss-';
                if (typeof (c = this.c) !== 'number') {
                  cls += c.charAt(0);
                }
                return cls;
              },
              slot: function() {
                var ref1;
                return (ref1 = this.row.get('slot', this.c === 'slot' ? this.n : void 0)) != null ? ref1.slot : void 0;
              },
              record: function() {
                var ref1;
                return (ref1 = this.d) != null ? ref1.record : void 0;
              },
              asset: function() {
                var ref1;
                return (ref1 = this.d) != null ? ref1.asset : void 0;
              },
              v: function() {
                var d, m;
                if ((d = this.d) && (m = this.metric)) {
                  return d[m.id];
                }
              }
            };

            property = function(v, f) {
              return {
                get: f,
                set: function(x) {
                  Object.defineProperty(this, v, {
                    value: x,
                    writable: true,
                    configureable: true,
                    enumerable: true
                  });
                }
              };
            };

            for (v in properties) {
              f = properties[v];
              properties[v] = property(v, f);
            }

            Object.defineProperties(Info.prototype, properties);

            return Info;

          })();
          parseId = function(el) {
            var info;
            if (el.tagName !== 'TD') {
              return;
            }
            info = new Info(el);
            if (info.c != null) {
              return info;
            }
          };

          /**
           * Populate data structures and fill Cols and Groups from volume metrics
           * @interface spreadsheet/populateCols
           */
          populateCols = function(slot) {
            var all, cats, i, partindex;
            Cols = [];
            all = Object.keys(volume.metrics).sort(byNumber).map(function(i) {
              return constants.category[i];
            });
            if (slot) {
              slot = void 0;
              cats = all.filter(function(c) {
                var ref1;
                return $scope.showGlobal || ((ref1 = Cats[c.id]) != null ? ref1 : Editing);
              });
              cats.unshift(pseudoCategory.asset);
              cats.unshift(pseudoCategory.slot);
              $scope.anyGlobal = all.some(function(c) {
                return Cats[c.id] === false;
              });
            } else {
              slot = [pseudoMetric.summary];
              cats = [Key, pseudoCategory.slot];
              $scope.anyGlobal = false;
            }
            $scope.groups = Groups = cats.map(function(category) {
              var l, metrics, si;
              metrics = (category.template || volume.metrics[category.id]).map(getMetric);
              if (!Editing && indexOf.call(metrics, Birthdate) >= 0) {
                (slot || metrics).push(pseudoMetric.age);
              }
              if (slot && category.id === 'slot') {
                metrics.push.apply(metrics, slot);
              }
              metrics.sort(bySortId);
              si = Cols.length;
              Cols.push.apply(Cols, metrics.map(function(m) {
                return {
                  category: category,
                  metric: m
                };
              }));
              l = metrics.length;
              Cols[si].first = Cols[si + l - 1].last = l;
              return {
                category: category,
                metrics: metrics,
                start: si
              };
            });
            $scope.cols = Cols;
            $scope.views = all;
            partindex = false;
            i = 0;
            while (i < $scope.views.length) {
              if ($scope.views[i].name === 'participant') {
                partindex = true;
              }
              i++;
            }
            if (!Editing || partindex === false) {
              $scope.views.unshift(pseudoCategory.slot);
            } else {
              $scope.views.splice(1, 0, pseudoCategory.slot);
            }
          };
          populateSlotData = function(s) {
            var d, ref1;
            d = {
              slot: s,
              id: s.id,
              top: s.top || false,
              name: s.name,
              date: s.date,
              release: (ref1 = s.release) != null ? ref1 : -1
            };
            if (s.top === 'global') {
              d.global = true;
              d.summary = constants.message('global.name') + " (only displayed in volume description)";
            }
            return d;
          };
          populateRecordData = function(r) {
            var d;
            d = Object.create(r.measures);
            d.record = r;
            d.id = r.id;
            return d;
          };
          populateAssetData = function(a) {
            var d;
            d = {
              asset: a,
              id: a.id,
              name: a.name
            };
            return d;
          };

          /**
           * Fill all Data values for Row i
           * @interface spreadsheet/populateSlot
           */
          populateSlot = function(slot) {
            var asset, assetId, d, len, p, record, ref1, ref2, row, rr;
            row = new Row();
            row.add('slot', populateSlotData(slot));
            ref1 = slot.records;
            for (p = 0, len = ref1.length; p < len; p++) {
              rr = ref1[p];
              if (!((record = rr.record))) {
                continue;
              }
              d = populateRecordData(record);
              if ('age' in rr) {
                d.age = rr.age;
              }
              row.add(record.category, d);
            }
            ref2 = slot.assets;
            for (assetId in ref2) {
              asset = ref2[assetId];
              row.add('asset', populateAssetData(asset));
            }
            return row;
          };
          populateSlots = function() {
            var ci, len, name1, p, record, ref1, ref2, results, rr, slot;
            SlotCount = -1;
            ref1 = volume.containers;
            results = [];
            for (ci in ref1) {
              slot = ref1[ci];
              SlotCount++;
              if (slot.top === 'global') {
                ref2 = slot.records;
                for (p = 0, len = ref2.length; p < len; p++) {
                  rr = ref2[p];
                  if (!((record = rr.record))) {
                    continue;
                  }
                  record.global = true;
                  Cats[name1 = record.category] || (Cats[name1] = false);
                }
                continue;
              }
              results.push(populateSlot(slot));
            }
            return results;
          };
          populateRecord = function(record) {
            var row;
            row = new Row();
            row.add(record.category, populateRecordData(record));
            return row;
          };
          populateRecords = function() {
            var any, ci, d, len, nog, nor, p, r, record, records, recs, ref1, ref2, row, rr, rrr, slot;
            records = {};
            nog = void 0;
            ref1 = volume.records;
            for (r in ref1) {
              record = ref1[r];
              if (!(record.category === Key.id)) {
                continue;
              }
              if (nog) {
                nog++;
              } else {
                nog = 1;
              }
              delete record.global;
              records[r] = populateRecord(record);
            }
            SlotCount = -1;
            nor = void 0;
            ref2 = volume.containers;
            for (ci in ref2) {
              slot = ref2[ci];
              SlotCount++;
              recs = slot.records;
              any = false;
              for (p = 0, len = recs.length; p < len; p++) {
                rr = recs[p];
                if (!((row = records[rr.id]))) {
                  continue;
                }
                d = populateSlotData(slot);
                if ('age' in rr) {
                  d.age = rr.age;
                }
                if (d.global) {
                  if (!rr.record.global) {
                    rr.record.global = true;
                    nog--;
                  }
                  row.partial = true;
                } else {
                  d.summary = ((function() {
                    var len1, q, results;
                    results = [];
                    for (q = 0, len1 = recs.length; q < len1; q++) {
                      rrr = recs[q];
                      if (rrr.id !== rr.id) {
                        results.push(rrr.record.displayName);
                      }
                    }
                    return results;
                  })()).join(', ');
                  row.partial || (row.partial = !Segment.isFull(rr.segment));
                }
                row.add('slot', d);
                any = true;
              }
              if (!(any || slot.top === 'global')) {
                nor || (nor = new Row());
                d = populateSlotData(slot);
                d.summary = ((function() {
                  var len1, q, results;
                  results = [];
                  for (q = 0, len1 = recs.length; q < len1; q++) {
                    rrr = recs[q];
                    results.push(rrr.record.displayName);
                  }
                  return results;
                })()).join(', ');
                nor.add('slot', d);
              }
            }
            if (nog === 0 && nor) {
              return delete Rows[nor.i];
            }
          };

          /**
           * Generate HTML and add or replace the text contents of cell c for measure/type m with value v
           * @interface spreadsheet/generateText
           */
          generateText = function(info) {
            var a, asset, cell, cls, cn, del, delicon, icon, slot, t, v;
            $(cell = info.cell).empty();
            cell.className = '';
            v = info.v;
            slot = info.slot;
            if (info.col.first && info.d) {
              if (info.c === 'asset') {
                cell.classList.add('clickable');
                cell.onclick = function(event) {
                  return $location.url(Editing ? slot.editRoute(t) : slot.route(t));
                };
                a = cell.appendChild(document.createElement('a'));
                icon = a.appendChild(document.createElement('img'));
                asset = info.asset;
                icon.src = asset.icon;
                icon.className = "format hint-format-" + asset.format.extension;
                t = {
                  asset: asset.id
                };
                a.setAttribute('href', Editing ? slot.editRoute(t) : slot.route(t));
                icon = cell.appendChild(document.createElement('span'));
                icon.className = 'icon release ' + constants.release[asset.release] + ' hint-release-' + constants.release[asset.release];
              } else {
                if (Editing && Key.id === info.c) {
                  cell.classList.add('folder-type');
                  cell.classList.add('clickable');
                  del = cell.appendChild(document.createElement('a'));
                  del.className = 'clickable trash icon';
                  $(del).on('click', $scope.$lift(clickRemove));
                }
                if (info.c === 'slot') {
                  a = cell.appendChild(document.createElement('a'));
                  a.className = "session icon hint-action-slot";
                  a.setAttribute('href', Editing ? slot.editRoute() : slot.route());
                }
              }
            }
            switch (info.metric.id) {
              case 'name':
                if (v == null) {
                  v = '';
                }
                break;
              case 'release':
                if (v >= 0 || !(slot != null ? slot.top : void 0)) {
                  cn = constants.release[v];
                  cell.className = cn + ' release icon hint-release-' + cn;
                }
                if (slot != null ? slot.top : void 0) {
                  cell.classList.add('null');
                }
                v = '';
                break;
              case 'date':
                if (slot != null ? slot.top : void 0) {
                  cell.classList.add('null');
                }
                break;
              case 'age':
                v = display.formatAge(v);
                break;
              case 'top':
                if (v != null) {
                  v = info.metric.options[v];
                } else if (Editing && info.col.first) {
                  cell.classList.add('add');
                  cell.classList.add('button');
                  v = 'add folder';
                }
                break;
              case 'summary':
                if (info.d.global && Editing) {
                  cell.classList.add('spreadsheet-global-record-cell');
                  del = cell.appendChild(document.createElement('a'));
                  del.className = 'button mini global-record white icon-text';
                  delicon = del.appendChild(document.createElement('span'));
                  delicon.className = 'icon trash';
                  del.appendChild(document.createTextNode('Remove'));
                  $(del).on('click', $scope.$lift(clickGlobal));
                }
                break;
              default:
                if (info.metric.type === 'void' && info.d) {
                  if (Editing && Key.id !== info.c) {
                    cell.className = 'clickable';
                  }
                  v = info.metric.name;
                }
            }
            if (info.metric.long) {
              cell.classList.add('long');
            }
            if (v != null) {
              cell.classList.remove('blank');
            } else {
              cell.classList.add('blank');
              v = info.metric.assumed || '';
            }
            cell.appendChild(document.createTextNode(v));
            cell.id = info.id;
            if (info.d) {
              cell.classList.add(cls = info.p + info.d.id);
              cell.classList.add(cls + '_' + info.metric.id);
            }
          };
          generateAdd = function(info, td) {
            var width;
            info.m = info.cols.start;
            width = info.width || info.cols.metrics.length;
            if (info.metric.type === 'top' || typeof info.metric.id === 'number' && info.metric.type !== 'void') {
              info.id = ID + '-' + info.i + '_' + info.cols.start + (info.hasOwnProperty('n') ? '_' + info.n : '');
              info.cell = info.tr.appendChild(document.createElement('td'));
              generateText(info);
              info.tr.insertBefore(info.cell, td);
              if (width > 1) {
                td.setAttribute("colspan", width - 1);
                td.classList.add('prompt');
                if (info.c !== 'slot') {
                  return td.appendChild(document.createTextNode("\u2190 add " + info.category.name));
                }
              } else {
                return info.tr.removeChild(td);
              }
            } else {
              td.setAttribute("colspan", width);
              td.classList.add('add');
              td.classList.add('clickable');
              td.id = ID + '-add_' + info.i + '_' + info.c;
              return td.appendChild(document.createTextNode(Editing && info.metric.type !== 'void' ? "add " + info.category.name : info.category.not));
            }
          };
          generateMultiple = function(info) {
            var add, addicon, t, td, width;
            t = info.count;
            if ((info.hasOwnProperty('n') ? info.n < t : t === 1)) {
              return;
            }
            td = info.tr.appendChild(document.createElement('td'));
            width = info.cols.metrics.length;
            td.setAttribute("colspan", width);
            if (info.hasOwnProperty('n') || t <= 1) {
              td.className = 'null';
              if (!info.n || info.n === t) {
                if (Editing && info.c !== 'slot' && info.c !== Key.id) {
                  generateAdd(info, td);
                } else if (!info.n) {
                  td.appendChild(document.createTextNode(info.category.not));
                  if (Editing && info.c === 'slot' && Key.id !== 'slot' && Key.id !== 1) {
                    td.classList.add('spreadsheet-global-record-cell');
                    add = td.appendChild(document.createElement('a'));
                    add.className = 'button mini white global-record icon-text';
                    addicon = add.appendChild(document.createElement('span'));
                    addicon.className = 'add icon';
                    add.appendChild(document.createTextNode('Apply to whole volume'));
                    $(add).on('click', $scope.$lift(clickGlobal));
                  }
                  td.id = ID + '-no_' + info.i + '_' + info.c;
                }
              }
            } else {
              td.className = 'more';
              td.id = ID + '-more_' + info.i + '_' + info.c;
              if (Editing && info.c === 'slot' && Key.id !== 'slot' && t === SlotCount && !info.row.partial && info.row.key) {
                td.classList.add('spreadsheet-global-record-cell');
                add = td.appendChild(document.createElement('a'));
                add.className = 'button mini yellow global-record icon-text hint-action-globalrecord';
                add.appendChild(document.createTextNode('Convert to ' + constants.message('global.name').toLowerCase() + ' (irreversible)'));
                $(add).on('click', $scope.$lift(clickGlobal));
              }
              td.appendChild(document.createTextNode(t + " " + info.category.name + "s"));
            }
            return td;
          };

          /**
           * Add all the measure tds to row i for count n, record r
           * @interface spreadsheet/generateRecord
           */
          generateRecord = function(info) {
            var cls, generateCell, l, mi, n, p, post, pre, q, ref1, ref2, td;
            if (!(l = info.cols.metrics.length)) {
              return;
            }
            if (td = generateMultiple(info)) {
              if (!info.hasOwnProperty('n')) {
                cls = info.p;
                for (n = p = 0, ref1 = info.count - 1; p <= ref1; n = p += 1) {
                  td.classList.add(cls + info.row.get(info.c, n).id);
                }
              }
              return;
            }
            pre = ID + '-' + info.i + '_';
            post = info.hasOwnProperty('n') ? '_' + info.n : '';
            generateCell = function(i) {
              info.m = info.cols.start + i;
              info.id = pre + info.m + post;
              info.cell = info.tr.appendChild(document.createElement('td'));
              generateText(info);
            };
            if (info.category.id === 'slot' && info.d.global) {
              generateCell(l - 1);
              info.cell.setAttribute("colspan", l);
              return;
            }
            for (mi = q = 0, ref2 = l - 1; q <= ref2; mi = q += 1) {
              generateCell(mi);
            }
          };

          /**
           * Fill out Rows[i].
           * @interface spreadsheet/generateRow
           */
          generateRow = function(i) {
            var col, info, len, p, row, tr;
            info = new Info(i);
            row = info.row;
            if (row.tr) {
              $(row.tr).empty();
              row.tr;
            } else {
              row.tr = document.createElement('tr');
            }
            tr = row.tr;
            tr.id = ID + '_' + i;
            tr.data = i;
            for (p = 0, len = Groups.length; p < len; p++) {
              col = Groups[p];
              info.category = (info.cols = col).category;
              generateRecord(info);
            }
          };
          generateFoot = function() {
            var info, row, td;
            if (row = Rows[-1]) {
              $(Rows[-1].tr).empty();
            } else {
              row = new Row(-1);
              row.tr = TFoot.appendChild(document.createElement('tr'));
              row.tr.id = ID + '_add';
            }
            info = new Info(row.i);
            info.cols = Groups[0];
            info.category = Key;
            td = info.tr.appendChild(document.createElement('td'));
            td.setAttribute("colspan", info.cols.metrics.length);
            td.className = 'null';
            info.width = Cols.length;
            return generateAdd(info, td);
          };
          $scope.$on('displayService-toggleAge', function() {
            var i, info, len, len1, m, mi, mid, n, p, pre, premid, q, results;
            info = new Info();
            results = [];
            for (mi = p = 0, len = Cols.length; p < len; mi = ++p) {
              m = Cols[mi];
              if (m.metric.id !== 'age') {
                continue;
              }
              info.m = mi;
              pre = ID + '-';
              mid = '_' + mi;
              for (n = q = 0, len1 = Order.length; q < len1; n = ++q) {
                i = Order[n];
                info.i = i;
                if (info.v) {
                  info.cell = document.getElementById(pre + i + mid);
                  if (info.cell) {
                    generateText(info);
                  }
                }
              }
              if ((Expanded != null ? Expanded.c : void 0) === info.c && Expanded.count > 1) {
                info.i = Expanded.i;
                premid = pre + info.i + mid + '_';
                results.push((function() {
                  var ref1, results1, w;
                  results1 = [];
                  for (n = w = 0, ref1 = Expanded.count - 1; w <= ref1; n = w += 1) {
                    info.n = n;
                    if (info.v) {
                      info.cell = document.getElementById(premid + n);
                      if (info.cell) {
                        results1.push(generateText(info));
                      } else {
                        results1.push(void 0);
                      }
                    } else {
                      results1.push(void 0);
                    }
                  }
                  return results1;
                })());
              } else {
                results.push(void 0);
              }
            }
            return results;
          });

          /**
           * Place DOM elements and place all rows into spreadsheet.
           * @interface spreadsheet/fill
           */
          fill = function() {
            var i, len, n, next, p, tr;
            collapse();
            n = 0;
            next = TBody.firstChild;
            for (p = 0, len = Order.length; p < len; p++) {
              i = Order[p];
              tr = Rows[i].tr;
              if (Rows[i].filt && n++ < Limit) {
                if (next === tr) {
                  next = next.nextSibling;
                } else if (next) {
                  TBody.insertBefore(tr, next);
                } else {
                  TBody.appendChild(tr);
                }
              } else {
                if (next === tr) {
                  next = next.nextSibling;
                  TBody.removeChild(tr);
                } else if (tr.parentNode) {
                  TBody.removeChild(tr);
                }
              }
            }
            if (n > Limit) {
              $scope.more = n;
            } else {
              delete $scope.more;
            }
          };

          /**
           * Populate order based on compare function applied to values.
           * @interface spreadsheet/sort
           */
          sort = function(get) {
            var i, idx, len, o, p;
            idx = new Array(Order.length);
            for (i = p = 0, len = Order.length; p < len; i = ++p) {
              o = Order[i];
              idx[o] = i;
            }
            Order.sort(function(i, j) {
              return byMagic(get(i), get(j)) || idx[i] - idx[j];
            });
          };
          currentSort = void 0;
          currentSortDirection = false;

          /**
           * Sort by column
           * @interface spreadsheet/sortBy
           */
          sortBy = function(col, dir) {
            var c, m;
            if (currentSort === col && (dir == null)) {
              currentSortDirection = !currentSortDirection;
              Order.reverse();
            } else {
              if (!currentSort) {
                Order.sort(function(i, j) {
                  var ref1, ref2;
                  return byNumber((ref1 = Rows[i].key) != null ? ref1.id : void 0, (ref2 = Rows[j].key) != null ? ref2.id : void 0);
                });
              }
              c = col.category.id;
              m = col.metric.id;
              sort(function(i) {
                var ref1;
                return (ref1 = Rows[i].get(c)) != null ? ref1[m] : void 0;
              });
              currentSort = col;
              if (dir) {
                currentSortDirection = true;
                Order.reverse();
              } else {
                currentSortDirection = false;
              }
            }
          };
          $scope.colClasses = function(col) {
            var cls;
            cls = [];
            if (typeof col === 'object') {
              if (col.first) {
                cls.push('first');
              }
              if (col.last) {
                cls.push('last');
              }
            }
            cls.push('sort');
            if (currentSort === col) {
              cls.push('intransitive sort-' + (currentSortDirection ? 'desc' : 'asc'));
            } else {
              cls.push('intransitive sortable');
            }
            return cls;
          };

          /**
           * Backend saving
           * @interface spreadsheet/saveRun
           */
          setFocus = void 0;
          saveRun = function(cell, run) {
            messages.clear(cell);
            cell.classList.remove('error');
            cell.classList.add('saving');
            return run.then(function(res) {
              cell.classList.remove('saving');
              $scope.form.$setPristine();
              return res;
            }, function(res) {
              cell.classList.remove('saving');
              cell.classList.add('error');
              messages.addError({
                body: 'Could not save data',
                report: res,
                owner: cell
              });
            });
          };
          addRow = function(row) {
            Order.push(row.i);
            generateRow(row.i);
            TBody.appendChild(row.tr);
            return row;
          };
          createSlot = function(info) {
            return saveRun(info.cell, volume.createContainer().then(function(slot) {
              arr(slot, 'records');
              return addRow(populateSlot(slot));
            }));
          };
          createRecord = function(info) {
            return saveRun(info.cell, volume.createRecord(info.c || void 0).then(function(record) {
              return addRow(populateRecord(record));
            }));
          };
          createNew = function(info) {
            if (info.c === 'slot') {
              return createSlot(info);
            } else if (typeof info.c === 'number') {
              return createRecord(info);
            }
          };
          removeRow = function(i) {
            var row;
            unedit(false);
            row = Rows[i];
            delete Rows[i];
            if (row.tr.parentNode) {
              TBody.removeChild(row.tr);
            }
            Order.remove(i);
            if ((Expanded != null ? Expanded.i : void 0) === i) {
              Expanded = void 0;
            }
          };
          removeSlot = function(info) {
            return saveRun(info.cell, info.slot.remove().then(function(done) {
              if (!done) {
                messages.add({
                  body: "You must remove all associated records and files before you can remove this folder.",
                  type: 'red',
                  owner: info.cell
                });
                return;
              }
              removeRow(info.i);
            }));
          };
          removeRecord = function(info) {
            return saveRun(info.cell, info.record.remove().then(function(done) {
              if (!done) {
                messages.add({
                  body: "You must remove all associated sessions and materials before you can remove this " + info.category.name + ".",
                  type: 'red',
                  owner: info.cell
                });
                return;
              }
              removeRow(info.i);
            }));
          };
          setRecord = function(info, record) {
            var act, add;
            add = function() {
              if (record) {
                return info.slot.addRecord(record);
              } else if (record !== null) {
                return info.slot.newRecord(info.c || '');
              }
            };
            act = info.record ? info.slot.removeRecord(info.record).then(add) : add();
            return saveRun(info.cell, act.then(function(rr) {
              var i, o, r, ref1;
              record = rr != null ? rr.record : void 0;
              o = info.d;
              if (record) {
                r = populateRecordData(record);
                if (o) {
                  info.row.set(info.c, info.n, r);
                } else {
                  info.n = info.row.add(info.c, r);
                }
              } else {
                info.row.set(info.c, info.n, void 0);
              }
              collapse();
              generateRow(info.i);
              if (info.n) {
                expand(info);
              }
              if (record && setFocus === (i = info.id) && (i = (ref1 = document.getElementById(i)) != null ? ref1.nextSibling : void 0) && (i = parseId(i))) {
                select(i);
              }
              setFocus = void 0;
              return record;
            }));
          };
          updateDatum = function(info, v) {
            var len, li, p, ref1;
            info.v = v;
            info.d[info.metric.id] = v;
            if (info.metric.id === 'top') {
              collapse();
              generateRow(info.i);
              expand(info);
            } else if (info.category.fixed) {
              generateText(info);
            } else {
              ref1 = TBody.getElementsByClassName(info.p + info.d.id + '_' + info.metric.id);
              for (p = 0, len = ref1.length; p < len; p++) {
                li = ref1[p];
                info.cell = li;
                generateText(info);
              }
            }
          };
          saveDatum = function(info, v) {
            var data, t;
            if (info.c === 'slot') {
              data = {};
              if (info.metric.id === 'top') {
                v = v === 'true';
              }
              data[info.metric.id] = v != null ? v : '';
              if (info.slot[info.metric.id] == v) {
                return;
              }
              return saveRun(info.cell, info.slot.save(data).then(function() {
                updateDatum(info, v);
              }));
            } else if (info.c === 'asset') {
              data = {};
              t = info.metric.id;
              data[t] = v != null ? v : '';
              if (info.asset[t] === data[t]) {
                return;
              }
              return saveRun(info.cell, info.asset.save(data).then(function() {
                updateDatum(info, v);
              }));
            } else {
              if (info.record.measures[info.metric.id] === v) {
                return;
              }
              return saveRun(info.cell, info.record.measureSet(info.metric.id, v).then(function() {
                updateDatum(info, v);
              }));
            }
          };

          /**
           * Interaction and collapse any expanded row.
           * @interface spreadsheet/collapse
           */
          collapse = function() {
            var el, i, t, tr;
            if (!Expanded) {
              return;
            }
            i = Expanded.i;
            tr = Expanded.tr;
            Expanded = void 0;
            tr.classList.remove('expand');
            t = 0;
            while ((el = tr.nextSibling) && el.data === i) {
              t++;
              $(el).remove();
            }
            el = tr.firstChild;
            while (el) {
              el.removeAttribute("rowspan");
              el = el.nextSibling;
            }
            return t;
          };

          /**
           * Expand (or collapse) a row
           * @interface spreadsheet/expand
           */
          expand = function(info) {
            var el, max, n, next, p, ref1, ref2, start;
            if ((Expanded != null ? Expanded.i : void 0) === info.i && Expanded.c == info.c) {
              if (info.t === 'more') {
                collapse();
              }
              return;
            }
            collapse();
            if (info.i < 0) {
              return;
            }
            Expanded = new Info(info.i);
            Expanded.c = info.c;
            info.tr.classList.add('expand');
            max = Expanded.count;
            if (Editing && Expanded.c !== Key.id && Expanded.c !== 'slot') {
              max++;
            }
            if (max <= 1) {
              return;
            }
            next = info.tr.nextSibling;
            start = Expanded.count === 1;
            for (n = p = ref1 = +start, ref2 = max - 1; p <= ref2; n = p += 1) {
              Expanded.n = n;
              Expanded.tr = TBody.insertBefore(document.createElement('tr'), next);
              Expanded.tr.data = Expanded.i;
              Expanded.tr.className = 'expand';
              generateRecord(Expanded);
            }
            Expanded.tr = info.tr;
            if (!start) {
              max++;
            }
            el = info.tr.firstChild;
            while (el) {
              info = new Info(el);
              if (info.c != Expanded.c) {
                el.setAttribute("rowspan", max);
              }
              el = el.nextSibling;
            }
          };
          save = function(info, type, value) {
            var c, r, ref1, ref2, u, v;
            if (value === '') {
              value = void 0;
            } else {
              switch (type) {
                case 'record':
                  if (value === 'new') {
                    setRecord(info);
                  } else if (value === 'remove') {
                    if (info.d) {
                      setRecord(info, null);
                    }
                  } else if (v = stripPrefix(value, 'add_')) {
                    u = v.indexOf('_');
                    info.metric = constants.metric[v.slice(0, u)];
                    v = v.slice(u + 1);
                    setRecord(info).then(function(r) {
                      info.record = r;
                      if (r) {
                        saveDatum(info, v);
                      }
                    });
                  } else if (!isNaN(v = parseInt(value, 10))) {
                    if (v !== ((ref1 = info.d) != null ? ref1.id : void 0)) {
                      setRecord(info, volume.records[v]);
                    }
                  }
                  return;
                case 'options':
                  if (value) {
                    c = optionCompletions(value);
                  }
                  if (c != null ? c.length : void 0) {
                    value = c[0];
                  }
              }
            }
            if (type === 'ident') {
              r = editScope.identCompleter(value);
              if (Array.isArray(r)) {
                if ((ref2 = r.find(function(o) {
                  return o["default"];
                })) != null) {
                  ref2.run(info);
                }
              }
            } else if (info.i === -1) {
              if (!value) {
                return;
              }
              createNew(info).then(function(row) {
                saveDatum(new Info(row.tr.firstChild), value);
              });
            } else {
              saveDatum(info, value);
            }
          };
          editScope = $scope.$new(true);
          editScope.constants = constants;
          editInput = editScope.input = {};
          editCellTemplate = $compile($templateCache.get('volume/spreadsheetEditCell.html'));
          editCell = void 0;
          unedit = function(event) {
            var cell, edit, info;
            if (!(edit = editCell)) {
              return;
            }
            editCell = void 0;
            cell = edit.parentNode;
            $(edit).remove();
            if (!(cell != null ? cell.parentNode : void 0)) {
              return;
            }
            cell.classList.remove('editing');
            tooltips.clear();
            info = new Info(cell);
            if (event !== false) {
              save(info, editScope.type, editInput.value);
            }
            return info;
          };
          recordDescription = function(r) {
            var k;
            k = Object.keys(r.measures);
            if (k.length) {
              return k.sort(byNumber).map(function(m) {
                return r.measures[m];
              }).join(', ');
            } else {
              return '[' + r.id + ']';
            }
          };
          edit = function(info) {
            var c, e, found, len, m, mf, mi, ms, o, p, r, ref1, ref10, ref2, ref3, ref4, ref5, ref6, ref7, ref8, ref9, ri, rs, v;
            switch (info.t) {
              case void 0:
                if (info.c === 'asset') {
                  $location.url(info.slot.editRoute({
                    asset: info.d.id
                  }));
                  return;
                }
                m = info.metric;
                if (m.readonly) {
                  return;
                }
                editScope.type = m.type;
                mi = m.id;
                editScope.options = m.options;
                if (m.type === 'void') {
                  editScope.type = 'record';
                  editScope.options = {
                    remove: info.category.not
                  };
                  editScope.options[v = info.d.id] = m.name;
                } else if (info.c === 'slot') {
                  if (((ref1 = info.slot) != null ? ref1.top : void 0) && (mi === 'date' || mi === 'release')) {
                    return;
                  }
                  v = (ref2 = info.slot) != null ? ref2[mi] : void 0;
                  if (mi === 'top' && info.slot) {
                    v = !!v;
                  }
                  if (mi === 'release') {
                    v = (v != null ? v : -1) + '';
                  }
                } else if (info.c === 'asset') {
                  v = info.asset[mi];
                } else {
                  v = (ref3 = info.record) != null ? ref3.measures[mi] : void 0;
                  if (info.col.first && info.category !== Key) {
                    editScope.type = 'ident';
                    editScope.info = info;
                    rs = [];
                    mf = function(r) {
                      return function(m) {
                        return r.measures[m];
                      };
                    };
                    ref4 = volume.records;
                    for (ri in ref4) {
                      r = ref4[ri];
                      if (r.category === info.c && !info.row.list(info.c).some(function(d) {
                        return d.id == ri;
                      }) && !r.global) {
                        rs.push({
                          r: r,
                          v: ((ref5 = r.measures[mi]) != null ? ref5 : '').toLowerCase(),
                          d: recordDescription(r)
                        });
                      }
                    }
                    editScope.records = rs.sort(function(a, b) {
                      return byMagic(a.v, b.v);
                    });
                  } else if (m.options) {
                    editScope.type = 'options';
                  } else if (m.long) {
                    editScope.type = 'long';
                  }
                }
                editInput.value = (v != null ? v : '') + '';
                break;
              case 'add':
                if (info.i === -1) {
                  createNew(info);
                  return;
                }
                if (info.c === 'asset') {
                  $location.url(info.slot.editRoute() + '?file=open');
                  return;
                }
                c = info.category;
                editInput.value = ((ref6 = (ref7 = info.d) != null ? ref7.id : void 0) != null ? ref6 : 'remove') + '';
                editScope.type = 'record';
                editScope.options = {
                  "new": 'Create new ' + c.name,
                  remove: c.not
                };
                ref8 = volume.records;
                for (ri in ref8) {
                  r = ref8[ri];
                  if (r.category === c.id && (!info.row.list(info.c).some(function(d) {
                    return d.id == ri;
                  }) || ri == editInput.value) && !r.global) {
                    editScope.options[ri] = r.displayName;
                  }
                }
                ms = info.cols.metrics;
                if (ms.length === 1) {
                  if (ms[0].type === 'void' && Object.keys(editScope.options).length > 2) {
                    delete editScope.options['new'];
                  } else if (ms[0].options) {
                    delete editScope.options['new'];
                    ref9 = ms[0].options;
                    for (p = 0, len = ref9.length; p < len; p++) {
                      o = ref9[p];
                      found = false;
                      ref10 = volume.records;
                      for (ri in ref10) {
                        r = ref10[ri];
                        if (r.category === c.id && r.measures[m.id] === o) {
                          found = true;
                          break;
                        }
                      }
                      if (!found) {
                        editScope.options['add_' + ms[0].id + '_' + o] = o;
                      }
                    }
                  }
                }
                break;
              default:
                return;
            }
            e = editCellTemplate(editScope, function(e) {
              info.cell.insertBefore(editCell = e[0], info.cell.firstChild);
              info.cell.classList.add('editing');
            });
            e.on('click', function($event) {
              $event.stopPropagation();
            });
            tooltips.clear();
            $timeout(function() {
              var input;
              input = e.find('[name=edit]');
              input.filter('input,textarea').focus().select();
              input.filter('select').focus().one('change', $scope.$lift(editScope.unedit));
            });
          };
          unselect = function() {
            styles.clear();
            unedit(false);
          };
          $scope.$on('$destroy', unselect);
          select = function(info) {
            var c, len, p, ref1;
            styles.clear();
            unedit();
            expand(info);
            if (!info.t) {
              ref1 = info.cell.classList;
              for (p = 0, len = ref1.length; p < len; p++) {
                c = ref1[p];
                if (!(c.startsWith('ss-'))) {
                  continue;
                }
                styles.set('.' + c + '{background-color:' + (c.includes('_', 4) ? 'rgba(243,180,140,0.7)' : 'rgba(243,180,140,0.4)') + ';\n text-}');
                info.cell.classList.add('selected');
              }
            }
            if (Editing) {
              edit(info);
            } else if (info.category) {
              $scope.filter.add(info);
            }
          };
          $scope.click = function(event) {
            var info, ref1;
            if (!(info = parseId(event.target))) {
              return;
            }
            select(info);
            if (((ref1 = info.metric) != null ? ref1.id : void 0) === 'age') {
              display.toggleAge();
            }
          };
          doneEdit = function(event, info) {
            var c, i, ref1, ref2;
            if (info && event && event.$key === 'Tab') {
              setFocus = !event.shiftKey && info.cell.id;
              c = info.cell;
              while (true) {
                c = event.shiftKey ? c.previousSibling : c.nextSibling;
                if (!(c && (i = parseId(c)))) {
                  return;
                }
                if (!(((ref1 = i.category) != null ? ref1.id : void 0) === 'asset' || ((ref2 = i.metric) != null ? ref2.type : void 0) === 'void')) {
                  break;
                }
              }
              select(i);
            }
          };
          editScope.unedit = function(event) {
            doneEdit(event, unedit(event));
            if (event) {
              event.preventDefault();
            }
            return false;
          };
          editSelect = function(event) {
            editInput.value = this.text;
            editScope.unedit(event);
          };
          editScope.identCompleter = function(input) {
            var add, defd, info, inputl, len, o, os, p, r, rs, set, x;
            info = editScope.info;
            o = [];
            defd = false;
            add = function(t, f, d) {
              o.push({
                text: t,
                select: function(event) {
                  info = unedit(false);
                  f(info);
                  doneEdit(event, info);
                },
                run: f,
                "default": d && !defd
              });
              return defd || (defd = d);
            };
            if (info.d) {
              if (input === info.record.measures[info.metric.id]) {
                add("Keep " + info.record.displayName, function() {}, true);
              }
              if (!input) {
                add("Remove " + info.record.displayName + " from this session", function(info) {
                  return setRecord(info, null);
                }, true);
              }
            }
            if (!info.d || input && input !== info.record.measures[info.metric.id]) {
              inputl = (input != null ? input : '').toLowerCase();
              set = function(r) {
                return function(info) {
                  return setRecord(info, r);
                };
              };
              rs = (function() {
                var len, p, ref1, results;
                ref1 = editScope.records;
                results = [];
                for (p = 0, len = ref1.length; p < len; p++) {
                  r = ref1[p];
                  if (r.v.startsWith(inputl)) {
                    results.push(r);
                  }
                }
                return results;
              })();
              for (p = 0, len = rs.length; p < len; p++) {
                r = rs[p];
                add("Use " + info.category.name + ' ' + r.d, set(r.r), input && rs.length === 1 || r.v === inputl);
              }
              os = info.metric.options ? (function() {
                var len1, q, ref1, results;
                ref1 = info.metric.options;
                results = [];
                for (q = 0, len1 = ref1.length; q < len1; q++) {
                  x = ref1[q];
                  if (x.toLowerCase().startsWith(inputl)) {
                    results.push(x);
                  }
                }
                return results;
              })() : [];
              if (input && !os.length) {
                os = [input];
              }
              os.forEach(function(i) {
                if (info.d) {
                  add("Change all " + info.record.displayName + " " + info.metric.name + " to '" + i + "'", function(info) {
                    return saveDatum(info, i);
                  }, input && !rs.length && os.length === 1 || i === input);
                }
                return add("Create new " + info.category.name + " with " + info.metric.name + " '" + i + "'", function(info) {
                  return setRecord(info).then(function(r) {
                    info.record = r;
                    if (r) {
                      saveDatum(info, i);
                    }
                  });
                }, input && !rs.length && os.length === 1 || i === input);
              });
            }
            if (o.length) {
              return o;
            } else {
              return input;
            }
          };
          optionCompletions = function(input) {
            var i, len, o, p, ref1, results;
            i = input.toLowerCase();
            ref1 = editScope.options;
            results = [];
            for (p = 0, len = ref1.length; p < len; p++) {
              o = ref1[p];
              if (o.toLowerCase().startsWith(i)) {
                results.push(o);
              }
            }
            return results;
          };
          editScope.optionsCompleter = function(input) {
            var i, len, match, o, p, results;
            match = optionCompletions(input);
            switch (match.length) {
              case 0:
                return input;
              case 1:
                return match[0];
              default:
                results = [];
                for (i = p = 0, len = match.length; p < len; i = ++p) {
                  o = match[i];
                  results.push({
                    text: o,
                    select: editSelect,
                    "default": input && i === 0
                  });
                }
                return results;
            }
          };
          $scope.clickHeader = function(col) {
            if (!Editing) {
              $scope.filter.add(col);
            }
            if (col.metric) {
              sortBy(col);
              fill();
            }
          };
          clickRemove = function(event) {
            var info;
            if (!(info = parseId(event.target.parentNode))) {
              return;
            }
            if (info.c === 'slot') {
              removeSlot(info);
            } else if (typeof info.c === 'number') {
              removeRecord(info);
            }
            event.stopPropagation();
            return false;
          };
          clickGlobal = function(event) {
            var act, info, rec;
            if (!(info = parseId(event.target.parentNode))) {
              return;
            }
            rec = info.row.key.record;
            act = rec.global ? volume.top.removeRecord(rec).then(function() {
              delete rec.global;
              info.row.set(info.c, info.n, void 0);
            }) : info.count ? router.http(router.controllers.deleteRecordAllSlot, rec.id).then(function() {
              return volume.top.addRecord(rec).then(function() {
                return true;
              });
            }) : volume.top.addRecord(rec).then(function() {
              rec.global = true;
              info.row.set(info.c, info.n, populateSlotData(volume.top));
            });
            saveRun(info.cell, act.then(function(r) {
              if (r) {
                $route.reload();
                return;
              }
              collapse();
              generateRow(info.i);
              if (info.n) {
                expand(info);
              }
            }));
          };
          $scope.unlimit = function() {
            Limit = 2e308;
            fill();
          };
          $scope.showHideGlobal = function() {
            $scope.showGlobal = !$scope.showGlobal;
            $scope.setKey(Key.id);
          };
          $scope.tabOptionsClick = false;
          $scope.tabOptionsToggle = function($event) {
            $scope.tabOptionsClick = !$scope.tabOptionsClick;
            $event.stopPropagation();
            return false;
          };
          pivotOpts = void 0;
          $scope.pivot = {
            show: function() {
              this.run(Rows);
            },
            hide: function() {
              this.clear();
            },
            load: function(opts) {
              if (this.run) {
                if (opts) {
                  this.run(Rows, opts);
                } else {
                  this.clear();
                }
              } else {
                pivotOpts = opts;
              }
            },
            init: function() {
              if (pivotOpts) {
                this.run(Rows, pivotOpts);
              }
              pivotOpts = void 0;
            }
          };
          $scope.filter = {
            update: function(f) {
              unedit(false);
              setFilter(f);
              fill();
            },
            accept: function(f) {
              if (f.category === pseudoCategory.slot) {
                if (Key === pseudoCategory.slot) {
                  return f.metric;
                } else {
                  return f.metric !== pseudoMetric.summary;
                }
              } else {
                return f.category !== pseudoCategory.asset && (Key === pseudoCategory.slot || Key === f.category);
              }
            },
            add: function(info) {
              var last, ref1;
              if (!this.accept(info)) {
                return;
              }
              last = this.list.pop();
              if (last != null ? last.op : void 0) {
                this.list.push(last);
              }
              this.list.push({
                category: info.category,
                metric: info.metric && info.metric.type !== 'void' ? info.metric : pseudoMetric.indicator,
                value: ((ref1 = info.metric) != null ? ref1.type : void 0) === 'numeric' ? parseFloat(info.v) : info.v
              });
            },
            count: 0
          };
          setFilter = function(f) {
            var c, len, len1, len2, p, q, ref1, row, s, w;
            c = 0;
            if (!f) {
              for (p = 0, len = Rows.length; p < len; p++) {
                row = Rows[p];
                if (row) {
                  row.filt = true;
                }
              }
            } else if (Key.id === 'slot') {
              for (q = 0, len1 = Rows.length; q < len1; q++) {
                row = Rows[q];
                if (!(row)) {
                  continue;
                }
                s = row.slot;
                if (!(row.filt = f(s, s.slot.records))) {
                  c++;
                }
              }
            } else {
              for (w = 0, len2 = Rows.length; w < len2; w++) {
                row = Rows[w];
                if (row) {
                  if (!(row.filt = f((ref1 = row.key) != null ? ref1.record : void 0, row.list('slot')))) {
                    c++;
                  }
                }
              }
            }
            $scope.filter.count = c;
            if ($scope.pivot.active) {
              $scope.pivot.show();
            }
          };

          /**
           * Call all populate functions on folder tab change/onload
           * @interface spreadsheet/setKey
           */
          setKey = function(key) {
            var base, bySlot, foot, i, len, oldKey, p, row;
            unselect();
            oldKey = Key;
            Key = $scope.key = (key != null) && volume.metrics[key] && getCategory(key) || pseudoCategory.slot;
            foot = Rows[-1];
            Rows = [];
            Cats = {};
            if (bySlot = Key === pseudoCategory.slot) {
              populateSlots();
            } else {
              populateRecords();
            }
            $scope.count = Rows.length;
            $(TBody).empty();
            Expanded = void 0;
            if (foot) {
              Rows[-1] = foot;
            }
            populateCols(bySlot);
            if (Key !== oldKey || Order.length !== Rows.length) {
              Order = (function() {
                var len, p, results;
                results = [];
                for (p = 0, len = Rows.length; p < len; p++) {
                  row = Rows[p];
                  if (row) {
                    results.push(row.i);
                  }
                }
                return results;
              })();
              currentSort = void 0;
              sortBy(Cols.find(function(c) {
                return c.category.id === 'slot' && c.metric.id === 'date';
              }));
            }
            Order.sort(function(a, b) {
              return a - b;
            });
            for (p = 0, len = Order.length; p < len; p++) {
              i = Order[p];
              generateRow(i);
            }
            if (Editing) {
              generateFoot();
            }
            tooltips.clear();
            $scope.filter.key = Key.id;
            $scope.filter.list = $scope.filter.list.filter($scope.filter.accept);
            setFilter(typeof (base = $scope.filter).make === "function" ? base.make() : void 0);
            $location.replace().search('key', void 0);
            $scope.tabOptionsClick = void 0;
          };
          $scope.setKey = function(key) {
            setKey(key);
            fill();
          };
          $scope.state = {
            get: function() {
              var base, l, state;
              state = {
                key: Key.id,
                sort: currentSort && {
                  c: currentSort.category.id,
                  m: currentSort.metric.id,
                  dir: currentSortDirection
                },
                filter: $scope.filter.list.map(function(f) {
                  return {
                    c: f.category.id,
                    m: f.metric.id,
                    op: f.op,
                    v: f.value
                  };
                }),
                pivot: typeof (base = $scope.pivot).get === "function" ? base.get() : void 0,
                "public": this["public"]
              };
              if (!state.sort) {
                delete state.sort;
              }
              l = state.filter.pop();
              if (l != null ? l.op : void 0) {
                state.filter.push(l);
              }
              if (!state.pivot) {
                delete state.pivot;
              }
              if (!state["public"]) {
                delete state["public"];
              }
              return state;
            },
            put: function(state, key) {
              var col, f, l, len, p, r, ref1;
              if (state.filter) {
                l = [];
                ref1 = state.filter;
                for (p = 0, len = ref1.length; p < len; p++) {
                  f = ref1[p];
                  r = {
                    category: getCategory(f.c),
                    metric: getMetric(f.m),
                    op: f.op,
                    value: f.v
                  };
                  if (r.category && r.metric) {
                    l.push(r);
                  }
                }
                $scope.filter.list = l;
              } else {
                $scope.filter.list = [
                  {
                    category: pseudoCategory.slot,
                    metric: pseudoMetric.top
                  }
                ];
              }
              setKey(key || state.key);
              if (state.sort && (col = Cols.find(function(c) {
                return c.category.id === state.sort.c && c.metric.id === state.sort.m;
              }))) {
                sortBy(col, state.sort.dir);
              }
              fill();
              $scope.pivot.load(state.pivot);
            },
            name: 'default',
            save: function() {
              var name, state;
              if (!(name = this.name)) {
                return;
              }
              state = this.get();
              router.http(router.controllers.postVolumeState, volume.id, encodeURIComponent(name), state).then(function() {
                volume.state[name] = state;
                messages.add({
                  type: 'green',
                  body: 'Display mode "' + name + '" saved successfully.'
                });
              }, function(res) {
                messages.addError({
                  body: 'Error saving report state',
                  report: res
                });
              });
            },
            "delete": function() {
              if (!this.name) {
                return;
              }
              router.http(router.controllers.deleteVolumeState, volume.id, encodeURIComponent(this.name)).then((function(_this) {
                return function() {
                  delete volume.state[_this.name];
                  _this.name = void 0;
                };
              })(this), function(res) {
                messages.addError({
                  body: 'Error deleting report state',
                  report: res
                });
              });
            },
            restore: function(key, state) {
              var ref1;
              state = state || ((ref1 = volume.state) != null ? ref1[this.name] : void 0) || {};
              state["public"] = !!state["public"];
              this.put(state, key);
            }
          };
          if (volume.state) {
            if ((base = volume.state)['NIH Inclusion Enrollment Report'] == null) {
              base['NIH Inclusion Enrollment Report'] = {
                key: constants.categoryName.participant.id,
                pivot: {
                  cols: ["participant ethnicity", "participant gender"],
                  rows: ["participant race"],
                  rendererName: "Table",
                  aggregatorName: "Count"
                },
                filter: [
                  {
                    c: "slot",
                    m: "top",
                    op: "false"
                  }, {
                    c: "slot",
                    m: "date",
                    op: "ge"
                  }, {
                    c: "slot",
                    m: "date",
                    op: "le",
                    v: $filter('date')(new Date(), 'yyyy-MM-dd')
                  }
                ]
              };
            }
            if ((base1 = volume.state)['Session release level summary'] == null) {
              base1['Session release level summary'] = {
                key: 'slot',
                pivot: {
                  cols: [],
                  rows: ["release"],
                  rendererName: "Table",
                  aggregatorName: "Count"
                },
                filter: [
                  {
                    c: "slot",
                    m: "top",
                    op: "false"
                  }
                ]
              };
            }
          }
          $scope.zip = function(event) {
            var add, b, c, cs, f, l, len, len1, p, params, q, row;
            event.preventDefault();
            cs = [];
            for (p = 0, len = Rows.length; p < len; p++) {
              row = Rows[p];
              if (row) {
                cs.push({
                  i: row.slot.id,
                  f: !!row.filt
                });
              }
            }
            cs.sort(function(a, b) {
              return a.i - b.i;
            });
            f = $scope.filter.count < cs.length / 2;
            l = [];
            b = void 0;
            add = function() {
              if (b) {
                l.push(b.join('-'));
                return b = void 0;
              }
            };
            for (q = 0, len1 = cs.length; q < len1; q++) {
              c = cs[q];
              if (c.f === f) {
                add();
              } else if (b) {
                b[1] = c.i;
              } else {
                b = [c.i];
              }
            }
            add();
            params = {};
            if (l.length) {
              params[f ? 'exclude' : 'include'] = l.join(',');
            }
            $location.url(router.volumeDescription([volume.id], params));
          };
        }
      ],
      link: function($scope, $element, $attrs) {
        var state;
        state = storage.getValue('spreadsheet-state');
        if ((state != null ? state.volume : void 0) !== $scope.volume.id) {
          state = void 0;
        }
        $scope.state.restore($attrs.key || $location.search().key, state);
        $scope.setKey($scope.views[0].id);
        $scope.$on('refreshParent', function() {
          $scope.volume.get({
            records: true
          }).then((function(res) {
            $scope.volume.records = res.records;
            $scope.setKey($scope.views[0].id);
          }), function(res) {
            return messages.addError({
              body: constants.message('Please refresh the page.'),
              report: res
            });
          });
        });
        $scope.$on('skiptrue', function() {
          $scope.skiptrue = true;
        });
        $scope.$on('skipfalse', function() {
          $scope.skiptrue = false;
        });
        $scope.$on('$destroy', function() {
          state = $scope.state.get();
          state.volume = $scope.volume.id;
          storage.setValue('spreadsheet-state', state);
        });
      }
    };
  }
]);

//# sourceMappingURL=spreadsheet.js.map