Skip to content

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:

vue
<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:

vue
<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:

ts
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:

vue
<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:

vue
<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.

ts
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:

  • items defines the getter for accessing the list of items
  • query defines the action for requesting a set of items
  • queryParmas defines the additional parameters that should be passed to the query action. By default this will set and override any limit and offset values.
  • limit defines the maximum number of items that should be retrieved per query. By default this is 50.

For the returned items:

  • items is the set of items that have been requested
  • loading indicates if the latest request is loading
  • fetch is a function that can be called to fetch the next set of results
  • reset is 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:

ts
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:

vue
<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 any query parameters to pass to the query action.

Example Usage

vue
<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:

vue
<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:

vue
<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:

ts
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 message

Pagination

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:

  • pageSize defines the size of each page, i.e. the number of items on the page
  • total defines the total number of results for the overall query
  • currentPage defines the "current" page, that is the starting point for the pagination logic

This returns the following:

  • pageSize the size of the page
  • total the total number of results
  • currentPage the current page
  • offset the offset, i.e. the number of items to skip for the query to retrieve the "current" page
  • lastPage the final page, i.e. the final page number
  • next a function for moving the state to the "next" page
  • prev a function for moving the state to the "previous" page
  • first a function for moving the state back to the first page
  • last a function for moving the state to the last page
  • set a function for moving the state to a specific page

Example usage:

ts
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 == 2

Patchable

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 to ResourceDictVuex, i.e. the getter returns a function that accepts a key to retrieve the item from a map
    • getter. The getter that is to be used to retrieve the data
    • query. The action that is to be used to query for the data.
    • queryParmas. A function returning a set of parameters to be passed to the query to retrieve the data
  • An existing Ref to 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:

  • patchQuery defines the action that should be called
  • patchQueryParams defines a function that provides the parameters that should be passed to the query
  • callback defines 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:

  • original The original state prior to it being changed
  • updated The 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:

  • selector i.e. the DOM element that the popover should be anchored from
  • descriptor i.e. the DOM element that should appear as the popover
  • options a configuration object that defines the options for the popper element. Options are identical to those documented here
  • onClose the function to be called when an "outside" click is detected, i.e. a click that does not fall within the selector or descriptor elements.

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:

vue
<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:

  • canPreview simply returns true if the provided content type or file extension is supported in terms preview ability
  • toExtension converts the provided content type or extension to a file extension where possible.

Example Usage:

vue
<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>

Released by DevOps Team