AJAX-enhanced knockout for throttling updates

I am in the process of developing an HTML form that dynamically updates its values based on the selection made in a dropdown menu. The structure of my view model is as follows:

function RoomViewModel() {
    var self = this;

    self.companyOptions = @Html.Raw({ ... });
    self.companyValue = ko.observable().extend({ rateLimit: 5000 });
    self.companyInfo = ko.observable();
    ko.computed(function() {
        if (self.companyValue()) {
            $.getJSON('@Html.GenerateActionLinkUrl("GetCompanyAndPlans")', {}, self.companyInfo);
        }
    });
}

ko.options.deferUpdates = true;
ko.applyBindings(new RoomViewModel());

My select dropdown is bound to companyValue, and when I change the selection multiple times, the computed feature only triggers after 5 seconds to display the currently selected value. While this nearly achieves what I intend, my concern arises when changing the dropdown for the first time - waiting 5 seconds seems unnecessary. The purpose behind the rate limiting is to prevent additional JSON requests before and after the initial change within the 5-second window. How can I modify the functionality to immediately send the JSON request and update upon the very first change?

Answer №1

var ratelimit = 0; // setting the initial rate limit to zero globally 

function RoomViewModel() {
    var self = this;

    self.companyOptions = @Html.Raw({ ... });

    if(ratelimit == 0){
      self.companyValue = ko.observable().extend({ rateLimit: ratelimit }); // update value instantly without delay for first request
      ratelimit = 5000; // set rate limit to 5 seconds for subsequent requests 
    } else { // proceeding with regular rate-limited change since rate limit is not 0 (for subsequent requests)
      self.companyValue = ko.observable().extend({ rateLimit: ratelimit }); // extending observable with current rate limit
    }

    self.companyInfo = ko.observable();
    ko.computed(function() {
        if (self.companyValue()) {
            $.getJSON('@Html.GenerateActionLinkUrl("GetCompanyAndPlans")', {}, self.companyInfo);
        }
    });
}

ko.options.deferUpdates = true;
ko.applyBindings(new RoomViewModel());

My approach involves using a global variable 'ratelimit' to determine whether it's the first request or not, adjusting the delay accordingly. Ideally, you should consider tracking the user's activity to dynamically adjust the rate limit based on their behavior rather than using a preset delay each time.

Answer №2

This problem piqued my interest, so I decided to explore it further. After some experimentation, I came to the conclusion that a custom extender is needed for this specific scenario. I managed to find one that mimics the functionality of rateLimit, and with a few modifications, it appears to meet your requirements.

By utilizing this solution, you can implement the following:

self.companyValue = ko.observable().extend({ customRateLimit: 5000 });

This approach ensures that the initial change takes effect immediately, while subsequent changes are subject to rate limiting.

Here is the demo

Below is the code snippet that you can run:

ko.extenders.customRateLimit = function(target, timeout) {
  var writeTimeoutInstance = null;
  var currentValue = target();
  var updateValueAgain = false;
  var interceptor;
  var isFirstTime = true

  if (ko.isComputed(target) && !ko.isWriteableObservable(target)) {
    interceptor = ko.observable().extend({
      customRateLimit: timeout
    });
    target.subscribe(interceptor);
    return interceptor;
  }

  return ko.dependentObservable({
    read: target,
    write: function(value) {
      var updateValue = function(value) {
        if (isFirstTime) {
          target(value);
          isFirstTime = false;
        } else {
          if (!writeTimeoutInstance) {
            writeTimeoutInstance = setTimeout(function() {
              writeTimeoutInstance = null;
              if (updateValueAgain) {
                updateValueAgain = false;
                updateValue(currentValue);
              }
              target(value);
            }, timeout);
          }
        }
      }
      currentValue = value;
      if (!writeTimeoutInstance)
        updateValue(currentValue);
      else
        updateValueAgain = true;
    }
  });
}

function AppViewModel() {
  this.text = ko.observable().extend({
    customRateLimit: 1000
  });
  this.rateLimited = ko.computed(this.text).extend({
    customRateLimit: 1000
  });
}

ko.applyBindings(new AppViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-debug.js"></script>

<h4>Change value is default so move the focus out of the input to change values.</h4>

<div>
  Enter Text: <input type='text' data-bind='value: text' />
</div>
<div>
  Rete Limited <small>(after the first change)</small>: <input type='text' data-bind='value: text' />
</div>
<div>
  Rete Limited Computed <small>(after the first change)</small>: <input type='text' data-bind='value: rateLimited' />
</div>

After entering text in the first input, observe how the change propagates instantly to other inputs. However, any subsequent changes following the initial one are delayed as per the rate limit.

This method allows you to extend both observables and computed observables seamlessly.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Issue encountered while trying to implement a recursive function for mapping through nested elements was not producing the

I am currently working on recursively mapping through an array of nested objects, where each object can potentially contain the same type of objects nested within them. For example: type TOption = { id: string; name: string; options?: TOption; } con ...

Expanding a div container to accommodate its inner divs using CSS

I have a div containing three inner divs. ONE - I am div TWO - I am div THREE - I am div When viewed on mobile, only two divs can fit in a row horizontally. I want the third div to move down. Like this: ONE - I am div TWO - I am div THREE - ...

Encountered an error when trying to retrieve JSON string response in AJAX using jQuery due to inability to utilize the 'in' operator

I am facing an issue with creating and fetching a JSON array in Laravel. While I am able to create the JSON array successfully, I encounter problems when trying to fetch it using AJAX jQuery. I am struggling to fetch the key-value pairs from the array. Be ...

Modifying the order of Vuetify CSS in webpack build process

While developing a web app using Vue (3.1.3) and Vuetify (1.3.8), everything appeared to be working fine initially. However, when I proceeded with the production build, I noticed that Vue was somehow changing the order of CSS. The issue specifically revol ...

Switch out multiline text with javascript

Could someone assist me with this question? I am attempting to locate and replace specific code within a JavaScript file. The code is included in an AJAX response that contains a significant amount of HTML code. After retrieving the AJAX response, I stor ...

Executing a Javascript function through Typescript in an Ionic application

I integrated a plugin into my ionic project, which includes both Java and JS code: cordova.define("cordova-sms-plugin.Sms", function(require, exports, module) { 'use strict'; var exec = require('cordova/exec'); var sms = {}; functio ...

Error encountered when referencing iscrollview and iscroll js

Greetings! I am new to the world of JavaScript and jQuery, currently working on developing a phonegap application. As part of my project, I am exploring the implementation of the pull-to-refresh feature using iscroll and iscrollview as demonstrated on & ...

When a VueJS button is located within a div that also contains a link, clicking on

Can someone help me with my HTML issue? <a href="/someplace"> <div> <vuecomp></vuecomp> <span>Click row for more info</span> </div> </a> Following is the Vue component I am working on... ...

What is the best way to sequentially read various sections of a file in vue.js?

I am currently working on a browser tool that analyzes a large file and provides statistics based on its content. The tool randomly selects k parts of the file for processing, treating each part individually. As each part is processed, an object is update ...

Tracking the Height of Hidden Elements During Page Resizing

I am currently attempting to determine the height of a hidden element after the page has been resized. More details can be found at this link: The script is able to successfully log the height upon page load, but when the page is resized, it goes back to ...

updating a hyperlink attribute dynamically using jQuery ajax

Currently, I have a URL that I am passing to jQuery AJAX. <a href="/wishlist.php?sku=C5&amp;action=move&amp;qty=1" class="buttoncart black">Move To Wishlist</a>; However, when the request reaches the AJAX function, I want to change th ...

Using InnerHTML in Javascript within the Quasar/VueJS framework is unsupported

I am looking to dynamically create tables based on the items inside the 'counts' array below. The number of tables and their contents can change. Here is my divContainer, where the innerHTML will be appended: <div id="divContainer" style="pa ...

Persistent hover state remains on buttons following a click event

In my current project, I am dealing with a form that has two distinct states: editing and visible. When the user clicks on an icon to edit the form, two buttons appear at the bottom - one for saving changes and one for canceling. Upon clicking either of th ...

In the Redux framework, the reducer fails to identify the action object

I'm currently working on a React project using Redux. I've encountered an issue where my reducer is not recognizing the action type being sent to it, or even the action itself. The error message I am receiving is TypeError: Cannot read property & ...

Tips for integrating three-dimensional JSON models from ThreeJS into A-frame

Looking to incorporate some low poly models from into a ThreeJS JSON model in A-frame. This method is preferred due to its efficiency and smaller file size, resulting in faster loading times. However, there seems to be a lack of guidance on how to achieve ...

The third dropdown menu's options are determined by both the selections made in the first and second

I found this script on a different website and I am planning to incorporate it into my project. The goal is to display a list of pages in the third dropdown based on the selection of English and The Indian Express, or The Hindu. Here is the code: <sc ...

Modifying Line Colors in Angular-charts.js: A Step-by-Step Guide

I have different colors specified in my scope: $scope.colors = [{ // light grey backgroundColor: 'rgba(148,159,177,0.2)', pointBackgroundColor: 'rgba(148,159,177,1)', pointHoverBackgroundColor: 'rgba(148,1 ...

Creating a plug-in enables you to discover and journey through geographical information on a three-dimensional globe using a web browser

Recently, I completed a mini project utilizing the three.js library. Now, I am looking to develop a Javascript plugin that can efficiently load large 3D models and utilize a Javascript API on the client side similar to Google Earth Plugin for faster perfor ...

Ways to evaluate two objects and update the key identifier?

Compare the keys in the headerObject with the keys in dataObj and create a new object using the labels from headerObject. const headerObject = [{ label: 'Code', field_key: 'code' }, { label: 'Worked Accounts&apos ...