Bugzilla – Attachment 140235 Details for
Bug 30225
Rewrite cities admin view in Vue
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 30225: Rewrite cities admin view in Vue
Bug-30225-Rewrite-cities-admin-view-in-Vue.patch (text/plain), 33.15 KB, created by
Slava Shishkin
on 2022-09-06 16:33:17 UTC
(
hide
)
Description:
Bug 30225: Rewrite cities admin view in Vue
Filename:
MIME Type:
Creator:
Slava Shishkin
Created:
2022-09-06 16:33:17 UTC
Size:
33.15 KB
patch
obsolete
>From cfae8150e0832caa6c543d70471f94bd9f2293b5 Mon Sep 17 00:00:00 2001 >From: Jonathan Druart <jonathan.druart@bugs.koha-community.org> >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 032f6fe9cb..b39ca21313 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 @@ > <div class="row"> > <div class="col-sm-10 col-sm-push-2"> > <main> >- >-[% FOR m IN messages %] >- <div class="dialog [% m.type | html %]"> >- [% SWITCH m.code %] >- [% CASE 'error_on_update' %] >- <span>An error occurred when updating this city. Perhaps it already exists.</span> >- [% CASE 'error_on_insert' %] >- <span>An error occurred when adding this city. The city id might already exist.</span> >- [% CASE 'error_on_delete' %] >- <span>An error occurred when deleting this city. Check the logs.</span> >- [% CASE 'success_on_update' %] >- <span>City updated successfully.</span> >- [% CASE 'success_on_insert' %] >- <span>City added successfully.</span> >- [% CASE 'success_on_delete' %] >- <span>City deleted successfully.</span> >- [% CASE 'already_exists' %] >- <span>This city already exists.</span> >- [% CASE %] >- <span>[% m.code | html %]</span> >- [% END %] >- </div> >-[% END %] >- >-[% IF op == 'add_form' %] >- [% IF city %] >- <h1>Modify a city</h1> >- [% ELSE %] >- <h1>New city</h1> >- [% END %] >- >- <form action="/cgi-bin/koha/admin/cities.pl" name="Aform" method="post" class="validated"> >- <input type="hidden" name="op" value="add_validate" /> >- <input type="hidden" name="cityid" value="[% city.cityid | html %]" /> >- >- <fieldset class="rows"> >- <ol> >- [% IF city %] >- <li><span class="label">City ID: </span>[% city.cityid | html %]</li> >- [% END %] >- <li> >- <label for="city_name" class="required">City: </label> >- <input type="text" name="city_name" id="city_name" size="80" maxlength="100" value="[% city.city_name | html %]" required="required" class="required" /> <span class="required">Required</span> >- </li> >- <li> >- <label for="city_state">State: </label> >- <input type="text" name="city_state" id="city_state" size="80" maxlength="100" value="[% city.city_state | html %]" /> >- </li> >- <li> >- <label for="city_zipcode" class="required">ZIP/Postal code: </label> >- <input type="text" name="city_zipcode" id="city_zipcode" size="20" maxlength="20" value="[% city.city_zipcode | html %]" required="required" class="required" /> <span class="required">Required</span> >- </li> >- <li> >- <label for="city_country">Country: </label> >- <input type="text" name="city_country" id="city_country" size="80" maxlength="100" value="[% city.city_country | html %]" /> >- </li> >- </ol> >- </fieldset> >- >- <fieldset class="action"> >- <input type="submit" value="Submit" /> >- <a class="cancel" href="/cgi-bin/koha/admin/cities.pl">Cancel</a> >- </fieldset> >- </form> >-[% END %] >- >-[% IF op == 'delete_confirm' %] >- <div class="dialog alert"> >- <h1>Delete city "[% city.city_name | html %]?"</h1> >- <table> >- <tr><th>City id</th> >- <td>[% city.cityid | html %]</td> >- </tr> >- <tr><th>City</th> >- <td>[% city.city_name | html %]</td> >- </tr> >- <tr><th>State</th> >- <td>[% city.city_state | html %]</td> >- </tr> >- <tr><th>ZIP/Postal code</th> >- <td>[% city.city_zipcode | html %]</td> >- </tr> >- <tr><th>Country</th> >- <td>[% city.city_country | html %]</td> >- </tr> >- </table> >- <form action="/cgi-bin/koha/admin/cities.pl" method="post"> >- <input type="hidden" name="op" value="delete_confirmed" /> >- <input type="hidden" name="cityid" value="[% city.cityid | html %]" /> >- <button type="submit" class="approve"><i class="fa fa-fw fa-check"></i> Yes, delete</button> >- </form> >- <form action="/cgi-bin/koha/admin/cities.pl" method="get"> >- <button type="submit" class="deny"><i class="fa fa-fw fa-remove"></i> No, do not delete</button> >- </form> >- </div> >-[% END %] >- >-[% IF op == 'list' %] >- >- <div id="toolbar" class="btn-toolbar"> >- <a class="btn btn-default" id="newcity" href="/cgi-bin/koha/admin/cities.pl?op=add_form"><i class="fa fa-plus"></i> New city</a> >- </div> >- >- <h1>Cities</h1> >- [% IF city_name_filter %] >- Searching: [% city_name_filter | html %] >- [% END %] >- >- [% IF cities_count > 0 %] >- <div class="table_cities_table_controls"></div> >- <table id="table_cities"> >- <thead> >- <tr> >- <th>City ID</th> >- <th>City</th> >- <th>State</th> >- <th>ZIP/Postal code</th> >- <th>Country</th> >- <th data-class-name="actions noExport">Actions</th> >- </tr> >- </thead> >- </table> >- [% ELSE %] >- <div class="dialog message"> >- There are no cities defined. <a href="/cgi-bin/koha/admin/cities.pl?op=add_form">Create a new city</a>. >- </div> >- [% END %] >-[% END %] >- >+ <div id="cities"></div> > </main> > </div> <!-- /.col-sm-10.col-sm-push-2 --> > >@@ -213,9 +85,7 @@ > [% INCLUDE 'datatables.inc' %] > [% INCLUDE 'columns_settings.inc' %] > <script> >- > var table_settings = [% TablesSettings.GetTableSettings( 'admin', 'cities', 'table_cities', 'json' ) | $raw %]; >- $(document).ready(function() { > var cities_table_url = '/api/v1/cities?'; > > [% IF city_name_filter %] >@@ -226,62 +96,9 @@ > }; > cities_table_url += 'q='+ encodeURIComponent(JSON.stringify(city_name_filter)); > [% END %] >- >- var cities_table = $("#table_cities").kohaTable({ >- "ajax": { >- "url": cities_table_url >- }, >- "order": [[ 1, "asc" ]], >- "columnDefs": [ { >- "targets": [0,1,2,3,4], >- "render": function (data, type, row, meta) { >- if ( type == 'display' ) { >- return data.escapeHtml(); >- } >- return data; >- } >- } ], >- "columns": [ >- { >- "data": "city_id", >- "searchable": true, >- "orderable": true >- }, >- { >- "data": "name", >- "searchable": true, >- "orderable": true >- }, >- { >- "data": "state", >- "searchable": true, >- "orderable": true >- }, >- { >- "data": "postal_code", >- "searchable": true, >- "orderable": true >- }, >- { >- "data": "country", >- "searchable": true, >- "orderable": true >- }, >- { >- "data": function( row, type, val, meta ) { >- >- var result = '<a class="btn btn-default btn-xs" role="button" href="/cgi-bin/koha/admin/cities.pl?op=add_form&cityid='+ encodeURIComponent(row.city_id) +'"><i class="fa fa-pencil" aria-hidden="true"></i> '+_("Edit")+'</a>'+"\n"; >- result += '<a class="btn btn-default btn-xs" role="button" href="/cgi-bin/koha/admin/cities.pl?op=delete_confirm&cityid='+ encodeURIComponent(row.city_id) +'"><i class="fa fa-trash" aria-hidden="true"></i> '+_("Delete")+'</a>'; >- return result; >- >- }, >- "searchable": false, >- "orderable": false >- } >- ] >- }, table_settings, 1); >- >- }); > </script> >+ [% 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 f4247ae343..b0a643c519 100644 >--- a/koha-tmpl/intranet-tmpl/prog/js/datatables.js >+++ b/koha-tmpl/intranet-tmpl/prog/js/datatables.js >@@ -511,10 +511,6 @@ jQuery.fn.dataTable.ext.errMode = function(settings, note, message) { > $.fn.kohaTable = function(options, table_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(); >@@ -846,8 +842,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 ) { > let input_type = 'input'; > if ( $(this).data('filter') ) { >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 @@ >+<template> >+ <Toolbar v-if="op=='list'" @switch-view="switchView"/> >+ <b-alert v-if="message" variant="success" show>{{message}}</b-alert> >+ <b-alert v-if="error" variant="warning" show>{{error}}</b-alert> >+ <List v-if="op=='list'" @set-current-city-id="setCurrentCityID" @switch-view="switchView" @set-error="setError"/> >+ <AddForm v-if="op=='add-form'" :city_id=city_id @city-created="cityCreated" @city-updated="cityUpdated" @set-error="setError" @switch-view="switchView"/> >+ <ConfirmDeleteForm v-if="op=='confirm-delete-form'" :city_id=city_id @city-deleted="cityDeleted" @switch-view="switchView"/> >+</template> >+ >+<script> >+import Toolbar from "./components/CitiesToolbar.vue"; >+import List from "./components/CitiesList.vue"; >+import AddForm from "./components/CitiesFormAdd.vue"; >+import ConfirmDeleteForm from "./components/CitiesFormConfirmDelete.vue"; >+ >+import {reactive, computed} from "vue"; >+ >+export default { >+ data() { >+ return {city_id: null, op: 'list', message: null, error: null} >+ }, >+ methods: { >+ switchView(view) { >+ this.message = null >+ this.error = null >+ this.op = view >+ if (view == 'list' ) >+ this.city_id = null; >+ }, >+ cityCreated() { >+ this.message = "City created" >+ this.error = null >+ this.city_id = null >+ this.op = 'list' >+ }, >+ cityUpdated() { >+ this.message = "City updated" >+ this.error = null >+ this.city_id = null >+ this.op = 'list' >+ }, >+ cityDeleted() { >+ this.message = "City deleted" >+ this.error = null >+ this.city_id = null >+ this.op = 'list' >+ }, >+ setCurrentCityID(city_id){ >+ this.city_id = city_id >+ }, >+ setError(error){ >+ this.message = null >+ this.error = "Something went wrong: " + error >+ }, >+ }, >+ components: { >+ Toolbar, >+ List, >+ AddForm, >+ ConfirmDeleteForm, >+ }, >+ emits:['set-error'], >+}; >+</script> >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 @@ >+<template> >+ <button variant="default" size="sm">Delete</button> >+</template> >+ >+<script> >+export default { >+ name: "CitiesButtonDelete" >+} >+</script> >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 @@ >+<template> >+ <button variant="default" size="sm">Edit</button> >+</template> >+ >+<script> >+export default { >+ name: "CitiesButtonEdit", >+} >+</script> >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 @@ >+<template> >+ <h2 v-if="city.city_id">Edit city</h2> >+ <h2 v-else>New city</h2> >+ <div> >+ <b-form @submit="onSubmit"> >+ <b-form-group >+ id="city_name" >+ label="City name:" >+ label-for="city_name" >+ label-cols="4" >+ label-cols-lg="2" >+ > >+ <b-form-input >+ id="city_name" >+ v-model="city.name" >+ placeholder="City name" >+ required >+ ></b-form-input> >+ </b-form-group> >+ <b-form-group >+ id="city_state" >+ label="State:" >+ label-for="city_state" >+ label-cols="4" label-cols-lg="2" >+ > >+ <b-form-input >+ id="city_state" >+ v-model="city.state" >+ placeholder="City state" >+ ></b-form-input> >+ </b-form-group> >+ <b-form-group >+ id="city_postal_code" >+ label="ZIP/Postal code" >+ label-for="postal_code" >+ label-cols="4" label-cols-lg="2" >+ > >+ <b-form-input >+ id="postal_code" >+ v-model="city.postal_code" >+ placeholder="City postal code" >+ required >+ ></b-form-input> >+ </b-form-group> >+ <b-form-group >+ id="city_country" >+ label="Country:" >+ label-for="city_country" >+ label-cols="4" label-cols-lg="2" >+ > >+ <b-form-input >+ id="city_country" >+ v-model="city.country" >+ placeholder="City country" >+ ></b-form-input> >+ </b-form-group> >+ <b-button type="submit" variant="primary">Submit</b-button> >+ <a href="#" @click="$emit('switch-view', 'list')">Cancel</a> >+ </b-form> >+ </div> >+</template> >+ >+<script> >+ >+ >+export default { >+ data() { >+ return { >+ city: { >+ city_id: null, >+ name: '', >+ state: '', >+ postal_code: '', >+ country: '', >+ } >+ } >+ }, >+ created() { >+ if( this.city_id ) { >+ >+ const apiUrl = '/api/v1/cities/' + this.city_id; >+ >+ fetch(apiUrl) >+ .then(res => res.json()) >+ .then( >+ (result) => { >+ this.city = result >+ }, >+ (error) => { >+ this.$emit('set-error', error) >+ } >+ ) >+ } >+ }, >+ methods:{ >+ onSubmit(){ >+ >+ let city = this.city; >+ let apiUrl = '/api/v1/cities' >+ >+ const myHeaders = new Headers() >+ myHeaders.append('Content-Type', 'application/json') >+ >+ let method = 'POST' >+ if ( city.city_id ) { >+ method = 'PUT' >+ apiUrl += '/' + city.city_id >+ } >+ delete city.city_id >+ >+ const options = { >+ method: method, >+ body: JSON.stringify(city), >+ myHeaders >+ } >+ >+ fetch(apiUrl, options) >+ .then(response => { >+ if ( response.status == 200 ) { >+ this.$emit('city-updated') >+ } else if ( response.status == 201 ) { >+ this.$emit('city-created') >+ } else { >+ this.$emit('set-error', response.message || response.statusText) >+ } >+ }, (error) => { >+ this.$emit('set-error', error) >+ }) >+ } >+ }, >+ emits: ['city-created', 'city-updated', 'set-error', 'switch-view'], >+ props: { >+ city_id: Number >+ }, >+ name: "CitiesFormAdd.vue", >+} >+</script> >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 @@ >+<template> >+ <h2> Delete city</h2> >+ <div> >+ <b-form @submit="onSubmit"> >+ <b-form-group >+ id="city_id" >+ label="City ID:" >+ label-for="city_id" >+ label-cols="4" >+ label-cols-lg="2" >+ > >+ {{city.city_id}} >+ </b-form-group> >+ <b-form-group >+ id="city_name" >+ label="City name:" >+ label-for="city_name" >+ label-cols="4" >+ label-cols-lg="2" >+ > >+ {{city.name}} >+ </b-form-group> >+ <b-form-group >+ id="city_state" >+ label="State:" >+ label-for="city_state" >+ label-cols="4" label-cols-lg="2" >+ > >+ {{city.state}} >+ </b-form-group> >+ <b-form-group >+ id="city_postal_code" >+ label="ZIP/Postal code" >+ label-for="postal_code" >+ label-cols="4" label-cols-lg="2" >+ > >+ {{city.postal_code}} >+ </b-form-group> >+ <b-form-group >+ id="city_country" >+ label="Country:" >+ label-for="city_country" >+ label-cols="4" label-cols-lg="2" >+ > >+ {{city.country}} >+ </b-form-group> >+ <b-button type="submit" variant="primary">Submit</b-button> >+ <a href="#" @click="$emit('switch-view', 'list')">Cancel</a> >+ </b-form> >+ </div> >+</template> >+ >+<script> >+ >+export default { >+ data() { >+ return { >+ city: {}, >+ } >+ }, >+ created() { >+ console.log("created" + this.city_id) >+ const apiUrl = '/api/v1/cities/' + this.city_id; >+ >+ fetch(apiUrl) >+ .then(res => res.json()) >+ .then( >+ (result) => { >+ this.city = result >+ }, >+ (error) => { >+ this.$emit('set-error', error) >+ } >+ ) >+ }, >+ methods:{ >+ onSubmit(){ >+ >+ let apiUrl = '/api/v1/cities/' + this.city_id >+ >+ const myHeaders = new Headers() >+ myHeaders.append('Content-Type', 'application/json') >+ >+ const options = { >+ method: 'DELETE', >+ myHeaders >+ } >+ >+ fetch(apiUrl, options) >+ .then(response => { >+ if ( response.status == 204 ) { >+ this.$emit('city-deleted') >+ } else { >+ this.$emit('set-error', response.message || response.statusText) >+ } >+ }, (error) => { >+ this.$emit('set-error', error) >+ }) >+ } >+ }, >+ emits: ['city-deleted', 'set-error', 'switch-view'], >+ props: { >+ city_id: Number >+ }, >+ name: "CitiesFormConfirmDelete.vue", >+} >+</script> >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..94ce4271f4 >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/js/vue/components/CitiesList.vue >@@ -0,0 +1,129 @@ >+<template> >+ <div> >+ <table v-if="cities.length" id="my_table"></table> >+ <div v-else-if="this.initialized" class="dialog message">There are no cities defined.</div> >+ <div v-else>Loading...</div> >+ </div> >+</template> >+ >+<script> >+import CitiesButtonEdit from "./CitiesButtonEdit.vue"; >+import CitiesButtonDelete from "./CitiesButtonDelete.vue"; >+import {createVNode, defineComponent, render, resolveComponent} from 'vue' >+export default { >+ name: "CitiesList.vue", >+ created() { >+ const apiUrl = '/api/v1/cities'; >+ >+ fetch(apiUrl) >+ .then(res => res.json()) >+ .then( >+ (result) => { >+ this.cities = result >+ this.initialized = true >+ }, >+ (error) => { >+ this.$emit('set-error', error) >+ } >+ ) >+ }, >+ updated() { >+ let edit_city = this.edit_city; >+ let delete_city = this.delete_city; >+ $('#my_table').kohaTable({ >+ "ajax": { >+ "url": cities_table_url, >+ }, >+ "order": [[1, "asc"]], >+ "columnDefs": [{ >+ "targets": [0, 1, 2, 3, 4], >+ "render": function (data, type, row, meta) { >+ if (type == 'display') { >+ return escape_str(data); >+ } >+ return data; >+ } >+ }], >+ "columns": [ >+ { >+ "title": __("City ID"), >+ "data": "city_id", >+ "searchable": true, >+ "orderable": true >+ }, >+ { >+ "title": __("City"), >+ "data": "name", >+ "searchable": true, >+ "orderable": true >+ }, >+ { >+ "title": __("State"), >+ "data": "state", >+ "searchable": true, >+ "orderable": true >+ }, >+ { >+ "title": __("ZIP/Postal code"), >+ "data": "postal_code", >+ "searchable": true, >+ "orderable": true >+ }, >+ { >+ "title": __("Country"), >+ "data": "country", >+ "searchable": true, >+ "orderable": true >+ }, >+ { >+ "title": __("Actions"), >+ "data": function (row, type, val, meta) { >+ return '<div class="actions" data-city_id="' + row.city_id + '"></div>'; >+ }, >+ "searchable": false, >+ "orderable": false >+ } >+ ], >+ drawCallback: function (settings) { >+ $.each($(this).find(".actions"), function (index, e) { >+ let city_id = $(e).data('city_id') >+ let editButton = createVNode(CitiesButtonEdit, { >+ onClick: () => { >+ edit_city(city_id) >+ } >+ }) >+ let deleteButton = createVNode(CitiesButtonDelete, { >+ onClick: () => { >+ delete_city(city_id) >+ } >+ }) >+ let n = createVNode('span', {}, [editButton, deleteButton]); >+ render(n, e); >+ }); >+ } >+ >+ }, table_settings, 1); >+ }, >+ beforeUnmount() { >+ $('#my_table') >+ .DataTable() >+ .destroy(true); >+ }, >+ data: function () { >+ return { >+ cities: [], >+ initialized: false, >+ } >+ }, >+ methods: { >+ edit_city: function (city_id) { >+ this.$emit('set-current-city-id', city_id) >+ this.$emit('switch-view', 'add-form') >+ }, >+ delete_city: function (city_id) { >+ this.$emit('set-current-city-id', city_id) >+ this.$emit('switch-view', 'confirm-delete-form') >+ }, >+ } >+} >+</script> >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 @@ >+<template> >+ <b-button @click="$emit('switch-view', 'add-form')" id="newcity" variant="default"><font-awesome-icon icon="plus" /> New city</b-button> >+</template> >+ >+<script> >+export default { >+ name: "CitiesToolbar.vue", >+ emits: ['switch-view'], >+} >+</script> >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
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 30225
:
131371
|
131496
|
131523
|
132643
| 140235