widget.ts 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. import {
  4. IKernel
  5. } from 'jupyter-js-services';
  6. import {
  7. Message
  8. } from 'phosphor/lib/core/messaging';
  9. import {
  10. Widget
  11. } from 'phosphor/lib/ui/widget';
  12. import {
  13. ABCWidgetFactory, IDocumentModel, IDocumentContext
  14. } from '../docregistry';
  15. import {
  16. HTML_COMMON_CLASS
  17. } from '../renderers/widget';
  18. import * as d3Dsv from 'd3-dsv';
  19. import {
  20. PanelLayout
  21. } from 'phosphor/lib/ui/panel';
  22. /**
  23. * The class name added to a csv widget.
  24. */
  25. const CSV_CLASS = 'jp-CSVWidget';
  26. /**
  27. * The class name added to a csv toolbar widget.
  28. */
  29. const CSV_TOOLBAR_CLASS = 'jp-CSVWidget-toolbar';
  30. /**
  31. * The class name added to a csv toolbar's dropdown element.
  32. */
  33. const CSV_TOOLBAR_DROPDOWN_CLASS = 'jp-CSVWidget-toolbarDropdown';
  34. /**
  35. * The class name added to a csv table widget.
  36. */
  37. const CSV_TABLE_CLASS = 'jp-CSVWidget-table';
  38. /**
  39. * The class name added to a csv warning widget.
  40. */
  41. const CSV_WARNING_CLASS = 'jp-CSVWidget-warning';
  42. /**
  43. * The hard limit on the number of rows to display.
  44. */
  45. const DISPLAY_LIMIT = 1000;
  46. /**
  47. * A widget for csv tables.
  48. */
  49. export
  50. class CSVWidget extends Widget {
  51. /**
  52. * Construct a new csv table widget.
  53. */
  54. constructor(context: IDocumentContext<IDocumentModel>) {
  55. super();
  56. this._context = context;
  57. this.node.tabIndex = -1;
  58. this.addClass(CSV_CLASS);
  59. this.layout = new PanelLayout();
  60. this._toolbar = new Widget({ node: createDelimiterSwitcherNode() });
  61. this._toolbar.addClass(CSV_TOOLBAR_CLASS);
  62. this._table = new Widget();
  63. this._table.addClass(CSV_TABLE_CLASS);
  64. this._table.addClass(HTML_COMMON_CLASS);
  65. this._warning = new Widget();
  66. this._warning.addClass(CSV_WARNING_CLASS);
  67. let layout = this.layout as PanelLayout;
  68. layout.addWidget(this._toolbar);
  69. layout.addWidget(this._table);
  70. layout.addWidget(this._warning);
  71. let select = this._toolbar.node.getElementsByClassName(
  72. CSV_TOOLBAR_DROPDOWN_CLASS)[0] as HTMLSelectElement;
  73. if (context.model.toString()) {
  74. this.update();
  75. }
  76. context.pathChanged.connect(() => {
  77. this.update();
  78. });
  79. context.model.contentChanged.connect(() => {
  80. this.update();
  81. });
  82. context.contentsModelChanged.connect(() => {
  83. this.update();
  84. });
  85. // Change delimiter on a change in the dropdown.
  86. select.addEventListener('change', event => {
  87. this.delimiter = select.value;
  88. this.update();
  89. });
  90. }
  91. /**
  92. * Dispose of the resources used by the widget.
  93. */
  94. dispose(): void {
  95. if (this.isDisposed) {
  96. return;
  97. }
  98. this._context = null;
  99. super.dispose();
  100. }
  101. /**
  102. * Handle `update-request` messages for the widget.
  103. */
  104. protected onUpdateRequest(msg: Message): void {
  105. this.title.label = this._context.path.split('/').pop();
  106. let cm = this._context.contentsModel;
  107. if (cm === null) {
  108. return;
  109. }
  110. let content = this._context.model.toString();
  111. let delimiter = this.delimiter as string;
  112. this.renderTable(content, delimiter);
  113. }
  114. /**
  115. * Render an html table from a csv string.
  116. */
  117. renderTable(content: string, delimiter: string) {
  118. let parsed = d3Dsv.dsvFormat(delimiter).parse(content);
  119. let table = document.createElement('table');
  120. let header = document.createElement('thead');
  121. let body = document.createElement('tbody');
  122. for (let name of parsed.columns) {
  123. let th = document.createElement('th');
  124. th.textContent = name;
  125. header.appendChild(th);
  126. }
  127. for (let row of parsed.slice(0, DISPLAY_LIMIT)) {
  128. let tr = document.createElement('tr');
  129. for (let col of parsed.columns) {
  130. let td = document.createElement('td');
  131. td.textContent = row[col];
  132. tr.appendChild(td);
  133. }
  134. body.appendChild(tr);
  135. }
  136. let msg = `Table is too long to render, rendering ${DISPLAY_LIMIT} of ` +
  137. `${parsed.length} rows`;
  138. if (parsed.length > DISPLAY_LIMIT) {
  139. this._warning.node.textContent = msg;
  140. } else {
  141. this._warning.node.textContent = '';
  142. }
  143. table.appendChild(header);
  144. table.appendChild(body);
  145. this._table.node.textContent = '';
  146. this._table.node.appendChild(table);
  147. }
  148. /**
  149. * Handle `'activate-request'` messages.
  150. */
  151. protected onActivateRequest(msg: Message): void {
  152. this.node.focus();
  153. }
  154. private _context: IDocumentContext<IDocumentModel>;
  155. private delimiter: string = ',';
  156. private _toolbar: Widget = null;
  157. private _table: Widget = null;
  158. private _warning: Widget = null;
  159. }
  160. /**
  161. * Create the node for the delimiter switcher.
  162. */
  163. function createDelimiterSwitcherNode(): HTMLElement {
  164. let div = document.createElement('div');
  165. let label = document.createElement('span');
  166. label.textContent = 'Delimiter:';
  167. let select = document.createElement('select');
  168. for (let delim of [',', ';', '\t']) {
  169. let option = document.createElement('option');
  170. option.value = delim;
  171. if (delim === '\t') {
  172. option.textContent = '\\t';
  173. } else {
  174. option.textContent = delim;
  175. }
  176. select.appendChild(option);
  177. }
  178. select.className = CSV_TOOLBAR_DROPDOWN_CLASS;
  179. div.appendChild(label);
  180. div.appendChild(select);
  181. return div;
  182. }
  183. /**
  184. * A widget factory for csv tables.
  185. */
  186. export
  187. class CSVWidgetFactory extends ABCWidgetFactory<CSVWidget, IDocumentModel> {
  188. /**
  189. * Create a new widget given a context.
  190. */
  191. createNew(context: IDocumentContext<IDocumentModel>, kernel?: IKernel.IModel): CSVWidget {
  192. let widget = new CSVWidget(context);
  193. this.widgetCreated.emit(widget);
  194. return widget;
  195. }
  196. }