コード例 #1
0
ファイル: caret.test.js プロジェクト: nymag/clay-kiln
describe('caret', () => {
  let textNode = document.createTextNode('Hello World'),
    span = create(`<span ${editAttr}="foo"></span>`),
    el = create(`<div ${refAttr}="domain.com/_components/hi"></div>`);

  span.appendChild(textNode);
  el.appendChild(span);

  describe('getClickOffset', () => {
    const fn = lib.getClickOffset;

    test('gets the offset of clicked text', () => {
      let doc = { caretPositionFromPoint: jest.fn(), createElement: document.createElement.bind(document) };

      doc.caretPositionFromPoint.mockReturnValue({ offsetNode: textNode, offset: 5 });
      components.getData.mockReturnValue('Hello World');
      expect(fn({ clientX: 0, clientY: 0 }, doc)).toBe(5);
    });

    test('gets the offset when clicking past text', () => {
      let doc = { caretRangeFromPoint: jest.fn(), createElement: document.createElement.bind(document) };

      doc.caretRangeFromPoint.mockReturnValue({ startContainer: span, startOffset: 5 });
      components.getData.mockReturnValue('Hello World');
      span.classList.add(selectorClass);
      expect(fn({ clientX: 0, clientY: 0 }, doc)).toBe(11);
    });

    test('returns 0 for placeholders', () => {
      let doc = { caretRangeFromPoint: jest.fn(), createElement: document.createElement.bind(document) };

      doc.caretRangeFromPoint.mockReturnValue({ startContainer: span, startOffset: 5 });
      components.getData.mockReturnValue('Hello World');
      span.classList.add(placeholderClass);

      expect(fn({ clientX: 0, clientY: 0 }, doc)).toBe(0);
    });

    test('returns 0 if data is not in parent element', () => {
      let doc = { caretPositionFromPoint: jest.fn(), createElement: document.createElement.bind(document) };

      doc.caretPositionFromPoint.mockReturnValue({ offsetNode: textNode, offset: 5 });
      components.getData.mockReturnValue('Other Text');
      span.classList.remove(placeholderClass);

      expect(fn({ clientX: 0, clientY: 0 }, doc)).toBe(0);
    });
  });
});
コード例 #2
0
    it('replaces the result.el', function () { // We could call this a "root" behavior
      var result,
        elBefore = dom.create('<div class="el-before"></div>');

      fixture.el = elBefore;
      result = lib(fixture, args);
      expect(result.el.querySelector('.el-before')).to.eql.null;
    });
コード例 #3
0
module.exports = function (result) {
  var name = result.name;

  result.bindings.data.value = dom.uri(); // returns the current uri (for now)
  // todo: if behaviors supported promises, we could get the full page id
  result.el.appendChild(dom.create(`<input type="hidden" class="input-text" rv-field="${name}" rv-value="${name}.data.value" />`));

  return result;
};
コード例 #4
0
function createOverlayEl(innerEl) {
  var el = dom.create(`
    <div class="editor-overlay-background">
      <div class="editor-overlay"></div>
    </div>
  `);

  dom.find(el, '.editor-overlay').appendChild(innerEl);
  return el;
}
コード例 #5
0
    it('allows access to component data and sorted list of components', function () {
      var spy = sandbox.spy(),
        ref = '/components/a',
        data = {},
        rules = [{ validate: spy }];

      dom.findAll.withArgs('[data-uri]').returns([dom.create('<section data-uri="' + ref + '" />')]);
      edit.getData.withArgs(ref).returns(Promise.resolve(data));

      return fn(rules).then(function () {
        expect(spy.args[0][0]).to.deep.equal({refs: {'/components/a': {}}, components: ['a']});
      });
    });
コード例 #6
0
function createInlineFormEl(innerEl) {
  var el = dom.create(`
    <section class="editor editor-inline">
      <form>
        <div class="input-container"></div>
        <button type="submit" class="hidden-submit"></button>
      </form>
    </section>
  `);

  dom.find(el, '.input-container').appendChild(innerEl);
  return el;
}
コード例 #7
0
module.exports = function (result, args) {
  var name = result.name,
    label = args.label,
    tpl = `
      <label class="input-label">
        <input class="input-checkbox" rv-field="${name}" type="checkbox" rv-checked="${name}.data.value" /><span class="checkbox-label">${label}</span>
      </label>
    `,
    checkbox = dom.create(tpl);

  result.el = checkbox;

  return result;
};
コード例 #8
0
ファイル: radio.js プロジェクト: oneokorganization/clay-kiln
module.exports = function (result, args) {
  var name = result.name,
    options = args.options,
    field = dom.create(`
      <span class="input-label">
        <ul class="editor-radios">
          ${ createOptions(name, options) }
        </ul>
      </span>
    `);

  result.el = field;
  return result;
};
コード例 #9
0
function createOverlayFormEl(formLabel, innerEl) {
  var el = dom.create(`
    <section class="editor">
      <form>
        <header>
          <span class="form-header">${formLabel}</span>
          <button type="submit" class="save">Save</button>
        </header>
        <div class="input-container"></div>
      </form>
    </section>
  `);

  dom.find(el, '.input-container').appendChild(innerEl);
  return el;
}
コード例 #10
0
module.exports = function (result, args) {
  var name = result.name,
    binders = result.binders,
    mode = args.mode,
    field = dom.create(`<label class="input-label">
      <textarea class="codemirror" rv-field="${name}" rv-codemirror="${name}.data.value" data-codemirror-mode="${mode}" rv-value="${name}.data.value"></textarea>
    </label>`);

  // add the input to the field
  result.el = field;

  // add the binder
  binders.codemirror = initCodemirror();

  return result;
};
コード例 #11
0
/**
 * Converts array of strings to option elements.
 * @param {array} items
 * @returns {Element}
 */
function formatOptions(items) {
  var optionsEl,
    options = flattenText(items).reduce(function (prev, curr) {
      return prev + '<option>' + curr + '</option>';
    }, '');

  optionsEl = dom.create(`
    <label>
      <select>
        ${ options }
      </select>
    </label>
  `);

  return optionsEl;
}
コード例 #12
0
module.exports = function (result, args) {
  var name = result.name,
    options = args.options,
    field;

  field = dom.create(`
    <div class="input-label">
      <div class="checkbox-group">
        ${ createOptions(name, options) }
      </div>
    </div>
  `);

  result.el = field;

  return result;
};
コード例 #13
0
module.exports = function (result, args) {
  var name = result.name,
    bindings = result.bindings,
    textArea;

  args = args || {};
  bindings.placeholder = args.placeholder || '';

  textArea = dom.create(`
    <label class="input-label">
      <textarea class="editor-textarea" rv-field="${name}" ${setRequired(args.required)} rv-placeholder="${name}.placeholder" rv-value="${name}.data.value"></textarea>
    </label>
  `);

  result.el = textArea;

  return result;
};
コード例 #14
0
module.exports = function (result, args) {
  var el = result.el,
    name = result.name,
    tpl = `
      <span class="soft-maxlength" data-maxlength="${args.value}" rv-remaining="${name}.data.value"></span>
    `,
    span = dom.create(tpl);

  result.binders.remaining = function (el, value) {
    var length = value ? cleanValue(value).length : 0,
      max = parseInt(el.getAttribute('data-maxlength'));

    setStyles(length, max, el);
  };

  el.appendChild(span);
  return result;
};
コード例 #15
0
ファイル: template.js プロジェクト: nymag/clay-kiln
 }).then((html) => create(html));
コード例 #16
0
module.exports = function (result, args) {
  var name = result.name,
    allowRepeat = !!args && !!args.allowRepeatedItems,
    el = dom.create(`
      <div class="input-label">
        <div rv-field="${name}" class="simple-list" rv-simplelist="${name}.data">
          <span tabindex="0" rv-each-item="${name}.data" class="simple-list-item" rv-class-selected="item._selected" rv-on-click="${name}.selectItem" rv-on-keydown="${name}.keyactions">{ item.text }</span>
          <input class="simple-list-add" data-allow-repeat=${allowRepeat} rv-on-click="${name}.unselectAll" placeholder="Start typing here&hellip;" />
        </div>
      </div>`);

  /**
   * unselect all items
   * @param  {[]} items
   * @return {[]}
   */
  function unselectAll(items) {
    return items.map(function (item) {
      item._selected = false;
      return item;
    });
  }

  /**
   * unselect all items, then select a specific item
   * @param {object} item
   * @param  {array} data
   */
  function selectItem(item, data) {
    unselectAll(data);
    item._selected = true;
  }

  /**
   * select previous item in list
   * @param  {Event} e
   * @param  {number} index
   * @param  {array} data
   */
  function selectPrevious(e, index, data) {
    if (index > 0 && e.target.previousSibling) {
      selectItem(data[index - 1], data);
      e.target.previousSibling.focus();
    }
  }

  /**
   * select next item in list
   * @param  {Event} e
   * @param  {number} index
   * @param  {array} data
   */
  function selectNext(e, index, data) {
    var input = dom.find(el, '.simple-list-add');

    if (index < data.length - 1) {
      e.preventDefault(); // kill that tab!
      selectItem(data[index + 1], data );
      e.target.nextSibling.focus();
    } else {
      // we currently have the last item selected, so focus the input
      e.preventDefault();
      e.stopPropagation(); // stop the current event first
      input.dispatchEvent(new Event('click'));
      input.focus();
    }
  }

  /**
   * remove item from list
   * @param  {Event} e
   * @param  {number} index
   * @param  {array} data
   */
  function deleteItem(e, index, data) {
    var prevSibling = e.target.previousSibling,
      input = dom.find(dom.closest(e.target, '.simple-list'), '.simple-list-add');

    e.preventDefault(); // prevent triggering the browser's back button
    data.splice(index, 1); // remove item from the list

    if (index > 0) {
      prevSibling.focus();
      prevSibling.dispatchEvent(new Event('click'));
    } else {
      // you deleted all the items! focus the input
      input.focus();
    }
  }

  /*
  Click Handlers
   */

  // unselect all items when you click to add a new one
  result.bindings.unselectAll = function (e, bindings) {
    unselectAll(bindings[name].data);
  };

  // select an item (and unselect all others) when you click it
  result.bindings.selectItem = function (e, bindings) {
    selectItem(bindings.item, bindings[name].data);
  };

  // move between items and delete items when pressing the relevant keys (when an item is selected)
  result.bindings.keyactions = function (e, bindings) {
    var key = keycode(e),
      data = bindings[name].data,
      index = data.indexOf(bindings.item);

    if (key === 'left') {
      selectPrevious(e, index, data);
    } else if (key === 'tab' || key === 'right') {
      selectNext(e, index, data);
    } else if (key === 'delete' || key === 'backspace') {
      deleteItem(e, index, data);
    }
  };

  // put the element into the result object
  result.el = el;

  // add binder for creating new items
  result.binders.simplelist = {
    publish: true,
    bind: function (boundEl) {
      // this is called when the binder initializes
      var addEl = dom.find(boundEl, '.simple-list-add'),
        allowRepeat = !!(addEl.getAttribute('data-allow-repeat') === 'true'),
        observer = this.observer;

      // check repeated items
      // returns true if repitition is disallowed and items repeat
      // returns false if repitition is allowed
      // returns false if repitition is disallowed and items don't repeat
      function hasRepeatedValue(value, data) {
        var oldItems = _.map(data, item => item.text.toLowerCase());

        return !allowRepeat && _.contains(oldItems, value.toLowerCase());
      }

      // add new item from the add-items field
      function addItem(e) {
        var data = observer.value(),
          newText = { text: addEl.value }, // get the new item text
          val = newText.text,
          repeated = hasRepeatedValue(val, data);

        // prevent creating newlines or tabbing out of the field
        if (e) {
          e.preventDefault();
        }

        if (val.length && repeated) {
          addEl.setCustomValidity('Repeated items are not allowed!');
          addEl.reportValidity();
        } else if (val.length && !repeated) {
          addEl.setCustomValidity(''); // valid input
          addEl.value = ''; // remove it from the add-item field
          data.push(newText); // put it into the data
          observer.setValue(data);
        } else {
          addEl.setCustomValidity(''); // remove the invalidity of the input, so you can close the form
          // close the form
          focus.unfocus().catch(_.noop);
        }
      }

      // select the last item when you backspace from the add-items field
      function selectLastItem(e) {
        var data = observer.value(),
          newItems = dom.findAll(boundEl, '.simple-list-item');

        if (!addEl.value || !addEl.value.length) {
          e.preventDefault(); // prevent triggering the browser's back button
          _.last(data)._selected = true;
          _.last(newItems).focus(); // focus on the last item
          observer.setValue(data);
        }
      }

      // handle keyboard events in the add-items field
      function handleItemKeyEvents(e) {
        var key = keycode(e);

        if (key === 'enter' || key === 'tab' || key === ',' && e.shiftKey === false) {
          addItem(e);
        } else if (key === 'delete' || key === 'backspace' || key === 'left') {
          selectLastItem(e);
        }
      }

      addEl.addEventListener('keydown', handleItemKeyEvents);
    }
  };

  return result;
};