All files / lib jsapi.ts

79.45% Statements 58/73
69.35% Branches 43/62
75% Functions 9/12
78.87% Lines 56/71
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 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210                                    1x                           294x 294x 294x 294x 294x 294x 294x 294x               1x 351x 47x   304x 1x   303x             1x 257x                   1x                                     1x 1237x 33x   1204x 618x   586x 4x   582x                 1x 420x     420x 2x   418x               1x                                                         1x 58x     58x 3x     58x 20x     38x 38x 6x   38x               1x 12x 12x         2x   10x 10x 10x                   1x 209x 18x   245x 245x   191x   1x  
export interface Attr {
  name: string;
  value: string;
  prefix: string;
  local: string;
}
 
export interface Options {
  content?: JsApi[];
  parentNode?: JsApi;
  elem?: string;
  prefix?: string;
  local?: string;
  attrs?: { [name: string]: Attr };
  comment?: { text: string };
  processingInstruction?: { name: string; body: string };
}
 
export class JsApi implements Options {
  content: JsApi[];
  parentNode?: JsApi;
  readonly elem?: string;
  readonly prefix?: string;
  readonly local?: string;
  attrs?: { [name: string]: Attr };
  readonly comment?: { text: string };
  readonly processingInstruction?: { name: string; body: string };
 
  // TODO: avoid this caching hackery
  pathJS?: Array<{ instruction: string; data?: number[] }>;
 
  constructor(arg: Options) {
    this.content = arg.content || [];
    this.parentNode = arg.parentNode;
    this.elem = arg.elem;
    this.prefix = arg.prefix;
    this.local = arg.local;
    this.attrs = arg.attrs;
    this.comment = arg.comment;
    this.processingInstruction = arg.processingInstruction;
  }
 
  /**
   * Determine if item is an element (any, with a specific name or in a names array).
   * @param {String|Array} [elemNames] element name or names arrays
   * @return {Boolean}
   */
  isElem(elemNames?: string | string[]) {
    if (!elemNames) {
      return !!this.elem;
    }
    if (Array.isArray(elemNames)) {
      return !!this.elem && elemNames.indexOf(this.elem) >= 0;
    }
    return !!this.elem && this.elem === elemNames;
  }
 
  /**
   * Determine if element is empty.
   * @return {Boolean}
   */
  isEmpty() {
    return !this.content.length;
  }
 
  /**
   * Changes content by removing elements and/or adding new elements.
   * @param {Number} start Index at which to start changing the content.
   * @param {Number} n Number of elements to remove.
   * @param {Array|Object} [insertion] Elements to add to the content.
   * @return {Array} Removed elements.
   */
  spliceContent(start: number, n: number, insertion: JsApi[]) {
    if (!Array.isArray(insertion)) {
      insertion = Array.apply(undefined, arguments).slice(2);
    }
    insertion.forEach(function(this: JsApi, inner) {
      inner.parentNode = this;
    }, this);
    return this.content.splice.apply(
      this.content,
      ([start, n] as Array<number | JsApi>).concat(insertion),
    );
  }
 
  /**
   * Determine if element has an attribute (any, or by name or by name + value).
   * @param {String} [name] attribute name
   * @param {String} [val] attribute value (will be toString()'ed)
   * @return {Boolean}
   */
  hasAttr(name?: string, val?: any) {
    if (!this.attrs || !Object.keys(this.attrs).length) {
      return false;
    }
    if (!arguments.length) {
      return !!this.attrs;
    }
    if (val !== undefined) {
      return !!this.attrs[name] && this.attrs[name].value === val.toString();
    }
    return !!this.attrs[name];
  }
 
  /**
   * Get a specific attribute from an element (by name or name + value).
   * @param {String} name attribute name
   * @param {String} [val] attribute value (will be toString()'ed)
   * @return {Object|Undefined}
   */
  attr(name: string, val?: string) {
    Iif (!this.hasAttr() || !arguments.length) {
      return undefined;
    }
    if (val !== undefined) {
      return this.hasAttr(name, val) ? this.attrs[name] : undefined;
    }
    return this.attrs[name];
  }
 
  /**
   * Get computed attribute value from an element.
   * @param {String} name attribute name
   * @return {Object|Undefined}
   */
  computedAttr(name: string, val?: any) {
    if (!arguments.length) {
      return undefined;
    }
 
    let elem: JsApi;
    for (
      elem = this;
      elem && (!elem.hasAttr(name) || !elem.attr(name).value);
      elem = elem.parentNode
    ) {}
 
    // tslint:disable-next-line:triple-equals no-null-keyword
    if (val != null) {
      // TODO: why return a boolean here instead of a string?
      return elem ? elem.hasAttr(name, val) : undefined;
    } else if (elem && elem.hasAttr(name)) {
      return elem.attrs[name].value;
    } else {
      return undefined;
    }
  }
 
  /**
   * Remove a specific attribute.
   * @param {String|Array} name attribute name
   * @param {String} [val] attribute value
   * @return {Boolean}
   */
  removeAttr(name: string | string[]) {
    Iif (!arguments.length) {
      return false;
    }
    if (Array.isArray(name)) {
      name.forEach(this.removeAttr, this);
    }
    // TODO: fix this cast
    if (!this.hasAttr(name as any)) {
      return false;
    }
    // TODO: fix this cast
    delete this.attrs[name as any];
    if (!Object.keys(this.attrs).length) {
      delete this.attrs;
    }
    return true;
  }
 
  /**
   * Add attribute.
   * @param {Object} [attr={}] attribute object
   * @return {Object|Boolean} created attribute or false if no attr was passed in
   */
  addAttr(attr: Attr) {
    attr = attr || ({} as Attr);
    if (
      attr.name === undefined ||
      attr.prefix === undefined ||
      attr.local === undefined
    ) {
      return false;
    }
    this.attrs = this.attrs || {};
    this.attrs[attr.name] = attr;
    return this.attrs[attr.name];
  }
 
  /**
   * Iterates over all attributes.
   *
   * @param {Function} callback callback
   * @param {Object} [context] callback context
   * @return {Boolean} false if there are no any attributes
   */
  eachAttr(callback: (attr: Attr) => void, context?: any) {
    if (!this.hasAttr()) {
      return false;
    }
    for (const name of Object.keys(this.attrs)) {
      callback.call(context, this.attrs[name]);
    }
    return true;
  }
}