diff --git a/package.json b/package.json
index edd7bf9..fb2aaac 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "fohn-ui",
- "version": "1.4.0",
+ "version": "1.5.0",
"description": "Javascript library for Fohn-Ui php framework.",
"main": "dist/fohn-ui.min.js",
"files": [
diff --git a/src/components/components-install.js b/src/components/components-install.js
index 503b3e3..d8b0339 100644
--- a/src/components/components-install.js
+++ b/src/components/components-install.js
@@ -13,6 +13,7 @@ import TableRow from './table/row.component.vue';
import TableCell from './table/cell.component.vue';
import ExceptionModal from './modal/exception-modal.component.vue';
import TablePaginator from './table/paginator.component.vue';
+import TableAction from "./table/table.action.component.vue";
import Dummy from './dummy.component.vue';
import Modal from './modal/modal.component.vue';
import Tabs from './tabs/tabs.component.vue';
@@ -33,6 +34,7 @@ const fohnComponents = [
{name: 'fohn-table-row', def: TableRow},
{name: 'fohn-table-cell', def: TableCell},
{name: 'fohn-table-paginator', def: TablePaginator},
+ {name: 'fohn-table-action', def: TableAction},
{name: 'fohn-modal', def: Modal},
{name: 'fohn-ui-exception', def: ExceptionModal},
{name: 'fohn-tab', def: Tab},
diff --git a/src/components/form/form.store.js b/src/components/form/form.store.js
index 03e61a1..6afe145 100644
--- a/src/components/form/form.store.js
+++ b/src/components/form/form.store.js
@@ -24,7 +24,7 @@ export const useFormStoreFactory = (id) => {
this.fetchControlValues();
}
},
- clearControlValues() {
+ clearControlsValue() {
for (const value of this.controls.values()) {
value.value = '';
}
diff --git a/src/components/modal/modal.component.vue b/src/components/modal/modal.component.vue
index 8ddee02..5a24882 100644
--- a/src/components/modal/modal.component.vue
+++ b/src/components/modal/modal.component.vue
@@ -1,5 +1,5 @@
+
+ row
+
+
diff --git a/src/components/table/table.action.component.vue b/src/components/table/table.action.component.vue
new file mode 100644
index 0000000..fe37c65
--- /dev/null
+++ b/src/components/table/table.action.component.vue
@@ -0,0 +1,61 @@
+
+
+
+ table action
+
diff --git a/src/components/table/table.component.vue b/src/components/table/table.component.vue
index 4805eac..45cbfeb 100644
--- a/src/components/table/table.component.vue
+++ b/src/components/table/table.component.vue
@@ -3,14 +3,14 @@
* Todo serve two different mode. Load all items and use fuse search internally or
* use as it is now, loading items per page load.
*/
-import {onMounted, ref} from 'vue';
+import {onMounted, ref, provide, computed} from 'vue';
import debounce from 'lodash.debounce';
import { useTableStoreFactory } from './table.store';
export default {
name: 'fohn-table',
props: {
- actions: {
+ rowActions: {
type: Object,
},
searchDebounceValue: {
@@ -20,16 +20,31 @@ export default {
columns: {
type: Array,
},
+ hasSelectableRows: {
+ type: Boolean,
+ default: false,
+ },
storeId: String,
dataUrl: String,
itemsPerPage: Number,
keepTableState: {
type: Boolean,
default: true,
+ },
+ keepSelectionAcrossPage: {
+ type: Boolean,
+ default: false,
}
},
setup(props, { attrs, slots, emit }) {
- const { columns, dataUrl, searchDebounceValue, storeId, keepTableState } = props;
+ const { columns,
+ dataUrl,
+ searchDebounceValue,
+ storeId,
+ keepTableState,
+ hasSelectableRows,
+ keepSelectionAcrossPage } = props;
+
const rows = ref([]);
const isFetching = ref(false);
const currentPage = ref(1);
@@ -37,12 +52,17 @@ export default {
const sortDirection = ref('');
const itemsPerPage = ref(props.itemsPerPage);
const totalItems = ref(0);
+ const selectedRows = ref(new Set());
const query = ref('');
+
// each table get its own tableStore.
const tableStore = useTableStoreFactory(storeId)();
const debounceSearch = debounce((query) => {
tableStore.searchItems(query);
+ if (!keepSelectionAcrossPage) {
+ clearSelectedRows();
+ }
}, searchDebounceValue);
tableStore.setDataUrl(dataUrl);
@@ -63,9 +83,36 @@ export default {
sortDirection.value = state.tableState.sort.direction;
itemsPerPage.value = state.tableState.itemsPerPage;
query.value = state.tableState.currentQuery;
+ selectedRows.value = new Set(state.selectedRows) ;
+ });
+
+ const hasAllRowSelected = computed(() => {
+ return (selectedRows.value.size === 0) ? false : rows.value.every((row) => selectedRows.value.has(row.id));
+ });
+
+ const hasSomeRowSelected = computed( () => {
+ return rows.value.reduce((acc, row) => {
+ if (selectedRows.value.has(row.id)) {
+ acc.push(row.id);
+ }
+ return acc;
+ }, []).length > 0;
+ });
+
+ const selectedRowSize = computed(() => selectedRows.value.size);
+
+ const pageSelectState = computed(() => {
+ return {
+ all : hasAllRowSelected.value,
+ partial: hasSomeRowSelected.value && !hasAllRowSelected.value,
+ none: !hasAllRowSelected.value && !hasSomeRowSelected.value,
+ };
});
const loadPage = (pageNumber) => {
+ if (!keepSelectionAcrossPage) {
+ tableStore.clearSelectedRows();
+ }
tableStore.loadPage(pageNumber);
};
@@ -78,6 +125,18 @@ export default {
tableStore.sortTable(columnName, dir);
};
+ const togglePageRows = () => {
+ if (hasAllRowSelected.value) {
+ rows.value.forEach( (row) => tableStore.removeRowIdFromSelection(row.id));
+ } else {
+ rows.value.forEach( (row) => tableStore.addRowIdToSelection(row.id));
+ }
+ }
+
+ const clearSelectedRows = () => {
+ tableStore.clearSelectedRows();
+ }
+
const searchItems = (query) => {
debounceSearch(query);
}
@@ -87,20 +146,23 @@ export default {
}
/**
- * Execute a table action, i.e. call a javascript function pass into props.action.
+ * Execute a table row action, i.e. call a javascript function pass into props.action.
* The function is executed with the row id as first param.
*
* @param actionName
* @param id
*/
- const executeAction = (actionName, id) => {
- props.actions[actionName](id);
+ const executeRowAction = (actionName, id) => {
+ props.rowActions[actionName](id);
}
onMounted(() => {
tableStore.fetchItems();
});
+ // have storeId available to children component
+ provide('tableStoreId', storeId);
+
return {
isFetching,
query,
@@ -116,7 +178,12 @@ export default {
sortTable,
clearSearch,
setItemsPerPage,
- executeAction,
+ hasSelectableRows,
+ selectedRowSize,
+ togglePageRows,
+ clearSelectedRows,
+ pageSelectState,
+ executeRowAction,
};
},
};
@@ -139,7 +206,12 @@ export default {
:sortTable="sortTable"
:clearSearch="clearSearch"
:setItemsPerPage="setItemsPerPage"
- :executeAction="executeAction"
+ :executeRowAction="executeRowAction"
+ :hasSelectableRows="hasSelectableRows"
+ :selectedRowSize="selectedRowSize"
+ :togglePageRows="togglePageRows"
+ :pageSelectState="pageSelectState"
+ :clearSelectedRows="clearSelectedRows"
v-bind="$attrs">table
diff --git a/src/components/table/table.store.js b/src/components/table/table.store.js
index 102eccd..a1907a6 100644
--- a/src/components/table/table.store.js
+++ b/src/components/table/table.store.js
@@ -26,10 +26,17 @@ export const useTableStoreFactory = (id) => {
}
}),
currentRows: [],
+ selectedRows: new Set(),
totalItems: 0,
isFetching: false,
}),
getters: {
+ isRowSelected: (state) => {
+ return (id) => state.selectedRows.has(id);
+ },
+ hasRowSelected: (state) => {
+ return state.selectedRows.size > 0;
+ },
},
actions: {
/**
@@ -75,10 +82,27 @@ export const useTableStoreFactory = (id) => {
}
});
},
+ toggleRow(id) {
+ if(this.isRowSelected(id)) {
+ this.selectedRows.delete(id);
+ } else {
+ this.selectedRows.add(id);
+ }
+ },
+ addRowIdToSelection (id) {
+ this.selectedRows.add(id);
+ },
+ removeRowIdFromSelection (id) {
+ this.selectedRows.delete(id);
+ },
+ clearSelectedRows() {
+ this.selectedRows = new Set();
+ },
deleteRow(id) {
this.currentRows = [...this.currentRows.filter((tableRow) => {
return tableRow.id !== id
})];
+ this.fetchItems();
},
loadPage(pageNumber) {
this.tableState.currentPage = pageNumber;
@@ -127,6 +151,43 @@ export const useTableStoreFactory = (id) => {
setDataUrl(url) {
this.url = url;
},
+ /**
+ * Callback server for the purpose of executing an action.
+ * Callback will trigger onTrigger event in TriggerCtrl.
+ */
+ executeAction(url, targetElement) {
+ const options = {
+ method: 'POST',
+ body: utils().json().stringify({
+ ids: Array.from(this.selectedRows),
+ }),
+ }
+
+ targetElement.classList.add('loading');
+ const { isFetching, data, onFetchFinally, onFetchError } = apiService.fetchAsResponse(url, options);
+
+ watch(isFetching, (inProgress) => {
+ this.isFetching = inProgress;
+ });
+
+ onFetchFinally( () => {
+ const results = data.value || {};
+ if (results.jsRendered) {
+ apiService.evalResponse(results.jsRendered);
+ }
+ if (results?.state?.reload) {
+ this.fetchItems();
+ }
+ if (results?.state?.keepSelection === false) {
+ this.selectedRows = new Set();
+ }
+ targetElement.classList.remove('loading');
+ });
+
+ onFetchError( (error) => {
+ console.error(error);
+ });
+ }
},
});
fohn.vueService.addStore(id, store);