All files / src/lib response.js

100% Statements 54/54
100% Branches 36/36
100% Functions 8/8
100% Lines 54/54
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143                10x 10x       9x 9x 8x     8x           8x 8x   7x 42x 36x       6x 6x 6x     7x                         8x 5x 5x       8x 8x 8x     8x         8x     8x 6x             8x     79x     76x 76x         8x             8x 8x 8x 8x       8x 8x 8x 82x 75x 7x 2x       8x 2x 2x     8x   8x         9x   9x 9x 9x 8x 8x   1x     9x          
import Row from './row';
import SheetrockError from './error';
 
import * as util from './util';
 
// Get useful information about the response.
class Response {
  constructor(request) {
    this.request = request;
    this.options = request.options;
  }
 
  setAttributes() {
    const fetchSize = this.options.user.fetchSize;
    const rows = this.raw.table.rows;
    const cols = this.raw.table.cols;
 
    // Initialize a hash for the response attributes.
    const attributes = {
      last: rows.length - 1,
      rowNumberOffset: this.request.state.header || 0,
    };
 
    // Determine if Google has extracted column labels from a header row.
    let columnLabels = this.request.state.labels;
    if (!this.request.state.offset) {
      // Use extracted column labels, the first row, or column letter.
      columnLabels = cols.map((col, i) => {
        if (col.label) {
          return col.label.replace(/\s/g, '');
        }
 
        // Get column labels from the first row of the response.
        attributes.last += 1;
        attributes.rowNumberOffset = 1;
        return util.getCellValue(rows[0].c[i]) || col.id;
      });
 
      this.request.update({
        header: attributes.rowNumberOffset,
        labels: columnLabels,
        offset: this.request.state.offset + attributes.rowNumberOffset,
      });
    }
 
    // The Google API generates an unrecoverable error when the 'offset' is
    // larger than the number of available rows, which is problematic for
    // paged requests. As a workaround, we request one more row than we need
    // and stop when we see less rows than we requested.
 
    // Remember whether this request has been fully loaded.
    if (!fetchSize || (rows.length - attributes.rowNumberOffset) < fetchSize) {
      attributes.last += 1;
      this.request.update({ loaded: true });
    }
 
    // If column labels are provided and have the expected length, use them.
    const userLabels = this.options.user.labels;
    const userLabelsValid = userLabels && userLabels.length === columnLabels.length;
    attributes.labels = userLabelsValid ? userLabels : columnLabels;
 
    // Return the response attributes.
    this.attributes = attributes;
  }
 
  // Parse data, row by row, and generate a simpler output array.
  setOutput() {
    this.rows = [];
 
    // Add a header row constructed from the column labels, if appropriate.
    if (!this.request.state.offset && !this.attributes.rowNumberOffset) {
      this.rows.push(new Row(0, this.attributes.labels, this.attributes.labels));
    }
 
    // Each table cell ('c') can contain two properties: 'p' contains
    // formatting and 'v' contains the actual cell value.
 
    // Loop through each table row.
    this.raw.table.rows.forEach((row, i) => {
      // Proceed if the row has cells and the row index is within the targeted
      // range. (This avoids displaying too many rows when paging data.)
      if (row.c && i < this.attributes.last) {
        // Get the "real" row index (not counting header rows). Create a row
        // object and add it to the output array.
        const counter = (this.request.state.offset + i + 1) - this.attributes.rowNumberOffset;
        this.rows.push(new Row(counter, row.c, this.attributes.labels));
      }
    });
 
    // Remember the new row offset.
    this.request.update({
      offset: this.request.state.offset + this.options.user.fetchSize,
    });
  }
 
  // Generate HTML from rows using a template.
  setHTML() {
    const target = this.options.user.target;
    const template = this.options.user.rowTemplate || util.toHTML;
    const isTable = util.isTable(target);
    const needsHeader = target && util.hasClass(target, 'sheetrock-header');
 
    // Pass each row to the row template. Only parse header rows if the target
    // is a table or indicates via className that it wants the header.
    let headerHTML = '';
    let bodyHTML = '';
    this.rows.forEach((row) => {
      if (row.num) {
        bodyHTML += template(row);
      } else if (isTable || needsHeader) {
        headerHTML += template(row);
      }
    });
 
    if (isTable) {
      headerHTML = util.wrapTag(headerHTML, 'thead');
      bodyHTML = util.wrapTag(bodyHTML, 'tbody');
    }
 
    util.append(target, headerHTML + bodyHTML);
 
    this.html = headerHTML + bodyHTML;
  }
 
  // Load API response.
  loadData(rawData, callback) {
    let thrown = null;
 
    try {
      this.raw = rawData;
      this.setAttributes();
      this.setOutput();
      this.setHTML();
    } catch (error) {
      thrown = new SheetrockError('Unexpected API response format.');
    }
 
    callback(thrown);
  }
}
 
export default Response;