Load filter by reference fields items to a repeater

Hi! I want to share something that could be usefull to someone else in the future, is not perfect (any sugestions for improvement please share), and also has a lot of “extra” for my case, but could be use has reference for starting from there.

Im developing a webapp that needs to loads items to a repeater with code, make possible to search by keyword press and filter the database by the reference field depending on the “tag” that is use. For example, one of the referenced fields is “product”, which is a reference field and a tag. So when this tag is clicked, and in the search bar someone writes “mini”, needs to show in the repeater all the items that reference a product that the name contains the word “mini”. This can’t be done directly without code, because before the data is loaded in the repeater, I process it with code to show different labels depending on the data format specific fields (like “monto”, “status” or “fecha”).

My first aproach was to use a wixQuery and the “include()” functions… and it worked well for the first part, but wasn’t easy to do pagination for filtered items (this was also a requirement), and also this function has his limitations (don’t include reference field of a reference field for example).

So I change the strategy to load the items from a collection dataset in the page, which I can easily filter and do pagination of the filtered items, and then use this items in the repeater.

//Get items from dataset for using in repeater.
export function getItems() {
    $w("#dataset1").onReady( () => {
        $w("#dataset1").getItems(0, 5)
        .then( (result) => {
            pedidos = result.items; itemsTotales = $w("#dataset1").getTotalCount(); 
            pagActual = $w("#dataset1").getCurrentPageIndex(); totalPages = $w("#dataset1").getTotalPageCount();
            $w("#txtCantItems").text = "Items: "+itemsTotales.toString();
            if (totalPages === 1) {$w("#btnNext").disable()} else {$w("#btnNext").enable()}
            let fadeOptions = {"duration": 500,"delay": 0};
            $w('#boxTabla').onViewportEnter(() => {$w('#loadingTable').hide();} );
            if($w('#boxTabla').hidden) { $w('#boxTabla').show('fade',fadeOptions)}
            $w('#tabla').data = pedidos;
            $w('#txtNumPagina').text = pagActual.toString()+" de "+totalPages.toString();
        } )
        .catch( (err) => {
            let errMsg = err.message;
            let errCode = err.code;
        });
    });
}

In parallel I load the different referenced fields databases into variables that is use to get the info.

productos = await getProductos(); 
categorias = await getCategorias();
empresas = await getEmpresas();

//Gets list of products for saving in array "productos".
export async function getProductos() {
  let results = await wixData.query("Productos").find();
  return results.items.map( (item) => {
        return {
        "_id": item._id,
        "nombre": item.nombre,
        "categoria": item.categoria
        };//Gets specific fields from query result.
   });
}

...the same for each database that is referenced.

Then this info in complement with the data loaded from the dataset collection build all the information necessary to be loaded in the repeater. The key is the “find” function use to find the specific info item in the referenced field array.

let producto = productos.find(item=> item._id===itemData.producto);
$item('#txtNomProducto').text = producto["nombre"];

Basically each time that the we need info from a reference field we use the id to search inside the array for this reference field we loaded before and get the rest of the info, like the name (“nombre”) in this upper example. This can also use for referenced of referenced fields (example “categorias” is a reference field of the referenced field “producto” of the “pedidos” database).

//Function that loads data into repeater.
function loadRepeater() {
    let dateOptions = {day: "numeric", month: "short", year: "numeric"};
    let timeOptions = {hour: '2-digit', minute: '2-digit'};
    
    $w('#tabla').onItemReady(($item, itemData,index) => {
        let producto = productos.find(item=> item._id===itemData.producto);
        let categoria = categorias.find(item=> item._id===producto["categoria"]);
        let cliente = empresas.find(item=> item._id===itemData.cliente);
        pedidos[index].producto = producto;
        pedidos[index].categoria = categoria;
        let status = itemData.status[0];
        $item('#txtNomProducto').text = producto["nombre"];
        $item('#txtCategoria').text = categoria.nombre; 
        $item('#imgCategoria').src = categoria.icono;
        $item('#txtCliente').text = cliente["nombreFantasia"];
        $item('#txtRut').text = cliente["rut"];
        $item('#txtStatus').text = status;
        $item('#txtMonto').text = numberPesos(itemData.monto);
        $item('#txtFecha').text = itemData._createdDate.toLocaleDateString("es-cl", dateOptions);
        $item('#txtFechaMobile').text = itemData._createdDate.toLocaleDateString("es-cl", dateOptions);
        $item('#txtHora').text = itemData._createdDate.toLocaleTimeString("es-cl",timeOptions);

        //For labeling the status property with different color accordinly.
        switch (status) {
            case "Entregado":
                $item('#boxStatus').style.backgroundColor = "#5FDBA7";
                break;
            case "Pendiente":
                $item('#boxStatus').style.backgroundColor = "#FFCC00";
                break;
            case "Cancelado":
                $item('#boxStatus').style.backgroundColor = "#B2AEAE";
                break;
            default:
                $item('#boxStatus').style.backgroundColor = "#E9EBE9";
                break;
        }
    });
    console.log("Pedidos post carga en repeater:",pedidos);
}

Then the fun part comes when we want to filter all this, do pagination of the results, and then show them using the same function for formating and labeling.

For this, I made a function that depending on the “tag” it first create a filtered array of the original array loaded from the reference field database. The array is filtered with all the items that contain the keyword use in the search bar.

productosFiltrados = productos.filter(({nombre}) => nombre.toLowerCase().includes(valorBuscado));

Then this new array is used to build with a loop a “filter” that is then send to filter the database.

for (let i = 0; i < productosFiltrados.length; i++) {
                filter = filter.or(wixData.filter().eq("producto", productosFiltrados[i]._id)); 
            }
setFilter(filter);

function setFilter(filter) {    
    $w("#dataset1").setFilter(filter
    ).then( () => {
    console.log("Dataset filtrado");
    getItems();
    })
    .catch( (err) => {
    console.log(err);
    });
}

So then when key is press in the search bar, it filter depending on the tag, the items that contain the input keyword.

export function iptBuscador_keyPress(event) {
    $w('#btnClear').show();
            if (debounceTimer){
        clearTimeout(debounceTimer);
        debounceTimer; undefined;
    }
    debounceTimer = setTimeout(() => {
        filtrar($w('#iptBuscador').value);
    }, 500);
}

Example, by tag “Cliente”, keyword “Contrat”.

So this was my case :slightly_smiling_face:. I hope that it give some reference for anyone that has a similar challenge. Please any sugestion for improvement please share. I learn to code by looking forums (and videos), so is very possible that a lot of things could be done better.

PD. I didn’t add the code for the behaviour of the buttons tags and other things, but that could be search easily in other posts.

Do you know if there is any other easier way to filter by reference fields? I’ve been struggling to find a solution for a while now. Been coding with Velo for 9 months now and I haven’t found any other solution except what you have showed here so I really appreciate it.