diff --git a/.docs/assets.md b/.docs/assets.md index 0a1674b90..d51a304a7 100644 --- a/.docs/assets.md +++ b/.docs/assets.md @@ -6,116 +6,103 @@ Table of contents # Assets -Datagrid needs for its precise functionality some third party scripts and styles. Install all required assets with NPM. +There are prepare JS/TS and CSS files for precise functionality. The best way is to use some frontend bundler, for example [Vite](https://vitejs.dev). -**CSS (external)** +## Installation -- bootstrap 5 -- bootstrap datepicker -- bootstrap select - -**CSS** - -- datagrid.css -- datagrid-spinners.css - -**JS (external)** - -- jquery -- nette forms -- nette ajax / naja -- bootstrap -- bootstrap datepicker -- bootstrap select - -**JS** - -- datagrid.js -- datagrid-instant-url-refresh.js -- datagrid-spinners.js - -**Icons** - -You will probably want to use some icon font, but that is in your command. -On this project website we use font awesome (you can change the icon prefix by setting new value to static property `Datagrid::$iconPrefix = 'fa fa-';`). - -**Spinners** - -As you can see, there is also a `datagrid-spinners.js` script in a datagrid repository. If you include this file within you project layout, there are some actions, that will show spinner/some other animation when waiting for ajax response. Actions, that has somehow animated spinner: - -- Group actions -- Pagination -- Changing items per page -- Toggling item detail - loading the detail for the first time - -## NPM - -``` -npm install --save ublaboo-datagrid -``` - -package.json: +You need to install datagrid's assets. For example this way. ```json { - "dependencies": { - "bootstrap-datepicker": "^1.9", - "bootstrap-select": "^1.14-beta2", - "bootstrap": "^5.0.0", - "happy-inputs": "^2.0", - "jquery": "^3.4.1", - "jquery-ui-sortable": "^1.0", - "nette-forms": "^3.0", - "nette.ajax.js": "^2.3", - "popper.js": "^1.14.7", - "ublaboo-datagrid": "^6.9" - } + "dependencies": { + "@contributte/datagrid": "git+ssh://git@github.com:contributte/datagrid.git#next" + } } ``` -## Example html when not using NPM - -```html - - -
- - - - - - - - - - +## - - +## Demo - - - - - - - - - - +**package.json** - - - - - +```json +{ + "dependencies": { + "@contributte/datagrid": "git+ssh://git@github.com:contributte/datagrid.git#next", + "@fortawesome/fontawesome-free": "^6.3.0", + "bootstrap": "^5.3.0-alpha3", + "naja": "^2.5.0", + "nette-forms": "^3.3.1", + "prismjs": "^1.29.0", + "sortablejs": "^1.15.0", + "tom-select": "^2.2.2", + "vanillajs-datepicker": "^1.3.1" + }, + "devDependencies": { + "@types/bootstrap-select": "^1.13.4", + "@types/jquery": "^3.5.16", + "@types/jqueryui": "^1.12.16", + "@types/sortablejs": "^1.15.1", + "@types/vanillajs-datepicker": "^1.2.1", + "autoprefixer": "^10.4.0", + "typescript": "^4.9.5", + "vite": "^2.6.10" + }, + "scripts": { + "watch": "vite build --watch --mode=development", + "build": "vite build --mode=production" + } +} +``` - - - - +**vite.config.js** + +```js +import { defineConfig } from 'vite'; +import { resolve } from 'path'; + +export default defineConfig(({ mode }) => { + const DEV = mode === 'development'; + + return { + publicDir: './assets/public', + resolve: { + alias: { + '@': resolve(__dirname, 'assets/js'), + '~': resolve(__dirname, 'node_modules'), + }, + }, + base: '/dist/', + server: { + open: false, + hmr: false, + }, + css: { + postcss: [ + "autoprefixer" + ] + }, + build: { + manifest: true, + assetsDir: '', + outDir: './www/dist/', + emptyOutDir: true, + minify: DEV ? false : 'esbuild', + rollupOptions: { + output: { + manualChunks: undefined, + chunkFileNames: '[name].js', + entryFileNames: '[name].js', + assetFileNames: '[name].[ext]', + }, + input: { + app: './assets/js/main.js' + } + } + }, + } +}); ``` diff --git a/assets/ajax/index.ts b/assets/ajax/index.ts new file mode 100644 index 000000000..23b37df97 --- /dev/null +++ b/assets/ajax/index.ts @@ -0,0 +1 @@ +export * from "./naja"; diff --git a/assets/ajax/naja.ts b/assets/ajax/naja.ts new file mode 100644 index 000000000..fbe0658f4 --- /dev/null +++ b/assets/ajax/naja.ts @@ -0,0 +1,135 @@ +import type { Naja } from "naja"; +import type { + Ajax, + AjaxEventMap as BaseAjaxEventMap, + BaseRequestParams as AjaxBaseRequestParams, + BeforeEventDetail as BaseBeforeEventDetail, + DatagridPayload, + ErrorEventDetail as BaseErrorEventDetail, + EventDetail, + EventListener, + InteractEventDetail as BaseInteractEventDetail, + Payload, + RequestParams, + Response as AjaxResponse, + SuccessEventDetail as BaseSuccessEventDetail, +} from "../types"; +import { Datagrid } from "../datagrid"; +import { BeforeEvent, ErrorEvent, Payload as NajaPayload, SuccessEvent } from "naja/dist/Naja"; +import { InteractionEvent } from "naja/dist/core/UIHandler"; + +export interface BaseRequestParams extends AjaxBaseRequestParams, Request { + url: string; + method: string; +} + +export interface BeforeEventDetail, EventDetail & NajaPayload;
+ response: AjaxResponse & Response;
+}
+
+export interface ErrorEventDetail<
+ E extends Error = Error,
+> extends BaseErrorEventDetail {
+ return await this.client.makeRequest(args.method, args.url, args.data) as P;
+ }
+
+ async submitForm {
+ return await this.client.uiHandler.submitForm(element) as P;
+ }
+
+ dispatch<
+ K extends string, M extends BaseAjaxEventMap = AjaxEventMap
+ >(type: K, detail: K extends keyof M ? EventDetail
', '\n');
- if (cell.attr('data-datagrid-editable-value')) {
- valueToEdit = String(cell.data('datagrid-editable-value'));
- } else {
- valueToEdit = cellValue;
- }
- cell.data('originalValue', cellValue);
- cell.data('valueToEdit', valueToEdit);
- if (cell.data('datagrid-editable-type') === 'textarea') {
- input = $('');
- cell_padding = parseInt(cell.css('padding').replace(/[^-\d\.]/g, ''), 10);
- cell_height = cell.outerHeight();
- line_height = Math.round(parseFloat(cell.css('line-height')));
- cell_lines = (cell_height - (2 * cell_padding)) / line_height;
- input.attr('rows', Math.round(cell_lines));
- } else if (cell.data('datagrid-editable-type') === 'select') {
- input = $(cell.data('datagrid-editable-element'));
- input.find("option[value='" + valueToEdit + "']").prop('selected', true);
- } else {
- input = $('');
- input.val(valueToEdit);
- }
- attrs = cell.data('datagrid-editable-attrs');
- for (attr_name in attrs) {
- attr_value = attrs[attr_name];
- input.attr(attr_name, attr_value);
- }
- cell.removeClass('edited');
- cell.html(input);
- submit = function(cell, el) {
- var value;
- value = el.val();
- if (value !== cell.data('valueToEdit')) {
- dataGridRegisterAjaxCall({
- url: cell.data('datagrid-editable-url'),
- data: {
- value: value
- },
- type: 'POST',
- success: function(payload) {
- if (cell.data('datagrid-editable-type') === 'select') {
- cell.html(input.find("option[value='" + value + "']").html());
- } else {
- if (payload._datagrid_editable_new_value) {
- value = payload._datagrid_editable_new_value;
- }
- cell.html(value);
- }
- return cell.addClass('edited');
- },
- error: function() {
- cell.html(cell.data('originalValue'));
- return cell.addClass('edited-error');
- }
- });
- } else {
- cell.html(cell.data('originalValue'));
- }
- return setTimeout(function() {
- return cell.removeClass('editing');
- }, 1200);
- };
- cell.find('input,textarea,select').focus().on('blur', function() {
- return submit(cell, $(this));
- }).on('keydown', function(e) {
- if (cell.data('datagrid-editable-type') !== 'textarea') {
- if (e.which === 13) {
- e.stopPropagation();
- e.preventDefault();
- return submit(cell, $(this));
- }
- }
- if (e.which === 27) {
- e.stopPropagation();
- e.preventDefault();
- cell.removeClass('editing');
- return cell.html(cell.data('originalValue'));
- }
- });
- return cell.find('select').on('change', function() {
- return submit(cell, $(this));
- });
- }
-});
-
-dataGridRegisterExtension('datagrid.after_inline_edit', {
- success: function(payload) {
- var grid = $('.datagrid-' + payload._datagrid_name);
-
- if (payload._datagrid_inline_edited) {
- grid.find('tr[data-id="' + payload._datagrid_inline_edited + '"] > td').addClass('edited');
- return grid.find('.datagrid-inline-edit-trigger').removeClass('hidden');
- } else if (payload._datagrid_inline_edit_cancel) {
- return grid.find('.datagrid-inline-edit-trigger').removeClass('hidden');
- }
- }
-});
-
-$(document).on('mouseup', '[data-datagrid-cancel-inline-add]', function(e) {
- var code = e.which || e.keyCode || 0;
- if (code === 1) {
- e.stopPropagation();
- e.preventDefault();
- return $('.datagrid-row-inline-add').addClass('datagrid-row-inline-add-hidden');
- }
-});
-
-dataGridRegisterExtension('datagrid-toggle-inline-add', {
- success: function(payload) {
- var grid = $('.datagrid-' + payload._datagrid_name);
-
- if (payload._datagrid_inline_adding) {
- var row = grid.find('.datagrid-row-inline-add');
-
- if (row.hasClass('datagrid-row-inline-add-hidden')) {
- row.removeClass('datagrid-row-inline-add-hidden');
- }
-
- row.find('input:not([readonly]),textarea:not([readonly])').first().focus();
- }
- }
-});
-
-datagridFilterMultiSelect = function() {
- var select = $('.selectpicker').first();
-
- if ($.fn.selectpicker) {
- let defaults = $.fn.selectpicker.defaults = {
- countSelectedText: select.data('i18n-selected'),
- iconBase: '',
- tickIcon: select.data('selected-icon-check')
- };
-
- $('.selectpicker')
- .removeClass('form-select form-select-sm')
- .addClass('form-control form-control-sm')
- .selectpicker('destroy')
- .selectpicker({
- iconBase: 'fa'
- });
-
- return defaults;
- }
-};
-
-$(function() {
- return datagridFilterMultiSelect();
-});
-
-datagridGroupActionMultiSelect = function() {
- var selects;
-
- if (!$.fn.selectpicker) {
- return;
- }
-
- selects = $('[data-datagrid-multiselect-id]');
-
- return selects.each(function() {
- var id;
- if ($(this).hasClass('selectpicker')) {
- $(this).removeAttr('id');
- id = $(this).data('datagrid-multiselect-id');
- $(this).on('loaded.bs.select', function(e) {
- $(this).parent().attr('style', 'display:none;');
- return $(this).parent().find('.hidden').removeClass('hidden').addClass('btn-default btn-secondary');
- });
- return $(this).on('rendered.bs.select', function(e) {
- return $(this).parent().attr('id', id);
- });
- }
- });
-};
-
-$(function() {
- return datagridGroupActionMultiSelect();
-});
-
-dataGridRegisterExtension('datagrid.fitlerMultiSelect', {
- success: function() {
- datagridFilterMultiSelect();
- }
-});
-
-dataGridRegisterExtension('datagrid.groupActionMultiSelect', {
- success: function() {
- return datagridGroupActionMultiSelect();
- }
-});
-
-dataGridRegisterExtension('datagrid.inline-editing', {
- success: function(payload) {
- var grid;
- if (payload._datagrid_inline_editing) {
- grid = $('.datagrid-' + payload._datagrid_name);
- return grid.find('.datagrid-inline-edit-trigger').addClass('hidden');
- }
- }
-});
-
-dataGridRegisterExtension('datagrid.redraw-item', {
- success: function(payload) {
- var row;
- if (payload._datagrid_redraw_item_class) {
- row = $('tr[data-id="' + payload._datagrid_redraw_item_id + '"]');
- return row.attr('class', payload._datagrid_redraw_item_class);
- }
- }
-});
-
-dataGridRegisterExtension('datagrid.reset-filter-by-column', {
- success: function(payload) {
- var grid, href, i, key, len, ref;
- if (!payload._datagrid_name) {
- return;
- }
- grid = $('.datagrid-' + payload._datagrid_name);
- grid.find('[data-datagrid-reset-filter-by-column]').addClass('hidden');
- if (payload.non_empty_filters && payload.non_empty_filters.length) {
- ref = payload.non_empty_filters;
- for (i = 0, len = ref.length; i < len; i++) {
- key = ref[i];
- grid.find('[data-datagrid-reset-filter-by-column="' + key + '"]').removeClass('hidden');
- }
- href = grid.find('.reset-filter').attr('href');
- return grid.find('[data-datagrid-reset-filter-by-column]').each(function() {
- var new_href;
- key = $(this).attr('data-datagrid-reset-filter-by-column');
- new_href = href.replace('do=' + payload._datagrid_name + '-resetFilter', 'do=' + payload._datagrid_name + '-resetColumnFilter');
- new_href += '&' + payload._datagrid_name + '-key=' + key;
- return $(this).attr('href', new_href);
- });
- }
- }
-});
diff --git a/assets/datagrid.ts b/assets/datagrid.ts
new file mode 100644
index 000000000..de36fcaa0
--- /dev/null
+++ b/assets/datagrid.ts
@@ -0,0 +1,145 @@
+import { defaultDatagridNameResolver, isEnter } from "./utils";
+import type { Ajax, DatagridEventMap, DatagridOptions, EventDetail, EventListener, } from "./types";
+
+export class Datagrid extends EventTarget {
+ private static readonly defaultOptions: DatagridOptions = {
+ confirm: confirm,
+ resolveDatagridName: defaultDatagridNameResolver,
+ plugins: [],
+ };
+
+ public readonly name: string;
+
+ public readonly ajax: Ajax;
+
+ private readonly options: DatagridOptions;
+
+ constructor(
+ public readonly el: HTMLElement,
+ ajax: Ajax | ((grid: Datagrid) => Ajax),
+ options: Partial