Velo Tutorial: Calculating and Displaying Collection Data
Per Item Calculations
You can add a hook to a collection by clicking on Hooks in the Content Manager's menu. After choosing which hooks you want to register, skeletal code will be added to a file named data.js in your site's Code Files, Backend section, for each hook you selected.
The idea is to create an afterQuery hook which intercepts the data from your collection before it reaches your page code. The hook runs for each item that is retrieved by a dataset or query. The hook performs the desired calculations and adds the calculated data to each item before it gets to the page. Then, in your page code, you can get the calculated data and apply it to your page's elements.
1// In file backend/data.js
2
3export function Employees_afterQuery(item, context) {
4 item.fullName = item.lastName + ", " + item.firstName;
5 item.age = ageFromDob(item.dob);
6
7 return item;
8}
The hook receives the item from the collection and adds two new properties to it. First, it adds a fullName
property that concatenates the employee's name in a "lastName, firstName" format. Second, it adds an age
property that calculates an employee's age based on the date stored in the dob
field.
1function ageFromDob(dob) {
2 const now = new Date();
3 const days = now.getDate() - dob.getDate();
4 const months = now.getMonth() - dob.getMonth();
5 let age = now.getFullYear() - dob.getFullYear();
6
7 if (months < 0 || (months === 0 && days < 0)) {
8 age--;
9 }
10
11 return age.toString();
12}
In the following examples, we will assume that the page receives its data using a dataset. Generally, this means that you can connect your page elements to any non-calculated data as you would normally do. However, since your calculated data is not part of the database schema, you will have to "connect" the data to page elements using code. If you use a query instead of a dataset to retrieve your page's information, you can use a similar approach to apply the calculated data to your page elements.
Text Elements
To apply calculated data to a text element, you set the value of the text element's text
property to the value of one of the item's calculated properties in the dataset's onReady()
function.
If your page's dataset contains more than one item and you allow your site visitors to click through multiple items using previous and next buttons, you also need to set the value of the text element's text
property in the dataset's onCurrentIndexChanged
function.
1$w.onReady(function () {
2 $w("#dataset1").onReady(() => {
3 populateCalculatedFields();
4 } );
5
6 $w("#dataset1").onCurrentIndexChanged( (index) => {
7 populateCalculatedFields();
8 } );
9} );
10
11function populateCalculatedFields() {
12 const currentItem = $w("#dataset1").getCurrentItem();
13 $w("#textFullName").text = currentItem.fullName;
14 $w("#textAge").text = currentItem.age;
15}
Here, the populateCalculatedFields()
function takes the calculated data from the dataset and applies it to the corresponding page elements. It is called when the dataset is first ready and then every time the current item is changed.
Table
To apply calculated data to a table, you reset the table's columns using its columns
property to include columns for each item's calculated values. You then set the data of the table using the table's rows
property.
1$w.onReady(function () {
2 const currentColumns = $w("#employeeTable").columns;
3 const calculatedColumns = [
4 {
5 "id": "colFullName",
6 "dataPath": "fullName",
7 "label": "Name",
8 "type": "string",
9 },
10 {
11 "id": "colAge",
12 "dataPath": "age",
13 "label": "Age",
14 "type": "string",
15 }
16 ];
17 $w("#employeeTable").columns = calculatedColumns.concat(currentColumns);
Here, the code begins by getting the current columns from the table using the columns
property. Remember, those will correspond to the fields from the dataset, minus the columns we deleted.
Next, it creates an array of two new columns that will hold the calculated data.
Then, it puts both sets of columns together and uses that to reset the table's columns
property.
Repeater
To apply calculated data to a repeater, you set the values of the corresponding elements using the repeater's onItemReady()
function.
1$w.onReady(function () {
2 $w("#employeeRepeater").onItemReady( ($item, itemData, index) => {
3 $item("#repeaterFullName").text = itemData.fullName;
4 $item("#repeaterAge").text = itemData.age;
5 } );
6} );
Here, the repeater onItemReady()
function takes the calculated data from the dataset and applies it to the corresponding elements in the repeater items. It is called for each item in the repeater as they are created.
Aggregations
1import wixData from 'wix-data';
2import {monthSort} from 'public/utils';
3
4$w.onReady(function () {
5 $w("#aggTable").columns = [
6 {
7 "id": "col1",
8 "dataPath": "month",
9 "label": "Month",
10 "type": "string"
11 },
12 {
13 "id": "col2",
14 "dataPath": "total",
15 "label": "Total Expenses",
16 "visible": true,
17 "type": "number"
18 }
19 ];
20
21 wixData.query("Expenses")
22 .ascending("month")
23 .limit(1000) // include a limit if you have more than 50 items
24 .find()
25 .then( (result) => {
26 const months = result.items.map(x => x.month)
27 .filter((obj, index, self) => index === self.indexOf(obj))
28 .sort(monthSort);
29 const aggregated = months.map(x => {
30 return {
31 month: x,
32 total: result.items.filter(obj => obj.month === x)
33 .map(z => z.amount)
34 .reduce((sum, current) => sum + current)
35 };
36 } );
37
38 $w("#aggTable").rows = aggregated;
39 } );
40} );
Next, the code performs a query on the expenses collection. Once it receives the results of the query, it uses standard JavaScript array functions to calculate the total expenditures for each month.
1const months = ["January", "February", "March", "April",
2 "May", "June", "July", "August",
3 "September", "October", "November", "December"];
4
5export function monthSort(a, b) {
6 return months.indexOf(a) - months.indexOf(b);
7}
Finally, the objects that were just created are set to be the row data for the table.