Composable
There are a number of general functions that have been pulled out for reuse. These are available in the composable folder.
Can
Provides functions to determine if the current user has permission to access certain parts of the system. Note that this is purely to enforce UI limitations. Data query limitations should also be limited permission verification steps on the backend.
This currently operates by calling a corresponding Vuex getter.
Example Usage:
<template>
<!-- User has this specific permission -->
<div v-if="can('This.Permission')">
...
</div>
<!-- User has any of these permissions -->
<div v-if="canAny(['This.Permission', 'That.Permission'])">
...
</div>
<!-- User has all of these permissions -->
<div v-if="canAll(['This.Permission', 'That.Permission'])">
...
</div>
</template>
<script>
export default defineComponent({
setup() {
const {can, canAny, canAll} = useCan();
return {can, canAny, canAll}
}
})
</script>Charts
This exports a useCharts function, which returns three functions that can be used to create pie, polar area, and bar charts using Chart.js library.
The createPieChartData, createPolarAreaChartData, and createBarChartData functions all take in two arrays as arguments: labels and data.
They return an array with two elements: the type of chart ("pie", "polarArea", or "bar") and the chart data object.
The colourOptions object defines the border colors and background colors for the charts.
The module imports the necessary Chart.js types and the chartjs-plugin-datalabels plugin.
Example Usage:
<template>
<div>
<canvas id="my-chart"></canvas>
</div>
</template>
<script>
import { useCharts } from './composable/chart';
export default {
setup() {
const { createPieChartData } = useCharts();
const labels = ['Apples', 'Bananas', 'Oranges'];
const data = [10, 5, 8];
const [chartType, chartData] = createPieChartData(labels, data);
const chartConfig = {
type: chartType,
data: chartData
};
return{
chartConfig
};
}.
mounted(){
// assuming you have a canvas element with id "my-chart"
const chart = new Chart(document.getElementById('my-chart'), chartConfig);
}
}
</script>Currency
Provides a helper function for formatting currency values a AUD.
Example usage:
const someAmountOfMoney = 1234;
const formatted = fmtCurrency(someAmountOfMoney);
console.log(formatted)
// > $1,234;Date
This defines a set of utility functions to work with dates and times using the Luxon library. The toDateTime() function takes a date-like value (DateTime, Date, string or undefined) and converts it into a Luxon DateTime instance. The other functions then operate on this DateTime instance to format it in different ways.
For example, fmtToLocalShortDate() takes a date-like value and returns a short text representation of the date in the local timezone (e.g. "28 Feb 2023"). fmtToLocalDatetime() returns a string representation of the date and time in the local timezone (e.g. "28 Feb 2023, 13:45:00").
Example Usage:
<script>
import { fmtToLocalDatetime } from "./composable/date";
const date = new Date("2023-02-28T12:34:56Z");
const formattedDate = fmtToLocalDatetime(date); // Returns "28 Feb 2023, 12:34:56"
</script>Enum
This defines a utility function named useEnum that transforms an TypeScript enumerations object(e.g. as generated by Service Proxies).
It takes enum object as input and returns two helper functions.
The first function toDescription converts an enum value into a human-readable text representation that is capitalised and spaced appropriately.
The second function toDropdownOptions maps the enum object into a list of key-value options that are suitable for usage in a dropdown list.
Example usage:
<template>
<select>
<option
v-for="option in options"
:key="option.value"
:value="option.value"
>
{{ option.key }}
</option>
</select>
</template>
<script>
export default defineComponent({
setup() {
enum SomeEnum {
SomeValue = 1,
AnotherValue = 2,
}
const {toDescription, toDropdownOptions} = useEnum(SomeEnum);
console.log(toDescription(SomeEnum.SomeValue))
// > Some Value
const options = toDropdownOptions();
return {options}
}
})
</script>InfiniteList
This provides two composition functions to implement infinite scrolling.
The useInfiniteListable and useInfiniteTrigger functions.
useInfiniteListable
useInfiniteListable wraps some basic management capabilities for handling infinite list queries. It defers all queries to the Vuex store, as defined by the ResourceInfiniteVuex helper function.
const { items, loading, fetch, reset } = useInfiniteListable({
items: MatterStore.getters.GET_MATTERS,
query: MatterStore.actions.GET_MATTERS,
queryParams: () => ({
search: props.search,
})
});In essence, for the configuration:
itemsdefines thegetterfor accessing the list of itemsquerydefines theactionfor requesting a set of itemsqueryParmasdefines the additional parameters that should be passed to thequeryaction. By default this will set and override anylimitandoffsetvalues.limitdefines the maximum number of items that should be retrieved per query. By default this is 50.
For the returned items:
itemsis the set of items that have been requestedloadingindicates if the latest request is loadingfetchis a function that can be called to fetch the next set of resultsresetis a function that be called to reset the query to start from the "beginning" of the list. Typically this is used to reset the results when a search query has been updated.
The reset function is typically used like so:
watch([() => props.search], reset);useInfiniteTrigger
useInfiniteTrigger wraps the handling of automatically calling a provided function (e.g. the fetch function or equivalent) when the container has been scrolled to the end.
This requires references to a containing DOM element, and a "sentinel" DOM element that is used as an indication of scroll location.
Example usage:
<template>
<div ref="container">
<div v-for="item in items" :key="item.id">
{{item.value}}
</div>
<div ref="sentinel">
</div>
</template>
<script>
export default defineComponent({
props: {
search: String
},
setup(){
const { items, loading, fetch, reset } = useInfiniteListable({
...
});
watch([() => props.search], reset);
const {container, sentinel} = useInfiniteTrigger(fetch);
return {items, container, sentinel}
}
})
</script>TIP
You may need to adjust the CSS to allow for overflow in order for scrolling to occur and for scroll events to be triggered/handled.
You may choose to use this helper directly as with the above example or by using the AlpInfiniteTable or AlpInfiniteContainer components which use this under the hood.
Listable
This is module that provides a useListable function, used as a minor wrapper around getters and actions for retrieving data in the PaginatedDTO format.
To use useListable, you need to pass a configuration object with three properties:
items: the name of the Vuex module that contains the items.query: the name of the Vuex action that retrieves the items.queryParams: a function that returns an object with anyqueryparameters to pass to thequeryaction.
Example Usage
<template>
<div>
<ul>
<li v-for="item in items" :key="item.id">{{ item.name }}</li>
</ul>
<div v-if="loading">Loading...</div>
<div>Total items: {{ count }}</div>
<button @click="fetch">Refresh</button>
</div>
</template>
<script>
import { useListable } from './composable/listable';
export default {
setup() {
const { items, count, loading, fetch } = useListable({
items: "myModule",
query: "fetchItems",
queryParams: () => ({ filter: 'myFilterValue' })
});
onMounted(() => {
fetch();
});
return {
items,
count,
loading
};
}
}
</script>ListableRelationships
The listableRelationships.ts module is similar to listable.ts, but it is specifically designed for retrieving relationships between entities. It also uses a PaginatedDTO format.
Example Usage:
<template>
<div>
<ul>
<li v-for="item in items" :key="item.id">{{ item.name }}</li>
</ul>
<button @click="fetch()">Load More</button>
<div v-if="loading">Loading...</div>
</div>
</template>
<script>
import { useListableRelationships } from "./composable/listableRelationships";
export default {
setup() {
const { items, loading, fetch } = useListableRelationships({
items: "users",
query: "getUserList",
queryParams: () => ({ type: "staff" })
});
return { items, loading, fetch };
}
};
</script>Logger
These are minor wrappers around the standard console logging functions that exports a useLogger() function that returns an object with methods for logging various types of messages to the console.
It also checks if the code is running in a production environment before logging any messages to avoid logging sensitive data in production.
Example Usage:
<template>
<div>
<h1>{{ message }}</h1>
<button @click="logMessage">Log message</button>
<button @click="infoMessage">Info message</button>
<button @click="warnMessage">Warn message</button>
<button @click="errorMessage">Error message</button>
<button @click="traceMessage">Trace message</button>
</div>
</template>
<script>
import { useLogger } from "./composable/logger";
export default {
setup() {
const { log, info, warn, error, trace } = useLogger();
const message = "Hello, world!";
function logMessage() {
log("This is a log message", message);
}
function infoMessage() {
info("This is an info message", message);
}
function warnMessage() {
warn("This is a warning message", message);
}
function errorMessage() {
error("This is an error message", message);
}
function traceMessage() {
trace("This is a trace message", message);
}
return { message, logMessage, infoMessage, warnMessage, errorMessage, traceMessage };
},
};
</script>Notify
This provides wrapping functions for firing off common dialogs/toast notifications.
Example usage:
const {fireConfirm, fireSuccessToast, fireErrorToast} = useNotify()
fireConfirm("Some confirmation message");
// Displays a pop up dialog for the user to confirm. This currently uses Sweetalert2
fireSuccessToast("Some success message");
// Triggers the display of a successful toast message
fireErrorToast("Some error message");
// Triggers the display of an error toast messagePagination
Manages the pagination state for the case of manual page based pagination.
TIP
Given the migration to the "infinite list" approach to pagination, this is largely obsolete. This remains for any tables that have not been migrated over / potential future use.
Given the following configuration options:
pageSizedefines the size of each page, i.e. the number of items on the pagetotaldefines the total number of results for the overall querycurrentPagedefines the "current" page, that is the starting point for the pagination logic
This returns the following:
pageSizethe size of the pagetotalthe total number of resultscurrentPagethe current pageoffsetthe offset, i.e. the number of items to skip for the query to retrieve the "current" pagelastPagethe final page, i.e. the final page numbernexta function for moving the state to the "next" pagepreva function for moving the state to the "previous" pagefirsta function for moving the state back to the first pagelasta function for moving the state to the last pageseta function for moving the state to a specific page
Example usage:
const {
pageSize,
total,
currentPage,
offset,
lastPage,
next,
prev,
first,
last,
set
} = usePagination({
currentPage: 1,
pageSize: 20,
total: 60
});
next()
// currentPage == 2
prev()
// currentPage == 1
first()
// currentPage == 1
last()
// currentPage == 3
set(2)
// currentPage == 2Patchable
This is a helper function for managing the patching of data when a change is made to it.
It accepts two approaches to providing the base data from which changes are made:
- A set of getters and queries (actions) to use to retrieve this information
identifier. The identifier for retrieving the item from a store defined in a manner similar toResourceDictVuex, i.e. the getter returns a function that accepts a key to retrieve the item from a mapgetter. The getter that is to be used to retrieve the dataquery. The action that is to be used to query for the data.queryParmas. A function returning a set of parameters to be passed to thequeryto retrieve the data
- An existing
Refto the data
It also accepts a set of parameters defining the Vuex store action that should be called when a state change has been detected:
patchQuerydefines the action that should be calledpatchQueryParamsdefines a function that provides the parameters that should be passed to the querycallbackdefines optional function that can be called after the patch query has been made
This returns a reactive state that is watched by the usePatchable.
The patchQuery is called with an object containing at least the following:
originalThe original state prior to it being changedupdatedThe updated state
Additional parameters are included as provided by patchQueryParams
Popper
This provides a wrapper for handling the configuration of a simple Popper based dismissible dialog. This uses Popper.
Given a:
selectori.e. the DOM element that the popover should be anchored fromdescriptori.e. the DOM element that should appear as the popoveroptionsa configuration object that defines the options for the popper element. Options are identical to those documented hereonClosethe function to be called when an "outside" click is detected, i.e. a click that does not fall within theselectorordescriptorelements.
The function will handle the set up of the popper elements, and return a forceUpdate function. This function simply prompts popper to update it's positioning. This is not handled correctly otherwise in some cases, likely due to Vue redrawing the DOM from its virtual DOM.
Example usage:
<template>
<span
ref="selector"
@click.stop="toggle"
>
Anchor
</span>
<span
ref="descriptor"
v-show="state.showDescriptor"
>
Content
</span>
</template>
<script>
export default defineComponent({
setup() {
const state = reactive({
showDescriptor: false
})
const selector = ref<HTMLElement | null>(null);
const descriptor = ref<HTMLElement | null>(null);
const { forceUpdate } = usePopper({
selector,
descriptor,
options: {
placement: "bottom-end",
modifiers: [{ name: "hide" }]
},
onClose: () => (state.showOptions = false)
});
function toggle() {
state.showDescriptor = !state.showDescriptor;
forceUpdate();
}
return {state, toggle}
}
})
</script>Preview
This provides helper functions related previewing a document:
canPreviewsimply returns true if the provided content type or file extension is supported in terms preview abilitytoExtensionconverts the provided content type or extension to a file extension where possible.
Example Usage:
<template>
<div>
<h1>File Preview</h1>
<div v-if="canPreview(file.contentType)">
<img v-if="toExtension(file.contentType) === 'jpg'"
:src="file.url" alt="file preview" />
<iframe v-else-if="toExtension(file.contentType) === 'pdf'"
:src="file.url"></iframe>
</div>
<div v-else>
<p>This file cannot be previewed.</p>
</div>
</div>
</template>
<script>
import { usePreview } from "./composable/preview";
export default {
props: {
file: {
type: Object,
required: true,
},
},
setup() {
const { canPreview, toExtension } = usePreview();
return {
canPreview,
toExtension,
};
},
};
</script>