From 9400e60dd759a939153b282840668d2e742ece14 Mon Sep 17 00:00:00 2001 From: Jonathan Druart Date: Fri, 4 Mar 2022 11:17:18 +0100 Subject: [PATCH] Bug 30225: Rewrite cities admin view in Vue Same as bug 30160 but using Vue 3 (https://vuejs.org/). TODO: * Use Vue Router and remove the switch-view weirdness * Clean package.json it contains too many things Notes: * Use of webpack instead of gulp (didn't manage to make it work, and webpack seems the "new standard"?) * Cannot fully use vue-boostrap-3 as it is using bootstrap 4 and conflict with our version (3.5). The add form for instance should display label and input inline with the code used in this patch, but it's not displayed correctly * Using bootstrap-vue-3 as bootstrap-vue is still for Vue 2 To improve/weirdness: * The fetch before the edit and delete is done too late, in created(). You can notice a "blink". But lifecycle hooks don't permit a await here https://vuejs.org/guide/essentials/lifecycle.html * I guess we should have the whole model stored in Vue to take full advantage of its functionalities * We need to inject the "Edit" and "Delete" buttons, it was a nightmare and it's not done properly. I didn't manage to render a component that is using the font-awesome-icon and bootstrap styles. Hot reload using `yarn watch_js` Bug 30225: Fix edit>cancel>list>add New form shouldn't be populated with last edited city. Appened when cancel link was used --- .../prog/en/modules/admin/cities.tt | 191 +----------------- koha-tmpl/intranet-tmpl/prog/js/datatables.js | 8 +- .../intranet-tmpl/prog/js/vue/Cities.vue | 64 ++++++ .../js/vue/components/CitiesButtonDelete.vue | 9 + .../js/vue/components/CitiesButtonEdit.vue | 9 + .../prog/js/vue/components/CitiesFormAdd.vue | 137 +++++++++++++ .../components/CitiesFormConfirmDelete.vue | 107 ++++++++++ .../prog/js/vue/components/CitiesList.vue | 129 ++++++++++++ .../prog/js/vue/components/CitiesToolbar.vue | 10 + .../intranet-tmpl/prog/js/vue/main-cities.ts | 18 ++ .../intranet-tmpl/prog/js/vue/vue-shims.d.ts | 4 + package.json | 38 +++- tsconfig.json | 4 + webpack.config.js | 36 ++++ 14 files changed, 570 insertions(+), 194 deletions(-) create mode 100644 koha-tmpl/intranet-tmpl/prog/js/vue/Cities.vue create mode 100644 koha-tmpl/intranet-tmpl/prog/js/vue/components/CitiesButtonDelete.vue create mode 100644 koha-tmpl/intranet-tmpl/prog/js/vue/components/CitiesButtonEdit.vue create mode 100644 koha-tmpl/intranet-tmpl/prog/js/vue/components/CitiesFormAdd.vue create mode 100644 koha-tmpl/intranet-tmpl/prog/js/vue/components/CitiesFormConfirmDelete.vue create mode 100644 koha-tmpl/intranet-tmpl/prog/js/vue/components/CitiesList.vue create mode 100644 koha-tmpl/intranet-tmpl/prog/js/vue/components/CitiesToolbar.vue create mode 100644 koha-tmpl/intranet-tmpl/prog/js/vue/main-cities.ts create mode 100644 koha-tmpl/intranet-tmpl/prog/js/vue/vue-shims.d.ts create mode 100644 tsconfig.json create mode 100644 webpack.config.js diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/cities.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/cities.tt index 2138aac124..22fe65321b 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/cities.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/cities.tt @@ -69,135 +69,7 @@
- -[% FOR m IN messages %] -
- [% SWITCH m.code %] - [% CASE 'error_on_update' %] - An error occurred when updating this city. Perhaps it already exists. - [% CASE 'error_on_insert' %] - An error occurred when adding this city. The city id might already exist. - [% CASE 'error_on_delete' %] - An error occurred when deleting this city. Check the logs. - [% CASE 'success_on_update' %] - City updated successfully. - [% CASE 'success_on_insert' %] - City added successfully. - [% CASE 'success_on_delete' %] - City deleted successfully. - [% CASE 'already_exists' %] - This city already exists. - [% CASE %] - [% m.code | html %] - [% END %] -
-[% END %] - -[% IF op == 'add_form' %] - [% IF city %] -

Modify a city

- [% ELSE %] -

New city

- [% END %] - -
- - - -
-
    - [% IF city %] -
  1. City ID: [% city.cityid | html %]
  2. - [% END %] -
  3. - - Required -
  4. -
  5. - - -
  6. -
  7. - - Required -
  8. -
  9. - - -
  10. -
-
- -
- - Cancel -
-
-[% END %] - -[% IF op == 'delete_confirm' %] -
-

Delete city "[% city.city_name | html %]?"

- - - - - - - - - - - - - - - - -
City id[% city.cityid | html %]
City[% city.city_name | html %]
State[% city.city_state | html %]
ZIP/Postal code[% city.city_zipcode | html %]
Country[% city.city_country | html %]
-
- - - -
-
- -
-
-[% END %] - -[% IF op == 'list' %] - - - -

Cities

- [% IF city_name_filter %] - Searching: [% city_name_filter | html %] - [% END %] - - [% IF cities_count > 0 %] -
- - - - - - - - - - - -
City IDCityStateZIP/Postal codeCountryActions
- [% ELSE %] -
- There are no cities defined. Create a new city. -
- [% END %] -[% END %] - +
@@ -213,9 +85,7 @@ [% INCLUDE 'datatables.inc' %] [% INCLUDE 'columns_settings.inc' %] + [% Asset.js("js/vue/dist/main.js") %] + [% Asset.js("js/vue/dist/runtime.js") %] + [% END %] [% INCLUDE 'intranet-bottom.inc' %] diff --git a/koha-tmpl/intranet-tmpl/prog/js/datatables.js b/koha-tmpl/intranet-tmpl/prog/js/datatables.js index 2d88fcdd05..882ed91104 100644 --- a/koha-tmpl/intranet-tmpl/prog/js/datatables.js +++ b/koha-tmpl/intranet-tmpl/prog/js/datatables.js @@ -505,10 +505,6 @@ jQuery.fn.dataTable.ext.errMode = function(settings, note, message) { $.fn.kohaTable = function(options, columns_settings, add_filters, default_filters) { var settings = null; - if ( add_filters ) { - $(this).find('thead tr').clone(true).appendTo( $(this).find('thead') ); - } - if(options) { if(!options.criteria || ['contains', 'starts_with', 'ends_with', 'exact'].indexOf(options.criteria.toLowerCase()) === -1) options.criteria = 'contains'; options.criteria = options.criteria.toLowerCase(); @@ -772,8 +768,12 @@ jQuery.fn.dataTable.ext.errMode = function(settings, note, message) { if ( add_filters ) { var table_dt = table.DataTable(); + + $(this).find('thead tr').clone().appendTo( $(this).find('thead') ); + $(this).find('thead tr:eq(1) th').each( function (i) { var is_searchable = table_dt.settings()[0].aoColumns[i].bSearchable; + $(this).removeClass('sorting'); if ( is_searchable ) { var title = $(this).text(); var existing_search = table_dt.column(i).search(); diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/Cities.vue b/koha-tmpl/intranet-tmpl/prog/js/vue/Cities.vue new file mode 100644 index 0000000000..481c9026f9 --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/js/vue/Cities.vue @@ -0,0 +1,64 @@ + + + diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/components/CitiesButtonDelete.vue b/koha-tmpl/intranet-tmpl/prog/js/vue/components/CitiesButtonDelete.vue new file mode 100644 index 0000000000..c725672162 --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/js/vue/components/CitiesButtonDelete.vue @@ -0,0 +1,9 @@ + + + diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/components/CitiesButtonEdit.vue b/koha-tmpl/intranet-tmpl/prog/js/vue/components/CitiesButtonEdit.vue new file mode 100644 index 0000000000..d47e0039f3 --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/js/vue/components/CitiesButtonEdit.vue @@ -0,0 +1,9 @@ + + + diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/components/CitiesFormAdd.vue b/koha-tmpl/intranet-tmpl/prog/js/vue/components/CitiesFormAdd.vue new file mode 100644 index 0000000000..af6a2b2c18 --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/js/vue/components/CitiesFormAdd.vue @@ -0,0 +1,137 @@ + + + diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/components/CitiesFormConfirmDelete.vue b/koha-tmpl/intranet-tmpl/prog/js/vue/components/CitiesFormConfirmDelete.vue new file mode 100644 index 0000000000..4a708c03fb --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/js/vue/components/CitiesFormConfirmDelete.vue @@ -0,0 +1,107 @@ + + + diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/components/CitiesList.vue b/koha-tmpl/intranet-tmpl/prog/js/vue/components/CitiesList.vue new file mode 100644 index 0000000000..1426bb55b6 --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/js/vue/components/CitiesList.vue @@ -0,0 +1,129 @@ + + + diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/components/CitiesToolbar.vue b/koha-tmpl/intranet-tmpl/prog/js/vue/components/CitiesToolbar.vue new file mode 100644 index 0000000000..ea7cf762a3 --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/js/vue/components/CitiesToolbar.vue @@ -0,0 +1,10 @@ + + + diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/main-cities.ts b/koha-tmpl/intranet-tmpl/prog/js/vue/main-cities.ts new file mode 100644 index 0000000000..09c3f32476 --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/js/vue/main-cities.ts @@ -0,0 +1,18 @@ +import {createApp} from 'vue' +import BootstrapVue3 from 'bootstrap-vue-3' + +//import 'bootstrap/dist/css/bootstrap.css' +import 'bootstrap-vue-3/dist/bootstrap-vue-3.css' + +import {library} from "@fortawesome/fontawesome-svg-core"; +import {faPlus, faPencil, faTrash} from "@fortawesome/free-solid-svg-icons"; +import {FontAwesomeIcon} from "@fortawesome/vue-fontawesome"; + +library.add(faPlus, faPencil, faTrash) + +import App from './Cities.vue' + +createApp(App) + .component("font-awesome-icon", FontAwesomeIcon) + .use(BootstrapVue3) + .mount('#cities') diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/vue-shims.d.ts b/koha-tmpl/intranet-tmpl/prog/js/vue/vue-shims.d.ts new file mode 100644 index 0000000000..d6e6b864e5 --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/js/vue/vue-shims.d.ts @@ -0,0 +1,4 @@ +declare module "*.vue" { + import Vue from "vue"; + export default Vue; +} diff --git a/package.json b/package.json index ac7ff7bcd7..61cbfef4ac 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,15 @@ "test": "test" }, "dependencies": { - "bootstrap": "^4.5.2", + "@fortawesome/fontawesome-svg-core": "^6.1.0", + "@fortawesome/free-solid-svg-icons": "^6.0.0", + "@fortawesome/vue-fontawesome": "^3.0.0-5", + "@popperjs/core": "^2.11.2", + "babel-core": "^7.0.0-beta.3", + "bootstrap": "^5.1.3", + "bootstrap-vue": "^2.21.2", + "bootstrap-vue-3": "^0.1.9", + "css-loader": "^6.6.0", "gulp": "^4.0.2", "gulp-autoprefixer": "^4.0.0", "gulp-concat-po": "^1.0.0", @@ -20,10 +28,15 @@ "js-yaml": "^3.13.1", "lodash": "^4.17.12", "merge-stream": "^2.0.0", - "minimist": "^1.2.5" + "minimist": "^1.2.5", + "style-loader": "^3.3.1", + "vue": "^3.2.31", + "vue-router": "^3.4.3" }, "scripts": { "build": "node_modules/.bin/gulp build", + "build_js": "webpack --mode production", + "watch_js": "webpack --mode development --watch", "css": "node_modules/.bin/gulp css", "watch": "node_modules/.bin/gulp watch" }, @@ -37,5 +50,24 @@ "js-yaml": "^3.13.1" }, "author": "", - "license": "GPL-3.0" + "license": "GPL-3.0", + "devDependencies": { + "@babel/core": "^7.17.5", + "@babel/preset-env": "^7.16.11", + "@vue/compiler-sfc": "^3.2.31", + "babel-loader": "^8.2.3", + "babelify": "^10.0.0", + "browserify": "^17.0.0", + "clean-webpack-plugin": "^4.0.0", + "gulp-tap": "^1.0.1", + "html-webpack-plugin": "^5.5.0", + "ts-loader": "^9.2.7", + "typescript": "^4.6.2", + "vinyl-source-stream": "^2.0.0", + "vue-loader": "^17.0.0", + "watchify": "^4.0.0", + "webpack": "^5.69.1", + "webpack-cli": "^4.9.2", + "webpack-dev-server": "^4.7.4" + } } diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000000..88ccffe748 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,4 @@ +{ + "compilerOptions": { + } +} diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000000..00350b9011 --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,36 @@ +const { VueLoaderPlugin } = require("vue-loader"); +const autoprefixer = require("autoprefixer"); +const path = require("path"); + +module.exports = { + entry: { + main: "./koha-tmpl/intranet-tmpl/prog/js/vue/main-cities.ts", + }, + output: { + filename: "[name].js", + path: path.resolve(__dirname, "koha-tmpl/intranet-tmpl/prog/js/vue/dist/"), + chunkFilename: "[name].js", + }, + module: { + rules: [ + { + test: /\.vue$/, + loader: "vue-loader", + }, + { + test: /\.ts$/, + loader: 'ts-loader', + options: { + appendTsSuffixTo: [/\.vue$/] + } + }, + { + test: /\.css$/, + use: ['style-loader', 'css-loader'] + } + ], + }, + plugins: [ + new VueLoaderPlugin(), + ], +}; -- 2.35.1