widgets/fileselect.js

  1. /*
  2. * This file is part of AUX.
  3. *
  4. * AUX is free software; you can redistribute it and/or
  5. * modify it under the terms of the GNU General Public
  6. * License as published by the Free Software Foundation; either
  7. * version 3 of the License, or (at your option) any later version.
  8. *
  9. * AUX is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  12. * General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU General
  15. * Public License along with this program; if not, write to the
  16. * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
  17. * Boston, MA 02110-1301 USA
  18. */
  19. import { element, addClass, createID } from './../utils/dom.js';
  20. import { limitDigits } from './../utils/limit_digits.js';
  21. import { FORMAT } from './../utils/sprintf.js';
  22. import { Container } from './container.js';
  23. import { Button } from './button.js';
  24. import { Label } from './label.js';
  25. import { defineChildWidget } from '../child_widget.js';
  26. import { defineChildElement } from '../widget_helpers.js';
  27. import { defineMeasure, defineRender } from '../renderer.js';
  28. /**
  29. * FileSelect is a file selector widget. It inherits all options of {@link Button}.
  30. *
  31. * @class FileSelect
  32. *
  33. * @extends Container
  34. *
  35. * @property {String} [options.accept=""] - The allowed file types as suffices
  36. * starting with a dot or as mime types with optional asterisk,
  37. * e.g. ".txt,.zip,.png,.jpg,image/*,application/pdf"
  38. * @property {boolean} [options.multiple=false] - Defines if users can select multiple files.
  39. * The label for the file name shows the amount of files selected instead of a single name
  40. * and the label displaying the file size shows the sum of all selected files sizes.
  41. * @property {String} [options.placeholder='No file(s) selected'] - The label to show as file name
  42. * if no file is selected.
  43. * @property {Function} [options.format_size=limitDigits(4, 'B', 1024)] - the formatting
  44. * function for the file size label.
  45. * @property {Function} [options.format_multiple=FORMAT('%d files selected') - the formatting
  46. * function for the file size label.
  47. * @property {FileList} [options.files=[]] -
  48. * @property {String} [options.filename=false] - The name of the selected file or `false`
  49. * if no file is selected. Read-only property!
  50. * @property {Integer} [options.filesize=0] - The size of the selected filein bytes.
  51. * Read-only property!
  52. */
  53. export class FileSelect extends Container {
  54. static get _options() {
  55. return {
  56. accept: 'string',
  57. multiple: 'boolean',
  58. placeholder: 'string',
  59. files: 'object',
  60. filename: 'string|boolean',
  61. filesize: 'number',
  62. format_size: 'function',
  63. format_multiple: 'function',
  64. };
  65. }
  66. static get options() {
  67. return {
  68. accept: '',
  69. multiple: false,
  70. placeholder: 'No file(s) selected',
  71. files: [],
  72. filename: false,
  73. filesize: 0,
  74. format_size: limitDigits(4, 'B', 1024),
  75. format_multiple: FORMAT('%d files selected'),
  76. icon: 'open',
  77. };
  78. }
  79. static get renderers() {
  80. return [
  81. defineMeasure(['files', 'placeholder', 'format_multiple'], function (
  82. files,
  83. placeholder,
  84. format_multiple
  85. ) {
  86. let filename;
  87. if (!files.length) {
  88. filename = placeholder;
  89. } else if (files.length === 1) {
  90. filename = files[0].name;
  91. } else {
  92. filename = format_multiple(files.length);
  93. }
  94. this.set('filename', filename);
  95. }),
  96. defineMeasure('files', function (files) {
  97. let filesize = 0;
  98. [...files].forEach((file) => (filesize += file.size));
  99. this.set('filesize', filesize);
  100. }),
  101. defineMeasure(['filesize', 'format_size'], function (
  102. filesize,
  103. format_size
  104. ) {
  105. this.size.set('label', format_size(filesize));
  106. }),
  107. defineMeasure('filename', function (filename) {
  108. this.name.set('label', filename);
  109. }),
  110. defineRender('accept', function (accept) {
  111. this._input.setAttribute('accept', accept);
  112. }),
  113. defineRender('multiple', function (multiple) {
  114. const _input = this._input;
  115. if (multiple) _input.setAttribute('multiple', true);
  116. else _input.removeAttribute('multiple');
  117. }),
  118. ];
  119. }
  120. constructor(options) {
  121. super(options);
  122. }
  123. draw(O, element) {
  124. const id = createID('aux-fileinput-');
  125. super.draw(O, element);
  126. addClass(element, 'aux-fileselect');
  127. this._input.addEventListener('input', this._onInput.bind(this));
  128. this._input.setAttribute('id', id);
  129. this._label.setAttribute('for', id);
  130. }
  131. _onInput(e) {
  132. this.userset('files', this._input.files);
  133. /**
  134. * Is fired when one or more or no files were selected by the user.
  135. *
  136. * @event Fader#scalechanged
  137. *
  138. * @param {string} key - The key of the option.
  139. * @param {mixed} value - The value to which it was set.
  140. */
  141. this.emit('select', this.options.files);
  142. }
  143. }
  144. /** @member {HTMLFileInput} FileSelect#_input - HTMLFileInput element.
  145. * Has class <code>.aux-fileinput</code>.
  146. */
  147. defineChildElement(FileSelect, 'input', {
  148. show: true,
  149. create: function () {
  150. return element('INPUT', {
  151. type: 'file',
  152. class: 'aux-fileinput',
  153. });
  154. },
  155. });
  156. /** @member {HTMLLabel} FileSelect#_label - The HTMLLabel element connecting
  157. * the widgets with the file input.
  158. * Has class <code>.aux-filelabel</code>.
  159. */
  160. defineChildElement(FileSelect, 'label', {
  161. show: true,
  162. create: function () {
  163. return element('LABEL', {
  164. type: 'file',
  165. class: 'aux-filelabel',
  166. });
  167. },
  168. option: 'foobar',
  169. });
  170. /**
  171. * @member {Button} FileSelect#button - The {@link Button} for opening the file selector.
  172. */
  173. defineChildWidget(FileSelect, 'button', {
  174. create: Button,
  175. show: true,
  176. toggle_class: true,
  177. inherit_options: true,
  178. append: function () {
  179. this._label.appendChild(this.button.element);
  180. },
  181. option: 'foobar',
  182. static_events: {
  183. keydown: function (e) {
  184. if (e.code === 'Enter' || e.code === 'Space') {
  185. this.parent._input.click();
  186. return false;
  187. }
  188. },
  189. },
  190. });
  191. /**
  192. * @member {Label} FileSelect#name - The {@link Label} for displaying the file name.
  193. * Has class `aux-name`.
  194. */
  195. defineChildWidget(FileSelect, 'name', {
  196. create: Label,
  197. show: true,
  198. toggle_class: true,
  199. default_options: {
  200. class: 'aux-name',
  201. },
  202. });
  203. /**
  204. * @member {Label} FileSelect#size - The {@link Label} for displaying the file size.
  205. * Has class `aux-size`.
  206. */
  207. defineChildWidget(FileSelect, 'size', {
  208. create: Label,
  209. show: true,
  210. toggle_class: true,
  211. default_options: {
  212. class: 'aux-size',
  213. },
  214. });