mirror of
				https://github.com/gristlabs/grist-core.git
				synced 2025-06-13 20:53:59 +00:00 
			
		
		
		
	(core) Convert a few widgets to typescript and grainjs.
Summary: No behavior changes. Diff includes an intermediate commit with only renames, for easier review. Test Plan: Existing tests should pass. Reviewers: paulfitz Reviewed By: paulfitz Differential Revision: https://phab.getgrist.com/D2669
This commit is contained in:
		
							parent
							
								
									f24a82e8d4
								
							
						
					
					
						commit
						4539521dff
					
				@ -1,7 +1,7 @@
 | 
			
		||||
import {basicButton, primaryButton} from 'app/client/ui2018/buttons';
 | 
			
		||||
import {colors, testId} from 'app/client/ui2018/cssVars';
 | 
			
		||||
import {icon} from 'app/client/ui2018/icons';
 | 
			
		||||
import {Computed, Disposable, dom, DomElementArg, Observable, styled} from 'grainjs';
 | 
			
		||||
import {Computed, Disposable, dom, DomContents, DomElementArg, Observable, styled} from 'grainjs';
 | 
			
		||||
import isEqual = require('lodash/isEqual');
 | 
			
		||||
import uniq = require('lodash/uniq');
 | 
			
		||||
 | 
			
		||||
@ -32,7 +32,7 @@ export class ListEntry extends Disposable {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Arg maxRows indicates the number of rows to display when the textarea is inactive.
 | 
			
		||||
  public buildDom(maxRows: number = 6): DomElementArg {
 | 
			
		||||
  public buildDom(maxRows: number = 6): DomContents {
 | 
			
		||||
    return dom.domComputed(this._isEditing, (editMode) => {
 | 
			
		||||
      if (editMode) {
 | 
			
		||||
        // Edit mode dom.
 | 
			
		||||
 | 
			
		||||
@ -58,6 +58,9 @@ export interface ViewFieldRec extends IRowModel<"_grist_Views_section_field"> {
 | 
			
		||||
 | 
			
		||||
  widgetOptionsJson: modelUtil.SaveableObjObservable<any>;
 | 
			
		||||
 | 
			
		||||
  // Whether lines should wrap in a cell.
 | 
			
		||||
  wrapping: ko.Computed<boolean>;
 | 
			
		||||
 | 
			
		||||
  // Observable for the parsed filter object saved to the field.
 | 
			
		||||
  activeFilter: modelUtil.CustomComputed<string>;
 | 
			
		||||
 | 
			
		||||
@ -180,6 +183,13 @@ export function createViewFieldRec(this: ViewFieldRec, docModel: DocModel): void
 | 
			
		||||
  this.widgetOptionsJson = modelUtil.jsonObservable(this._widgetOptionsStr,
 | 
			
		||||
    (opts: any) => UserType.mergeOptions(opts || {}, this.column().pureType()));
 | 
			
		||||
 | 
			
		||||
  this.wrapping = ko.pureComputed(() => {
 | 
			
		||||
    // When user has yet to specify a desired wrapping state, we use different defaults for
 | 
			
		||||
    // GridView (no wrap) and DetailView (wrap).
 | 
			
		||||
    // "??" is the newish "nullish coalescing" operator. How cool is that!
 | 
			
		||||
    return this.widgetOptionsJson().wrap ?? (this.viewSection().parentKey() !== 'record');
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  // Observable for the active filter that's initialized from the value saved to the server.
 | 
			
		||||
  this.activeFilter = modelUtil.customComputed({
 | 
			
		||||
    read: () => { const f = this.filter(); return f === 'null' ? '' : f; }, // To handle old empty filters
 | 
			
		||||
 | 
			
		||||
@ -1,96 +0,0 @@
 | 
			
		||||
var commands = require('../components/commands');
 | 
			
		||||
var dispose = require('../lib/dispose');
 | 
			
		||||
var kd = require('../lib/koDom');
 | 
			
		||||
var _ = require('underscore');
 | 
			
		||||
var TextBox = require('./TextBox');
 | 
			
		||||
 | 
			
		||||
const {fromKoSave} = require('app/client/lib/fromKoSave');
 | 
			
		||||
const {ListEntry} = require('app/client/lib/listEntry');
 | 
			
		||||
const {alignmentSelect} = require('app/client/ui2018/buttonSelect');
 | 
			
		||||
const {colors, testId} = require('app/client/ui2018/cssVars');
 | 
			
		||||
const {icon} = require('app/client/ui2018/icons');
 | 
			
		||||
const {menu, menuItem} = require('app/client/ui2018/menus');
 | 
			
		||||
const {cssLabel, cssRow} = require('app/client/ui/RightPanel');
 | 
			
		||||
const {dom, Computed, styled} = require('grainjs');
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * ChoiceTextBox - A textbox for choice values.
 | 
			
		||||
 */
 | 
			
		||||
function ChoiceTextBox(field) {
 | 
			
		||||
  TextBox.call(this, field);
 | 
			
		||||
 | 
			
		||||
  this.docData = field._table.docModel.docData;
 | 
			
		||||
  this.colId = field.column().colId;
 | 
			
		||||
  this.tableId = field.column().table().tableId;
 | 
			
		||||
 | 
			
		||||
  this.choices = this.options.prop('choices');
 | 
			
		||||
  this.choiceValues = Computed.create(this, (use) => use(this.choices) || [])
 | 
			
		||||
}
 | 
			
		||||
dispose.makeDisposable(ChoiceTextBox);
 | 
			
		||||
_.extend(ChoiceTextBox.prototype, TextBox.prototype);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
ChoiceTextBox.prototype.buildDom = function(row) {
 | 
			
		||||
  let value = row[this.field.colId()];
 | 
			
		||||
  return cssChoiceField(
 | 
			
		||||
    cssChoiceText(
 | 
			
		||||
      kd.style('text-align', this.alignment),
 | 
			
		||||
      kd.text(() => row._isAddRow() ? '' : this.valueFormatter().format(value())),
 | 
			
		||||
    ),
 | 
			
		||||
    cssDropdownIcon('Dropdown',
 | 
			
		||||
      // When choices exist, click dropdown icon to open edit autocomplete.
 | 
			
		||||
      dom.on('click', () => this._hasChoices() && commands.allCommands.editField.run()),
 | 
			
		||||
      // When choices do not exist, open a single-item menu to open the sidepane choice option editor.
 | 
			
		||||
      menu(() => [
 | 
			
		||||
        menuItem(commands.allCommands.fieldTabOpen.run, 'Add Choice Options')
 | 
			
		||||
      ], {
 | 
			
		||||
        trigger: [(elem, ctl) => {
 | 
			
		||||
          // Only open this menu if there are no choices.
 | 
			
		||||
          dom.onElem(elem, 'click', () => this._hasChoices() || ctl.open());
 | 
			
		||||
        }]
 | 
			
		||||
      }),
 | 
			
		||||
      testId('choice-dropdown')
 | 
			
		||||
    )
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
ChoiceTextBox.prototype.buildConfigDom = function() {
 | 
			
		||||
  return [
 | 
			
		||||
    cssRow(
 | 
			
		||||
      alignmentSelect(fromKoSave(this.alignment))
 | 
			
		||||
    ),
 | 
			
		||||
    cssLabel('OPTIONS'),
 | 
			
		||||
    cssRow(
 | 
			
		||||
      dom.create(ListEntry, this.choiceValues, (values) => this.choices.saveOnly(values))
 | 
			
		||||
    )
 | 
			
		||||
  ];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
ChoiceTextBox.prototype.buildTransformConfigDom = function() {
 | 
			
		||||
  return this.buildConfigDom();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
ChoiceTextBox.prototype._hasChoices = function() {
 | 
			
		||||
  return this.choiceValues.get().length > 0;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const cssChoiceField = styled('div.field_clip', `
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: space-between;
 | 
			
		||||
`);
 | 
			
		||||
 | 
			
		||||
const cssChoiceText = styled('div', `
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
  text-overflow: ellipsis;
 | 
			
		||||
`);
 | 
			
		||||
 | 
			
		||||
const cssDropdownIcon = styled(icon, `
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  background-color: ${colors.lightGreen};
 | 
			
		||||
  min-width: 16px;
 | 
			
		||||
  width: 16px;
 | 
			
		||||
  height: 16px;
 | 
			
		||||
`);
 | 
			
		||||
 | 
			
		||||
module.exports = ChoiceTextBox;
 | 
			
		||||
							
								
								
									
										89
									
								
								app/client/widgets/ChoiceTextBox.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								app/client/widgets/ChoiceTextBox.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,89 @@
 | 
			
		||||
import * as commands from 'app/client/components/commands';
 | 
			
		||||
import {ListEntry} from 'app/client/lib/listEntry';
 | 
			
		||||
import {DataRowModel} from 'app/client/models/DataRowModel';
 | 
			
		||||
import {ViewFieldRec} from 'app/client/models/entities/ViewFieldRec';
 | 
			
		||||
import {KoSaveableObservable} from 'app/client/models/modelUtil';
 | 
			
		||||
import {cssLabel, cssRow} from 'app/client/ui/RightPanel';
 | 
			
		||||
import {alignmentSelect} from 'app/client/ui2018/buttonSelect';
 | 
			
		||||
import {colors, testId} from 'app/client/ui2018/cssVars';
 | 
			
		||||
import {icon} from 'app/client/ui2018/icons';
 | 
			
		||||
import {menu, menuItem} from 'app/client/ui2018/menus';
 | 
			
		||||
import {NTextBox} from 'app/client/widgets/NTextBox';
 | 
			
		||||
import {Computed, dom, styled} from 'grainjs';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * ChoiceTextBox - A textbox for choice values.
 | 
			
		||||
 */
 | 
			
		||||
export class ChoiceTextBox extends NTextBox {
 | 
			
		||||
  private _choices: KoSaveableObservable<string[]>;
 | 
			
		||||
  private _choiceValues: Computed<string[]>;
 | 
			
		||||
 | 
			
		||||
  constructor(field: ViewFieldRec) {
 | 
			
		||||
    super(field);
 | 
			
		||||
    this._choices = this.options.prop('choices');
 | 
			
		||||
    this._choiceValues = Computed.create(this, (use) => use(this._choices) || []);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public buildDom(row: DataRowModel) {
 | 
			
		||||
    const value = row.cells[this.field.colId()];
 | 
			
		||||
    return cssChoiceField(
 | 
			
		||||
      cssChoiceText(
 | 
			
		||||
        dom.style('text-align', this.alignment),
 | 
			
		||||
        dom.text((use) => use(row._isAddRow) ? '' : use(this.valueFormatter).format(use(value))),
 | 
			
		||||
      ),
 | 
			
		||||
      cssDropdownIcon('Dropdown',
 | 
			
		||||
        // When choices exist, click dropdown icon to open edit autocomplete.
 | 
			
		||||
        dom.on('click', () => this._hasChoices() && commands.allCommands.editField.run()),
 | 
			
		||||
        // When choices do not exist, open a single-item menu to open the sidepane choice option editor.
 | 
			
		||||
        menu(() => [
 | 
			
		||||
          menuItem(commands.allCommands.fieldTabOpen.run, 'Add Choice Options')
 | 
			
		||||
        ], {
 | 
			
		||||
          trigger: [(elem, ctl) => {
 | 
			
		||||
            // Only open this menu if there are no choices.
 | 
			
		||||
            dom.onElem(elem, 'click', () => this._hasChoices() || ctl.open());
 | 
			
		||||
          }]
 | 
			
		||||
        }),
 | 
			
		||||
        testId('choice-dropdown')
 | 
			
		||||
      )
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public buildConfigDom() {
 | 
			
		||||
    return [
 | 
			
		||||
      cssRow(
 | 
			
		||||
        alignmentSelect(this.alignment)
 | 
			
		||||
      ),
 | 
			
		||||
      cssLabel('OPTIONS'),
 | 
			
		||||
      cssRow(
 | 
			
		||||
        dom.create(ListEntry, this._choiceValues, (values) => this._choices.saveOnly(values))
 | 
			
		||||
      )
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public buildTransformConfigDom() {
 | 
			
		||||
    return this.buildConfigDom();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private _hasChoices() {
 | 
			
		||||
    return this._choiceValues.get().length > 0;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const cssChoiceField = styled('div.field_clip', `
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: space-between;
 | 
			
		||||
`);
 | 
			
		||||
 | 
			
		||||
const cssChoiceText = styled('div', `
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
  text-overflow: ellipsis;
 | 
			
		||||
`);
 | 
			
		||||
 | 
			
		||||
const cssDropdownIcon = styled(icon, `
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  background-color: ${colors.lightGreen};
 | 
			
		||||
  min-width: 16px;
 | 
			
		||||
  width: 16px;
 | 
			
		||||
  height: 16px;
 | 
			
		||||
`);
 | 
			
		||||
@ -1,78 +0,0 @@
 | 
			
		||||
var _ = require('underscore');
 | 
			
		||||
var dom = require('../lib/dom');
 | 
			
		||||
var dispose = require('../lib/dispose');
 | 
			
		||||
var kd = require('../lib/koDom');
 | 
			
		||||
var TextBox = require('./TextBox');
 | 
			
		||||
const G = require('../lib/browserGlobals').get('URL');
 | 
			
		||||
 | 
			
		||||
const {colors, testId} = require('app/client/ui2018/cssVars');
 | 
			
		||||
const {icon}   = require('app/client/ui2018/icons');
 | 
			
		||||
const {styled} = require('grainjs');
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Creates a widget for displaying links.  Links can entered directly or following a title.
 | 
			
		||||
 * The last entry following a space is used as the url.
 | 
			
		||||
 * ie 'google https://www.google.com' would apears as 'google' to the user but link to the url.
 | 
			
		||||
 */
 | 
			
		||||
function HyperLinkTextBox(field) {
 | 
			
		||||
  TextBox.call(this, field);
 | 
			
		||||
}
 | 
			
		||||
dispose.makeDisposable(HyperLinkTextBox);
 | 
			
		||||
_.extend(HyperLinkTextBox.prototype, TextBox.prototype);
 | 
			
		||||
 | 
			
		||||
HyperLinkTextBox.prototype.buildDom = function(row) {
 | 
			
		||||
  var value = row[this.field.colId()];
 | 
			
		||||
  return cssFieldClip(
 | 
			
		||||
    kd.style('text-align', this.alignment),
 | 
			
		||||
    kd.toggleClass('text_wrapping', this.wrapping),
 | 
			
		||||
    kd.maybe(value, () =>
 | 
			
		||||
      dom('a',
 | 
			
		||||
        kd.attr('href', () => _constructUrl(value())),
 | 
			
		||||
        kd.attr('target', '_blank'),
 | 
			
		||||
        // As per Google and Mozilla recommendations to prevent opened links
 | 
			
		||||
        // from running on the same process as Grist:
 | 
			
		||||
        // https://developers.google.com/web/tools/lighthouse/audits/noopener
 | 
			
		||||
        kd.attr('rel', 'noopener noreferrer'),
 | 
			
		||||
        cssLinkIcon('FieldLink'),
 | 
			
		||||
        testId('tb-link')
 | 
			
		||||
      )
 | 
			
		||||
    ),
 | 
			
		||||
    kd.text(() => _formatValue(value()))
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Formats value like `foo bar baz` by discarding `baz` and returning `foo bar`.
 | 
			
		||||
 */
 | 
			
		||||
function _formatValue(value) {
 | 
			
		||||
  // value might be null, at least transiently. Handle it to avoid an exception.
 | 
			
		||||
  if (typeof value !== 'string') { return value; }
 | 
			
		||||
  const index = value.lastIndexOf(' ');
 | 
			
		||||
  return index >= 0 ? value.slice(0, index) : value;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Given value like `foo bar baz`, constructs URL by checking if `baz` is a valid URL and,
 | 
			
		||||
 * if not, prepending `http://`.
 | 
			
		||||
 */
 | 
			
		||||
function _constructUrl(value) {
 | 
			
		||||
  const url = value.slice(value.lastIndexOf(' ') + 1);
 | 
			
		||||
  try {
 | 
			
		||||
    // Try to construct a valid URL
 | 
			
		||||
    return (new G.URL(url)).toString();
 | 
			
		||||
  } catch (e) {
 | 
			
		||||
    // Not a valid URL, so try to prefix it with http
 | 
			
		||||
    return 'http://' + url;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const cssFieldClip = styled('div.field_clip', `
 | 
			
		||||
  color: ${colors.lightGreen};
 | 
			
		||||
`);
 | 
			
		||||
 | 
			
		||||
const cssLinkIcon = styled(icon, `
 | 
			
		||||
  background-color: ${colors.lightGreen};
 | 
			
		||||
  margin: -1px 2px 2px 0;
 | 
			
		||||
`);
 | 
			
		||||
 | 
			
		||||
module.exports = HyperLinkTextBox;
 | 
			
		||||
							
								
								
									
										71
									
								
								app/client/widgets/HyperLinkTextBox.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								app/client/widgets/HyperLinkTextBox.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,71 @@
 | 
			
		||||
import {DataRowModel} from 'app/client/models/DataRowModel';
 | 
			
		||||
import {ViewFieldRec} from 'app/client/models/entities/ViewFieldRec';
 | 
			
		||||
import {colors, testId} from 'app/client/ui2018/cssVars';
 | 
			
		||||
import {icon} from 'app/client/ui2018/icons';
 | 
			
		||||
import {NTextBox} from 'app/client/widgets/NTextBox';
 | 
			
		||||
import {dom, styled} from 'grainjs';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Creates a widget for displaying links.  Links can entered directly or following a title.
 | 
			
		||||
 * The last entry following a space is used as the url.
 | 
			
		||||
 * ie 'google https://www.google.com' would apears as 'google' to the user but link to the url.
 | 
			
		||||
 */
 | 
			
		||||
export class HyperLinkTextBox extends NTextBox {
 | 
			
		||||
  constructor(field: ViewFieldRec) {
 | 
			
		||||
    super(field);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public buildDom(row: DataRowModel) {
 | 
			
		||||
    const value = row.cells[this.field.colId()];
 | 
			
		||||
    return cssFieldClip(
 | 
			
		||||
      dom.style('text-align', this.alignment),
 | 
			
		||||
      dom.cls('text_wrapping', this.wrapping),
 | 
			
		||||
      dom.maybe(value, () =>
 | 
			
		||||
        dom('a',
 | 
			
		||||
          dom.attr('href', (use) => _constructUrl(use(value))),
 | 
			
		||||
          dom.attr('target', '_blank'),
 | 
			
		||||
          // As per Google and Mozilla recommendations to prevent opened links
 | 
			
		||||
          // from running on the same process as Grist:
 | 
			
		||||
          // https://developers.google.com/web/tools/lighthouse/audits/noopener
 | 
			
		||||
          dom.attr('rel', 'noopener noreferrer'),
 | 
			
		||||
          cssLinkIcon('FieldLink'),
 | 
			
		||||
          testId('tb-link')
 | 
			
		||||
        )
 | 
			
		||||
      ),
 | 
			
		||||
      dom.text((use) => _formatValue(use(value))),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Formats value like `foo bar baz` by discarding `baz` and returning `foo bar`.
 | 
			
		||||
 */
 | 
			
		||||
function _formatValue(value: string|null|undefined): string {
 | 
			
		||||
  if (typeof value !== 'string') { return ''; }
 | 
			
		||||
  const index = value.lastIndexOf(' ');
 | 
			
		||||
  return index >= 0 ? value.slice(0, index) : value;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Given value like `foo bar baz`, constructs URL by checking if `baz` is a valid URL and,
 | 
			
		||||
 * if not, prepending `http://`.
 | 
			
		||||
 */
 | 
			
		||||
function _constructUrl(value: string): string {
 | 
			
		||||
  const url = value.slice(value.lastIndexOf(' ') + 1);
 | 
			
		||||
  try {
 | 
			
		||||
    // Try to construct a valid URL
 | 
			
		||||
    return (new URL(url)).toString();
 | 
			
		||||
  } catch (e) {
 | 
			
		||||
    // Not a valid URL, so try to prefix it with http
 | 
			
		||||
    return 'http://' + url;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const cssFieldClip = styled('div.field_clip', `
 | 
			
		||||
  color: ${colors.lightGreen};
 | 
			
		||||
`);
 | 
			
		||||
 | 
			
		||||
const cssLinkIcon = styled(icon, `
 | 
			
		||||
  background-color: ${colors.lightGreen};
 | 
			
		||||
  margin: -1px 2px 2px 0;
 | 
			
		||||
`);
 | 
			
		||||
@ -5,7 +5,7 @@ import {cssRow} from 'app/client/ui/RightPanel';
 | 
			
		||||
import {alignmentSelect, makeButtonSelect} from 'app/client/ui2018/buttonSelect';
 | 
			
		||||
import {testId} from 'app/client/ui2018/cssVars';
 | 
			
		||||
import {NewAbstractWidget} from 'app/client/widgets/NewAbstractWidget';
 | 
			
		||||
import {Computed, dom, Observable} from 'grainjs';
 | 
			
		||||
import {dom, DomContents, fromKo, Observable} from 'grainjs';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * TextBox - The most basic widget for displaying text information.
 | 
			
		||||
@ -18,27 +18,15 @@ export class NTextBox extends NewAbstractWidget {
 | 
			
		||||
    super(field);
 | 
			
		||||
 | 
			
		||||
    this.alignment = fromKoSave<string>(this.options.prop('alignment'));
 | 
			
		||||
    const wrap = this.options.prop('wrap');
 | 
			
		||||
 | 
			
		||||
    this.wrapping = Computed.create(this, (use) => {
 | 
			
		||||
      const w = use(wrap);
 | 
			
		||||
      if (w === null || w === undefined) {
 | 
			
		||||
        // When user has yet to specify a desired wrapping state, GridView and DetailView have
 | 
			
		||||
        // different default states. GridView defaults to wrapping disabled, while DetailView
 | 
			
		||||
        // defaults to wrapping enabled.
 | 
			
		||||
        return (this.field.viewSection().parentKey() === 'record') ? false : true;
 | 
			
		||||
      } else {
 | 
			
		||||
        return w;
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
    this.wrapping = fromKo(this.field.wrapping);
 | 
			
		||||
 | 
			
		||||
    this.autoDispose(this.wrapping.addListener(() => {
 | 
			
		||||
      this.field.viewSection().events.trigger('rowHeightChange');
 | 
			
		||||
    }));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public buildConfigDom() {
 | 
			
		||||
    return dom('div',
 | 
			
		||||
  public buildConfigDom(): DomContents {
 | 
			
		||||
    return [
 | 
			
		||||
      cssRow(
 | 
			
		||||
        alignmentSelect(this.alignment),
 | 
			
		||||
        dom('div', {style: 'margin-left: 8px;'},
 | 
			
		||||
@ -46,7 +34,7 @@ export class NTextBox extends NewAbstractWidget {
 | 
			
		||||
          testId('tb-wrap-text')
 | 
			
		||||
        )
 | 
			
		||||
      )
 | 
			
		||||
    );
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public buildDom(row: DataRowModel) {
 | 
			
		||||
 | 
			
		||||
@ -10,7 +10,7 @@ import {cssLabel, cssRow} from 'app/client/ui/RightPanel';
 | 
			
		||||
import {colorSelect} from 'app/client/ui2018/buttonSelect';
 | 
			
		||||
import {colors, testId} from 'app/client/ui2018/cssVars';
 | 
			
		||||
import {BaseFormatter, createFormatter} from 'app/common/ValueFormatter';
 | 
			
		||||
import {Disposable, fromKo, Observable, styled} from 'grainjs';
 | 
			
		||||
import {Disposable, DomContents, fromKo, Observable, styled} from 'grainjs';
 | 
			
		||||
import * as ko from 'knockout';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -43,13 +43,13 @@ export abstract class NewAbstractWidget extends Disposable {
 | 
			
		||||
  /**
 | 
			
		||||
   * Builds the DOM showing configuration buttons and fields in the sidebar.
 | 
			
		||||
   */
 | 
			
		||||
  public buildConfigDom(): Element|null { return null; }
 | 
			
		||||
  public buildConfigDom(): DomContents { return null; }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Builds the transform prompt config DOM in the few cases where it is necessary.
 | 
			
		||||
   * Child classes need not override this function if they do not require transform config options.
 | 
			
		||||
   */
 | 
			
		||||
  public buildTransformConfigDom(): Element|null { return null; }
 | 
			
		||||
  public buildTransformConfigDom(): DomContents { return null; }
 | 
			
		||||
 | 
			
		||||
  public buildColorConfigDom(): Element[] {
 | 
			
		||||
    return [
 | 
			
		||||
 | 
			
		||||
@ -10,7 +10,7 @@ import {icon} from 'app/client/ui2018/icons';
 | 
			
		||||
import {NTextBox} from 'app/client/widgets/NTextBox';
 | 
			
		||||
import {clamp} from 'app/common/gutil';
 | 
			
		||||
import {buildNumberFormat, NumberFormatOptions, NumMode, NumSign} from 'app/common/NumberFormat';
 | 
			
		||||
import {Computed, dom, DomElementArg, fromKo, MultiHolder, Observable, styled} from 'grainjs';
 | 
			
		||||
import {Computed, dom, DomContents, DomElementArg, fromKo, MultiHolder, Observable, styled} from 'grainjs';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const modeOptions: Array<ISelectorOption<NumMode>> = [
 | 
			
		||||
@ -32,7 +32,7 @@ export class NumericTextBox extends NTextBox {
 | 
			
		||||
    super(field);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public buildConfigDom() {
 | 
			
		||||
  public buildConfigDom(): DomContents {
 | 
			
		||||
    // Holder for all computeds created here. It gets disposed with the returned DOM element.
 | 
			
		||||
    const holder = new MultiHolder();
 | 
			
		||||
 | 
			
		||||
@ -67,10 +67,11 @@ export class NumericTextBox extends NTextBox {
 | 
			
		||||
    const setMode = (val: NumMode) => setSave('numMode', val !== numMode.get() ? val : undefined);
 | 
			
		||||
    const setSign = (val: NumSign) => setSave('numSign', val !== numSign.get() ? val : undefined);
 | 
			
		||||
 | 
			
		||||
    return dom.update(super.buildConfigDom(),
 | 
			
		||||
      dom.autoDispose(holder),
 | 
			
		||||
    return [
 | 
			
		||||
      super.buildConfigDom(),
 | 
			
		||||
      cssLabel('Number Format'),
 | 
			
		||||
      cssRow(
 | 
			
		||||
        dom.autoDispose(holder),
 | 
			
		||||
        makeButtonSelect(numMode, modeOptions, setMode, {}, cssModeSelect.cls(''), testId('numeric-mode')),
 | 
			
		||||
        makeButtonSelect(numSign, signOptions, setSign, {}, cssSignSelect.cls(''), testId('numeric-sign')),
 | 
			
		||||
      ),
 | 
			
		||||
@ -79,7 +80,7 @@ export class NumericTextBox extends NTextBox {
 | 
			
		||||
        decimals('min', minDecimals, defaultMin, setMinDecimals, testId('numeric-min-decimals')),
 | 
			
		||||
        decimals('max', maxDecimals, defaultMax, setMaxDecimals, testId('numeric-max-decimals')),
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,112 +0,0 @@
 | 
			
		||||
var _              = require('underscore');
 | 
			
		||||
var ko             = require('knockout');
 | 
			
		||||
 | 
			
		||||
var gutil          = require('app/common/gutil');
 | 
			
		||||
 | 
			
		||||
var dispose        = require('../lib/dispose');
 | 
			
		||||
var dom            = require('../lib/dom');
 | 
			
		||||
var kd             = require('../lib/koDom');
 | 
			
		||||
 | 
			
		||||
var TextBox        = require('./TextBox');
 | 
			
		||||
 | 
			
		||||
const {colors, testId}   = require('app/client/ui2018/cssVars');
 | 
			
		||||
const {icon}             = require('app/client/ui2018/icons');
 | 
			
		||||
const {select}           = require('app/client/ui2018/menus');
 | 
			
		||||
const {cssLabel, cssRow} = require('app/client/ui/RightPanel');
 | 
			
		||||
const {isVersions}       = require('app/common/gristTypes');
 | 
			
		||||
const {Computed, styled} = require('grainjs');
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Reference - The widget for displaying references to another table's records.
 | 
			
		||||
 */
 | 
			
		||||
function Reference(field) {
 | 
			
		||||
  TextBox.call(this, field);
 | 
			
		||||
  var col = field.column();
 | 
			
		||||
  this.field = field;
 | 
			
		||||
  this.colId = col.colId;
 | 
			
		||||
  this.docModel = this.field._table.docModel;
 | 
			
		||||
 | 
			
		||||
  this._refValueFormatter = this.autoDispose(ko.computed(() =>
 | 
			
		||||
    field.createVisibleColFormatter()));
 | 
			
		||||
 | 
			
		||||
  this._visibleColRef = Computed.create(this, (use) => use(this.field.visibleColRef));
 | 
			
		||||
  // Note that saveOnly is used here to prevent display value flickering on visible col change.
 | 
			
		||||
  this._visibleColRef.onWrite((val) => this.field.visibleColRef.saveOnly(val));
 | 
			
		||||
 | 
			
		||||
  this._validCols = Computed.create(this, (use) => {
 | 
			
		||||
    const refTable = use(use(this.field.column).refTable);
 | 
			
		||||
    if (!refTable) { return []; }
 | 
			
		||||
    return use(use(refTable.columns))
 | 
			
		||||
      .filter(col => !use(col.isHiddenCol))
 | 
			
		||||
      .map(col => ({
 | 
			
		||||
        label: use(col.label),
 | 
			
		||||
        value: col.getRowId(),
 | 
			
		||||
        icon: 'FieldColumn',
 | 
			
		||||
        disabled: gutil.startsWith(use(col.type), 'Ref:') || use(col.isTransforming)
 | 
			
		||||
      }))
 | 
			
		||||
      .concat([{label: 'Row ID', value: 0, icon: 'FieldColumn'}]);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  // Computed returns a function that formats cell values.
 | 
			
		||||
  this._formatValue = this.autoDispose(ko.computed(() => {
 | 
			
		||||
    // If the field is pulling values from a display column, use a general-purpose formatter.
 | 
			
		||||
    if (this.field.displayColRef() !== this.field.colRef()) {
 | 
			
		||||
      const fmt = this._refValueFormatter();
 | 
			
		||||
      return (val) => fmt.formatAny(val);
 | 
			
		||||
    } else {
 | 
			
		||||
      const refTable = this.field.column().refTable();
 | 
			
		||||
      const refTableId = refTable ? refTable.tableId() : "";
 | 
			
		||||
      return (val) => val > 0 ? `${refTableId}[${val}]` : "";
 | 
			
		||||
    }
 | 
			
		||||
  }));
 | 
			
		||||
}
 | 
			
		||||
dispose.makeDisposable(Reference);
 | 
			
		||||
_.extend(Reference.prototype, TextBox.prototype);
 | 
			
		||||
 | 
			
		||||
Reference.prototype.buildConfigDom = function() {
 | 
			
		||||
  return [
 | 
			
		||||
    cssLabel('SHOW COLUMN'),
 | 
			
		||||
    cssRow(
 | 
			
		||||
      select(this._visibleColRef, this._validCols),
 | 
			
		||||
      testId('fbuilder-ref-col-select')
 | 
			
		||||
    )
 | 
			
		||||
  ];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
Reference.prototype.buildTransformConfigDom = function() {
 | 
			
		||||
  return this.buildConfigDom();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
Reference.prototype.buildDom = function(row, options) {
 | 
			
		||||
  const formattedValue = ko.computed(() => {
 | 
			
		||||
    if (row._isAddRow() || this.isDisposed() || this.field.displayColModel().isDisposed()) {
 | 
			
		||||
      // Work around JS errors during certain changes (noticed when visibleCol field gets removed
 | 
			
		||||
      // for a column using per-field settings).
 | 
			
		||||
      return "";
 | 
			
		||||
    }
 | 
			
		||||
    const value = row.cells[this.field.displayColModel().colId()];
 | 
			
		||||
    if (!value) { return ""; }
 | 
			
		||||
    const content = value();
 | 
			
		||||
    if (isVersions(content)) {
 | 
			
		||||
      // We can arrive here if the reference value is unchanged (viewed as a foreign key)
 | 
			
		||||
      // but the content of its displayCol has changed.  Postponing doing anything about
 | 
			
		||||
      // this until we have three-way information for computed columns.  For now,
 | 
			
		||||
      // just showing one version of the cell.  TODO: elaborate.
 | 
			
		||||
      return this._formatValue()(content[1].local || content[1].parent);
 | 
			
		||||
    }
 | 
			
		||||
    return this._formatValue()(content);
 | 
			
		||||
  });
 | 
			
		||||
  return dom('div.field_clip',
 | 
			
		||||
    dom.autoDispose(formattedValue),
 | 
			
		||||
    cssRefIcon('FieldReference',
 | 
			
		||||
      testId('ref-link-icon')
 | 
			
		||||
    ),
 | 
			
		||||
    kd.text(formattedValue));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const cssRefIcon = styled(icon, `
 | 
			
		||||
  background-color: ${colors.slate};
 | 
			
		||||
  margin: -1px 2px 2px 0;
 | 
			
		||||
`);
 | 
			
		||||
 | 
			
		||||
module.exports = Reference;
 | 
			
		||||
							
								
								
									
										106
									
								
								app/client/widgets/Reference.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								app/client/widgets/Reference.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,106 @@
 | 
			
		||||
import {DataRowModel} from 'app/client/models/DataRowModel';
 | 
			
		||||
import {ViewFieldRec} from 'app/client/models/entities/ViewFieldRec';
 | 
			
		||||
import {cssLabel, cssRow} from 'app/client/ui/RightPanel';
 | 
			
		||||
import {colors, testId} from 'app/client/ui2018/cssVars';
 | 
			
		||||
import {icon} from 'app/client/ui2018/icons';
 | 
			
		||||
import {IOptionFull, select} from 'app/client/ui2018/menus';
 | 
			
		||||
import {NTextBox} from 'app/client/widgets/NTextBox';
 | 
			
		||||
import {isVersions} from 'app/common/gristTypes';
 | 
			
		||||
import {BaseFormatter} from 'app/common/ValueFormatter';
 | 
			
		||||
import {Computed, dom, styled} from 'grainjs';
 | 
			
		||||
import * as ko from 'knockout';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Reference - The widget for displaying references to another table's records.
 | 
			
		||||
 */
 | 
			
		||||
export class Reference extends NTextBox {
 | 
			
		||||
  private _refValueFormatter: ko.Computed<BaseFormatter>;
 | 
			
		||||
  private _visibleColRef: Computed<number>;
 | 
			
		||||
  private _validCols: Computed<Array<IOptionFull<number>>>;
 | 
			
		||||
  private _formatValue: Computed<(val: any) => string>;
 | 
			
		||||
 | 
			
		||||
  constructor(field: ViewFieldRec) {
 | 
			
		||||
    super(field);
 | 
			
		||||
 | 
			
		||||
    this._refValueFormatter = this.autoDispose(ko.computed(() =>
 | 
			
		||||
      field.createVisibleColFormatter()));
 | 
			
		||||
 | 
			
		||||
    this._visibleColRef = Computed.create(this, (use) => use(this.field.visibleColRef));
 | 
			
		||||
    // Note that saveOnly is used here to prevent display value flickering on visible col change.
 | 
			
		||||
    this._visibleColRef.onWrite((val) => this.field.visibleColRef.saveOnly(val));
 | 
			
		||||
 | 
			
		||||
    this._validCols = Computed.create(this, (use) => {
 | 
			
		||||
      const refTable = use(use(this.field.column).refTable);
 | 
			
		||||
      if (!refTable) { return []; }
 | 
			
		||||
      return use(use(refTable.columns).getObservable())
 | 
			
		||||
        .filter(col => !use(col.isHiddenCol))
 | 
			
		||||
        .map<IOptionFull<number>>(col => ({
 | 
			
		||||
          label: use(col.label),
 | 
			
		||||
          value: col.getRowId(),
 | 
			
		||||
          icon: 'FieldColumn',
 | 
			
		||||
          disabled: use(col.type).startsWith('Ref:') || use(col.isTransforming)
 | 
			
		||||
        }))
 | 
			
		||||
        .concat([{label: 'Row ID', value: 0, icon: 'FieldColumn'}]);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // Computed returns a function that formats cell values.
 | 
			
		||||
    this._formatValue = Computed.create(this, (use) => {
 | 
			
		||||
      // If the field is pulling values from a display column, use a general-purpose formatter.
 | 
			
		||||
      if (use(this.field.displayColRef) !== use(this.field.colRef)) {
 | 
			
		||||
        const fmt = use(this._refValueFormatter);
 | 
			
		||||
        return (val: any) => fmt.formatAny(val);
 | 
			
		||||
      } else {
 | 
			
		||||
        const refTable = use(use(this.field.column).refTable);
 | 
			
		||||
        const refTableId = refTable ? use(refTable.tableId) : "";
 | 
			
		||||
        return (val: any) => val > 0 ? `${refTableId}[${val}]` : "";
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public buildConfigDom() {
 | 
			
		||||
    return [
 | 
			
		||||
      cssLabel('SHOW COLUMN'),
 | 
			
		||||
      cssRow(
 | 
			
		||||
        select(this._visibleColRef, this._validCols),
 | 
			
		||||
        testId('fbuilder-ref-col-select')
 | 
			
		||||
      )
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public buildTransformConfigDom() {
 | 
			
		||||
    return this.buildConfigDom();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public buildDom(row: DataRowModel) {
 | 
			
		||||
    const formattedValue = Computed.create(null, (use) => {
 | 
			
		||||
      if (use(row._isAddRow) || this.isDisposed() || use(this.field.displayColModel).isDisposed()) {
 | 
			
		||||
        // Work around JS errors during certain changes (noticed when visibleCol field gets removed
 | 
			
		||||
        // for a column using per-field settings).
 | 
			
		||||
        return "";
 | 
			
		||||
      }
 | 
			
		||||
      const value = row.cells[use(use(this.field.displayColModel).colId)];
 | 
			
		||||
      if (!value) { return ""; }
 | 
			
		||||
      const content = use(value);
 | 
			
		||||
      if (isVersions(content)) {
 | 
			
		||||
        // We can arrive here if the reference value is unchanged (viewed as a foreign key)
 | 
			
		||||
        // but the content of its displayCol has changed.  Postponing doing anything about
 | 
			
		||||
        // this until we have three-way information for computed columns.  For now,
 | 
			
		||||
        // just showing one version of the cell.  TODO: elaborate.
 | 
			
		||||
        return use(this._formatValue)(content[1].local || content[1].parent);
 | 
			
		||||
      }
 | 
			
		||||
      return use(this._formatValue)(content);
 | 
			
		||||
    });
 | 
			
		||||
    return dom('div.field_clip',
 | 
			
		||||
      dom.autoDispose(formattedValue),
 | 
			
		||||
      cssRefIcon('FieldReference',
 | 
			
		||||
        testId('ref-link-icon')
 | 
			
		||||
      ),
 | 
			
		||||
      dom.text(formattedValue)
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const cssRefIcon = styled(icon, `
 | 
			
		||||
  background-color: ${colors.slate};
 | 
			
		||||
  margin: -1px 2px 2px 0;
 | 
			
		||||
`);
 | 
			
		||||
@ -1,71 +0,0 @@
 | 
			
		||||
var _ = require('underscore');
 | 
			
		||||
var ko = require('knockout');
 | 
			
		||||
var dom = require('../lib/dom');
 | 
			
		||||
var dispose = require('../lib/dispose');
 | 
			
		||||
var kd = require('../lib/koDom');
 | 
			
		||||
var AbstractWidget = require('./AbstractWidget');
 | 
			
		||||
var modelUtil = require('../models/modelUtil');
 | 
			
		||||
 | 
			
		||||
const {fromKoSave} = require('app/client/lib/fromKoSave');
 | 
			
		||||
const {alignmentSelect, buttonToggleSelect} = require('app/client/ui2018/buttonSelect');
 | 
			
		||||
const {testId} = require('app/client/ui2018/cssVars');
 | 
			
		||||
const {cssRow} = require('app/client/ui/RightPanel');
 | 
			
		||||
const {Computed} = require('grainjs');
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * TextBox - The most basic widget for displaying text information.
 | 
			
		||||
 */
 | 
			
		||||
function TextBox(field) {
 | 
			
		||||
  AbstractWidget.call(this, field);
 | 
			
		||||
  this.alignment = this.options.prop('alignment');
 | 
			
		||||
  let wrap = this.options.prop('wrap');
 | 
			
		||||
  this.wrapping = this.autoDispose(ko.computed({
 | 
			
		||||
    read: () => {
 | 
			
		||||
      let w = wrap();
 | 
			
		||||
      if (w === null || w === undefined) {
 | 
			
		||||
        // When user has yet to specify a desired wrapping state, GridView and DetailView have
 | 
			
		||||
        // different default states. GridView defaults to wrapping disabled, while DetailView
 | 
			
		||||
        // defaults to wrapping enabled.
 | 
			
		||||
        return (this.field.viewSection().parentKey() === 'record') ? false : true;
 | 
			
		||||
      } else {
 | 
			
		||||
        return w;
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    write: val => wrap(val)
 | 
			
		||||
  }));
 | 
			
		||||
  modelUtil.addSaveInterface(this.wrapping, val => wrap.saveOnly(val));
 | 
			
		||||
 | 
			
		||||
  this.autoDispose(this.wrapping.subscribe(() =>
 | 
			
		||||
    this.field.viewSection().events.trigger('rowHeightChange')
 | 
			
		||||
  ));
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
dispose.makeDisposable(TextBox);
 | 
			
		||||
_.extend(TextBox.prototype, AbstractWidget.prototype);
 | 
			
		||||
 | 
			
		||||
TextBox.prototype.buildConfigDom = function() {
 | 
			
		||||
  const wrapping = Computed.create(null, use => use(this.wrapping));
 | 
			
		||||
  wrapping.onWrite((val) => modelUtil.setSaveValue(this.wrapping, Boolean(val)));
 | 
			
		||||
 | 
			
		||||
  return dom('div',
 | 
			
		||||
    cssRow(
 | 
			
		||||
      dom.autoDispose(wrapping),
 | 
			
		||||
      alignmentSelect(fromKoSave(this.alignment)),
 | 
			
		||||
      dom('div', {style: 'margin-left: 8px;'},
 | 
			
		||||
        buttonToggleSelect(wrapping, [{value: true, icon: 'Wrap'}]),
 | 
			
		||||
        testId('tb-wrap-text')
 | 
			
		||||
      )
 | 
			
		||||
    )
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
TextBox.prototype.buildDom = function(row) {
 | 
			
		||||
  let value = row[this.field.colId()];
 | 
			
		||||
  return dom('div.field_clip',
 | 
			
		||||
    kd.style('text-align', this.alignment),
 | 
			
		||||
    kd.toggleClass('text_wrapping', this.wrapping),
 | 
			
		||||
    kd.text(() => row._isAddRow() ? '' : this.valueFormatter().format(value()))
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
module.exports = TextBox;
 | 
			
		||||
@ -7,6 +7,9 @@ const UserType = require('./UserType');
 | 
			
		||||
const {HyperLinkEditor} = require('./HyperLinkEditor');
 | 
			
		||||
const {NTextEditor} = require('./NTextEditor');
 | 
			
		||||
const {ReferenceEditor} = require('./ReferenceEditor');
 | 
			
		||||
const {HyperLinkTextBox} = require('./HyperLinkTextBox');
 | 
			
		||||
const {ChoiceTextBox } = require('./ChoiceTextBox');
 | 
			
		||||
const {Reference} = require('./Reference');
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Convert the name of a widget to its implementation.
 | 
			
		||||
@ -15,15 +18,15 @@ const nameToWidget = {
 | 
			
		||||
  'TextBox': NTextBox,
 | 
			
		||||
  'TextEditor': NTextEditor,
 | 
			
		||||
  'NumericTextBox': NumericTextBox,
 | 
			
		||||
  'HyperLinkTextBox': require('./HyperLinkTextBox'),
 | 
			
		||||
  'HyperLinkTextBox': HyperLinkTextBox,
 | 
			
		||||
  'HyperLinkEditor': HyperLinkEditor,
 | 
			
		||||
  'Spinner': Spinner,
 | 
			
		||||
  'CheckBox': require('./CheckBox'),
 | 
			
		||||
  'CheckBoxEditor': require('./CheckBoxEditor'),
 | 
			
		||||
  'Reference': require('./Reference'),
 | 
			
		||||
  'Reference': Reference,
 | 
			
		||||
  'Switch': require('./Switch'),
 | 
			
		||||
  'ReferenceEditor': ReferenceEditor,
 | 
			
		||||
  'ChoiceTextBox': require('./ChoiceTextBox'),
 | 
			
		||||
  'ChoiceTextBox': ChoiceTextBox,
 | 
			
		||||
  'ChoiceEditor': require('./ChoiceEditor'),
 | 
			
		||||
  'DateTimeTextBox': require('./DateTimeTextBox'),
 | 
			
		||||
  'DateTextBox': require('./DateTextBox'),
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user