Debounce Help for Database Search--- URGENT

Hi Elizabeth:

You are mixing a number of things up here which is leading to a bad result. The function assignValidValue is a stand alone function and does not need any changes made to do what you need.

By adding assignValidValue calls like assignValidValue(’ #businessName ', ‘text’, itemData.businessName); inside of the function then you have created what is called infinite recursion. So when assignValidValue is called it executes until it sees assignValidValue and then executes itself again before the original call finishes. This is why you get the Maximum Stack Size Exceeded error. Every time you call a function, it has to remember where it was called from so it keeps a record of that in memory. Because your code does this without end, eventually the interpretter runs out of memory and gives up :-).

When you call assignValidValue it performs the element assignment for you by substituting in the values you give to the assignValidValue function. If any of those values are unusable (this is what the if statement checks) then the assignment is ignored and not made. If the arguments passed are aceptable then the assignment is made.

Let me work through the code you shared and send you something that should work in the next post. In the meantime I would recommend that you look at some javascript tutorials on defining functions etc. which might help you along. W3Schools has some good reference material:

Cheers

Steve

Hi Elizabeth:

You are trying to mimic the database search from the tutorial link you referenced above.

So let’s start from the beginning.

You need a filter function that the debounce timer calls.
At the moment you don’t have a filter so your debounce function isn’t going to work your code says this:

debounceTimer = setTimeout(()=> {
     ($w('#iTitle').value);  /// <<<<<< missing the filter function name before the ( 
},500);

Your filter should combine the title and business category searching similar to the example. So it needs to look something like this (I say something like this because its not clear that you are using a dataset in your solution and you seem to have two data collections in your existing code BusinessDirectory and BusinessDirectory_New):

function filter(title, businessCategory) {
     if (lastFilterTitle !== title || lastFilterCategory !== businessCategory) {
          let newFilter = wixData.filter();
          if (title) {
              newFilter = newFilter.contains('title', title);
          }
          if (businessCategory) { 
             newFilter = newFilter.eq('businessCategory1', businessCategory);
          }
          $w('#'BusinessDirectory_New_DataSet').setFilter(newFilter);
          lastFilterTitle = title;
          lastFilterCategory = businessCategory; //Remember the selected category to use in our filter
     }
 }

Now the filter can be added to your debounce timer code like this.

import wixData from 'wix-data';
let debounceTimer;
let lastFilterTitle = '';
let lastFilterCategory = '';
$w.onReady(function () {
    $w("#repeater1").onItemReady(($w, itemData, index) => {
        assignValidValue('#businessName', 'text', itemData.businessName);
        assignValidValue('#subcatdes'), 'text', itemDataSubCategoryDescriptor);
        assignValidValue('#addline1', 'text', itemData.addressLine1);
        assignValidValue('#addline2', 'text', itemData.addressLine2);
        assignValidValue('#description', 'text', itemData.businessDescription);
        assignValidValue('#citystzip', 'text', itemData.cityStateZip);
        assignValidValue('#logo', 'src', itemData.logo);
    } );
    
    wixData.query('BusinessDirectory_New')
       .find()
       .then((results) => {
           $w("#repeater1").data = results.items;
       });
} );

function assignValidValue(elementName, elementProperty, elementValue) {
    // Debug info
    console.log('assignValidValue('
             +(elementName?elementName:',missing name') + ', '
             +(elementProperty?elementProperty:',missing property') + ', '
             +(elementValue?elementValue:',missing value') + ')');
    if (elementName && elementProperty && elementValue) {
        // We have a value element, property and value
        $w(elementName)[elementProperty] = elementValue;
        // Show the element if it has a value
        $w(elementName).show();
    } else {
        // Hide the element if it has no value
        $w(elementName).hide();
    } 
}

function filter(title, businessCategory) {
    if (lastFilterTitle !== title || lastFilterCategory !== businessCategory) {
        let newFilter = wixData.filter();
        if (title) {
            newFilter = newFilter.contains('title', title);
        }
        if (businessCategory) {
            newFilter = newFilter.eq('businessCategory1', businessCategory);
        }
        $w('#'BusinessDirectory_DataSet').setFilter(newFilter);
        lastFilterTitle = title;
        lastFilterCategory = businessCategory; //Remember the selected category to use in our filter
   } 
}

export function iTitle_keyPress(event, $w) {
    if (debounceTimer){
        clearTimeout (debounceTimer);
        debounceTimer = undefined;
    }
    debounceTimer = setTimeout(()=> {
        filter($w('#iTitle').value, lastFilterCategory);
    },500);
}

export function ibusinessCategory1_change(event, $w) {
    filter(lastTitle, $w('#ibusinessCategory1').value);
}

NOTE: My understanding of your code is that you are not using a dataset and you have two tables one for the drop down list and one for the repeated data items. However I am just guessing at this point because I don’t have the full context of your page. If you review the tutorial video you will see that Yval from Wix creates the drop down list by creating an array in the page onReady function:

wixData.query('Continents')
      .find()
      .then(res => {
          let options = [{'value': '', 'label': 'All Continents'}];
          options.push(...res.items.map(continent => {
              return {'value':continent.title, 'label':continent.title};
          }));
          // Load drop down values
          $w('#iContinent').options = optons;
       });

So to make the above code work (as I have written it) you will need a dataset named BusinessDirectory_DataSet. If you already have one with a different name then that’s the name you need :slight_smile:

Hope this helps get you further.

Steve,
This is really more than I could ask for. It’ is so helpful. It’s true, I need to dive into java a bit more. I’m much stronger with the other languages. I’ll let you know how it goes.

Steve,
Just tried the code. It is sort of working.

No errors. It Just keeps repeating the same listing over and over.
I noticed that you had focused on business category over business name in the filter so I tried to see if that was the cause. It wasn’t. The search bar is supposed to look for the words in the title.

Also to address the onReady drop down. I didn’t mimic that exactly because I have too many values repeated. So this is part of my code that focuses on that. I created an on change for that.

function ibusinessCategory1_change(event, $w) {
wixData.query(‘Business_Directory’)
.contains(‘businessCategory1’,$w(’ #ibusinessCategory1 ‘).value)
.find() .then(res => { $w(’ #repeater1 ').data = res.items; });

Also, the title field key is for a status not the business name.
Just hoping to clarify things.

I am thinking if you just leave the elements names blank, just tell me what you think they should do,
I will have more success.

Thank you again.

This is the new code I am trying
import wixData from ‘wix-data’;
let debounceTimer;
$w.onReady( function () {
$w(“#repeater1”).onItemReady(($w, itemData, index) => {
assignValidValue(‘#businessname’, ‘text’, itemData.businessName);
assignValidValue(‘#subcatdes’, ‘text’, itemData.categorySubDescriptor);
assignValidValue(‘#addline1’, ‘text’, itemData.addressLine1);
assignValidValue(‘#addline2’, ‘text’, itemData.addressLine2);
assignValidValue(‘#description’, ‘text’, itemData.businessDescription);
assignValidValue(‘#citystzip’, ‘text’, itemData.cityStateZip);
assignValidValue(‘#logo’, ‘src’, itemData.logo);
console.log(itemData.title);
if (itemData.title === “NON”) {
$w(“#membadge”).hide();
$w(“#fullprofbutton”).hide();
} else {
$w(“#membadge”).show();
$w(“#fullprofbutton”).show();
}
} );

wixData.query('BusinessDirectory_New') 
   .find() 
   .then((results) => { 
       $w("#repeater1").data = results.items; 
   }); 

} );

function assignValidValue(elementName, elementProperty, elementValue) {
// Debug info
console.log(‘assignValidValue(’
+(elementName?elementName:‘,missing name’) + ‘, ’
+(elementProperty?elementProperty:’,missing property’) + ‘, ’
+(elementValue?elementValue:’,missing value’) + ‘)’);
if (elementName && elementProperty && elementValue) {
$w(elementName)[elementProperty] = elementValue;
$w(‘#addline2’).show();
$w(‘#description’).show();
$w(‘#logo’).show();
} else {
$w(‘#addline2’).hide();
$w(‘#description’).hide();
$w(‘#logo’).hide();
}
}

function iTitle_keyPress(event, $w) {
if (debounceTimer){
clearTimeout (debounceTimer);
debounceTimer = undefined;
}
debounceTimer = setTimeout(()=>
{($w(‘#iTitle’).value);
},500);
wixData.query(‘Business_Directory’)
.contains(‘businessName’,$w(‘#iTitle’).value)
.find()
.then(res => {
$w(‘#repeater1’).data = res.items;
});
}
function ibusinessCategory1_change(event, $w) {
wixData.query(‘Business_Directory’)
.contains(‘businessCategory1’,$w(‘#ibusinessCategory1’).value)
.find()
.then(res => {
$w(‘#repeater1’).data = res.items;
});
}

I keep getting the same values over and over again.

Hi Elizabeth:

Two things I can suggest:

The function I proposed needs you to send it an element not just the element’s name. My mistake. Basically, the repeater uses a scoped selector - the $w. What this does is ensures that the data that you retrieve from elements in this specific repeater iteration is the correct repeater collection. Because the $w isn’t being passed to the assignment function the $w used is the one that is used to select elements in the global scope.

So if you are familiar with the DOM, the repeater looks something like this

document.repeater.containerForItem1.businessname
document.repeater.containerForItem2.businessname

So when this handler is executed:

 $w("#repeater1").onItemReady(($w, itemData, index) => {

itemData is pointing to the correct data set value and the $w parameter is pointing to document.repeater.containerForItem1 so to configure the element with id businessname in containerForItem1 you access $w(‘#businessname’);
In the next iteration $w points to containerForItem2. and so on.

So to fix this you need to pass the whole element object (for each assignment call), not just the element name, like this:

assignValidValue($w('#businessname'), 'text', itemData.businessName);

Then you need to modify the assignValidValue function like this:

function assignValidValue(element, elementProperty, elementValue) {
  // Debug info
  console.log('assignValidValue('
                // We are getting an element object we want to print its id 
                +(element && element['id']?element.id:',missing element') + ', '
                +(elementProperty?elementProperty:',missing property') + ', '
                +(elementValue?elementValue:',missing value') + ')');
  // Make sure we have arguments with values to work with
  if (element && elementProperty && elementValue) {
      // Assign the value to the element's property
      element[elementProperty] = elementValue;
      // If this element is optional show it because we have a value
      if (element.id === 'addline2' ||
          element.id === 'description' ||
          element.id === 'logo') {
          element.show();
      }
  } else if (element && elementProperty) {
      // We don't have a value for this element
      // so don't try to set its value
      // If it is one of our optional elements then hide it 
      if (element.id === 'addline2' ||
          element.id === 'description' ||
          element.id === 'logo') {
          element.hide();
      }
  }  
}

Additionally you are not doing anything with your debounce timer. The setTimeout function takes three arguments, the first two are what you are interested in at this point.

The first one is a callback function, which you have defined as an argument-less anonymous function. The second is the timer value which you have set to 500ms (half a second).

The anonymous function that you have defined executes the following code when your 500ms timer completes:

($w('#iTitle').value);

This doesn’t really do anything except return the value of the title input element that you have on your page. It doesn’t get assigned to anything so it essentially disappears, effectively not doing the filtering you are expecting it to.

If your idea is to execute the this code when the timer completes:

wixData.query('Business_Directory')
    .contains('businessName',$w('#iTitle').value)
    .find()    
    .then(res => {
          $w('#repeater1').data = res.items;
    });

Then you will not get what you expect. Why? Well it executes immediately after you set the timer. When it executes it is likely that only one letter has been entered in your input field.

For this code to execute and perform the search it needs to replace the code highlighted above [($w(‘#iTitle’).value);] in the anonymous function. So when the timeout completes this code will have the value in $w(#iTitle1’).value .

Try this:

function iTitle_keyPress(event, $w) {
    // Starting a new search reset debounce and give the user 500ms to enter a search term
    if (debounceTimer){
        // New extra text entered before search function fired
        clearTimeout (debounceTimer);
        debounceTimer = undefined;
    }
    // Start new timer
    debounceTimer = setTimeout(()=> {
        // When timeout fires start query to find search term
        wixData.query('Business_Directory')
       .contains('businessName',$w('#iTitle').value)
       .find()    
       .then(res => {
           // Update repeater data set with result
           // NOTE: If the search returns with zero results then the repeater 
           // will end up removing all items and an empty page will be shown
           $w('#repeater1').data = res.items;
       });
       // Set timeout for 500ms. 
    } , 500);
}

Good luck

Steve

1 Like

Steve,
Thank you for this.
I just wanted to remind you of the URL so you could have an easier time inspecting elements.
https://www.sleepyhollowtarrytownchamber.com/business-directory

Also wanted to include the two sources of the original code (this is related to the suggestion below the code)

a. The repeater set up: Wix Code | How to Add & Use Repeating Layouts - YouTube
b. The search coding: https://www.youtube.com/watch?v=Hx7_8-lRsW0

I. THE GOOD NEWS:
-The assignment works.
-There are no more repeats
-I added the right A-Z sort for the query

II. THE BAD NEWS:
-Neither search functions are working.
-I noticed it is taking much longer to load the page
-I still cannot figure out how to adapt my dynamic page url so it links to the full profile button.

$w(" #fullprofbutton ").link = ’ https://www.sleepyhollowtarrytownchamber.com/BusinessDirectory-New/Profile/{businessName} ';

III. THE NEW CODE:

import wixData from 'wix-data';
let debounceTimer;
$w.onReady(function () {
     $w("#repeater1").onItemReady(($w, itemData, index) => {
    assignValidValue($w('#businessname'), 'text', itemData.businessName);
    assignValidValue($w('#subcatdes'), 'text', itemData.categorySubDescriptor);
    assignValidValue($w('#addline1'), 'text', itemData.addressLine1);
    assignValidValue($w('#addline2'), 'text', itemData.addressLine2);
    assignValidValue($w('#description'), 'text', itemData.businessDescription);
    assignValidValue($w('#citystzip'), 'text', itemData.cityStateZip);
    assignValidValue($w('#logo'), 'src', itemData.logo);
        console.log(itemData.title);
 if (itemData.title === "NON") {
            $w("#membadge").hide();
            $w("#fullprofbutton").hide();
        } else {
            $w("#membadge").show();
            $w("#fullprofbutton").show();
}
    } );
 
    wixData.query('BusinessDirectory_New')
    .ascending('businessName')
       .find()
       .then((results) => {
           $w("#repeater1").data = results.items;
       });
} );

function assignValidValue(element, elementProperty, elementValue) {
  console.log('assignValidValue('
                +(element && element['id']?element.id:',missing element') + ', '
                +(elementProperty?elementProperty:',missing property') + ', '
                +(elementValue?elementValue:',missing value') + ')');
 if (element && elementProperty && elementValue) {
      element[elementProperty] = elementValue;
 if (element.id === 'addline2' ||
          element.id === 'description' ||
          element.id === 'logo') {
          element.show();
      }
  } else if (element && elementProperty) {
 if (element.id === 'addline2' ||
          element.id === 'description' ||
          element.id === 'logo') {
          element.hide();
      }
  }  
}
function iTitle_keyPress(event, $w) {
   wixData.query('BusinessDirectory_New')
    .contains('businessName',$w('#iTitle').value)
    .find()    
    .then(res => {
          $w('#repeater1').data = res.items;
    });
 if (debounceTimer){
        clearTimeout (debounceTimer);
        debounceTimer = undefined;
    }
    debounceTimer = setTimeout(()=> {
       wixData.query('BusinessDirectory_New')
       .contains('businessName',$w('#iTitle').value)
       .find()    
       .then(res => {
           $w('#repeater1').data = res.items;
       });
    } , 500);
}
function ibusinessCategory1_change(event, $w) {
      wixData.query('BusinessDirectory_New')
   .contains('businessCategory1',$w('#ibusinessCategory1').value)
   .find()
  .then(res => {
     $w('#repeater1').data = res.items;
        });
}

Because I don’t want to take up more of your time and generosity. We can keep going with this, but if there is an easier way, for example using the function assignValidValue along with the attachments to the repeater that would be great!
Thank you for the continued help. This is much needed. I also didn’t think a debounce code could cause so much trouble. It is unfortunate that wix made it seem so simple, but did have the ability to mention empty fields would cause issues with the execution of this particular tutorial.

Thank you again.
Kindly,
Elizabeth

Hi Elizabeth:

The best way for me to solve this for you is to actually have access to the page the Editor at this point. The page link doesn’t really help me with context I need to connect the dots between how you are accessing your data from the data collection(s) and how you have named the elements.

When I tried to debug the page yesterday the breakpoints for your onChange function and onClick function were not working which also suggests that either the names of your elements do not tie up to the event handlers OR the binding in the element property is missing or incorrect.

If you want me to fix this once and for all send me add me as an Admin to your site (my contact info is in my profile) and I can fix the problem. Then I can update this post with the answer :slight_smile:

Cheers
Steve

Steve, Just Invited you. I’ll send the editor page link in another email.

1 Like

Did you and Steve ever solve this? If so, I’d love to see the completed code.

@jim32 Yes we did :-). @elizabethjhay was going to try to update this thread with the solution. If you need help with something feel free to ask!

Cheers