Using Google AppScript OAuth for multiple user accounts

I have a google script using the Oauth library for apps script, intended to integrate with the Twitch API. I have a working integration for testing with myself, but the build only stores one access token at a time in the Properties Service. How can I store multiple tokens as multiple people authorize the app integration? Or how could I access the token before being stored as a user property? Would not need to store any tokens if I could just run the API calls as soon as the token is received after the user authorization in Twitch. The AppScript application is intended to allow users to authorize the script and then collect their account info through the Twitch API. Any help would be greatly appreciated.

function run() {
  const service = twitchService_();
  
  if (service.hasAccess()) {
    const url = 'https://id.twitch.tv/oauth2/userinfo';
    const options = {
      "method": "GET",
      "headers": {
        "Authorization": `Bearer ${service.getAccessToken()}`,
        "Client-Id": `${CLIENT_ID}`,
        "Content-Type": "application/x-www-form-urlencoded"
      },
      "muteHttpExceptions": true,
      "followRedirects": true,
      "validateHttpsCertificates": true,
      "contentType": "application/x-www-form-urlencoded",
    }
    
    const response = UrlFetchApp.fetch(url, options);
    const result = JSON.parse(response.getContentText());
    Logger.log(result);
  } else {
    const authorizationUrl = service.getAuthorizationUrl();
    Logger.log('Open the following URL and re-run the script: %s',
      authorizationUrl);
  }
}

/**
 * Reset the authorization state, so that it can be re-tested.
 */
function reset() {
  twitchService_().reset();
}

/**
 * Configures the service.
 */
function twitchService_() {
  return OAuth2.createService('Twitch')
    // Set the endpoint URLs.
    .setAuthorizationBaseUrl('https://id.twitch.tv/oauth2/authorize')
    .setTokenUrl('https://id.twitch.tv/oauth2/token')

    // Set the client ID and secret.
    .setClientId(CLIENT_ID)
    .setClientSecret(CLIENT_SECRET)
    .setScope(["user:read:email"])


    // Set the name of the callback function that should be invoked to
    // complete the OAuth flow.
    .setCallbackFunction('authCallback')
    
    // Set the property store where authorized tokens should be persisted.
    .setPropertyStore(PropertiesService.getUserProperties())

    .setTokenHeaders({
      'Authorization': 'Basic ' +
        Utilities.base64Encode(CLIENT_ID + ':' + CLIENT_SECRET)
    });
}

/**
 * Handles the OAuth callback.
 */
function authCallback(request) {
  const service = twitchService_();
  const authorized = service.handleCallback(request);
  if (authorized) {
    return HtmlService.createHtmlOutput(`Success`);
  } else {
    return HtmlService.createHtmlOutput('Denied.');
  }
  
}

/**
 * Logs the redict URI to register.
 */
function logRedirectUri() {
  Logger.log(OAuth2.getRedirectUri());
}

The Oauth only stores one access token at a time. How to address multiple users using the Auth for the App?

Unset preventDefault() for element in event propagation [duplicate]

We are using eventPropagation for links on our page since we are updating and adding page content via hydration. Each link has a default href for folks with javascript disabled (and Lord Google).

We call preventDefault() at the start, rather than for each event.

We have encountered a situation with one link, that we want the default action to happen when accessing the website on a phone browser. While we could manually redirect using open() or location.href, I am wondering if there is a way to simply restore the default click thru action for this single situation?

$('mstrwrap').addEventListener('click', function(e) {
  // set eventlisteners for all A HREF links
  if (e.target.nodeName === 'A') {

    e.preventDefault();

    // lots of if/else if's

    // reveal booking
    else if (e.target.id.includes('ftrbook')) {
      if (phoneSize.matches) {
        // disable preventDefault to allow default link
      } else {
        ftrBookLink();
      }
    }

    // lots more else if's
  }
}

How do I use `window.external.Tampermonkey.openOptions`?

I am doing some research on how Greasyfork detect whether a script is installed or not. After doing some debugging and searching, I noticed that Greasyfork can access certain API via window.external.Tampermonkey, including:

  • getVersion: (<callback>) => undefined, gets Tampermonkey version info
  • isInstalled: (<script name>, <namespace>, <callback>) => undefined, check if a script is installed and its version
  • openOptions: (<param1>, <param2>) => ?, open option page (options.html)?

References:

  1. https://github.com/Tampermonkey/tampermonkey/issues/322#issuecomment-252505959
  2. https://github.com/erosman/support/issues/582#issuecomment-1658350841

The first two methods are simple enough for me to work out their usage, but I can’t find information on the usage of the third one. And the obfuscated code surely is not friendly for debugging. My test indicated that you can pass hash as the first parameter, and Tampermonkey would open that page successfully. For example:

window.external.Tampermonkey.openOptions("nav=help"); // * Tampermonkey opens the help page.
window.external.Tampermonkey.openOptions("nav=<script id>+settings"); // * Tampermonkey opens the script settings page

But I don’t know what the second parameter is. And is it possible to pass script name and namespace as parameters to open that script’s settings page?

Add Column In A Specific Part of the Array in JS/React

In my case, how to add this newProductColumn to a specific part of the column?
It should be placed 3rd last of the column.

Parent component

import listData from "./listData";

const isEnabled = true;

const newProductColumn = {
  name: "newProduct", 
  header: "New product?",
  isDefaultColumn: true,
  component: () => {
    return <div>Yes</div>;
  },
};


const updatedListData = isEnabled
  ? {
      ...listData,
      columns: [
        ...listData.columns.slice(0, -2), // All existing columns except the last two
        newProductColumn,
        ...listData.columns.slice(-2), // Add back the last two columns
      ],
    }
  : listData;


 <ReusableList listData={updatedListData}/>

Another component

const listData = {
  otherFragments: `
    id
  `,
  columns: [
    {
      name: "created_at",
      isDefaultColumn: true,
      header: "Date created",
      component: ({ row: date, props }) => {
        return (
          <>
            <div>
              {date}
            </div>
          </>
        );
      },
    },
  ],
};

export default listData

How to remove unwanted elements in JavaScript object array and create a new array [duplicate]

I have a JavaScript object array which has these attributes:

[
  {
    active: true
    conditionText: "Really try not to die. We cannot afford to lose people"
    conditionType: "CONDITION"
    id: 12
    identifier: "A1"
    superseded: false
    themeNames: "Star Wars"
    type here
  },
  {
    active: true
    conditionText: "Really try not to die. We cannot afford to lose people"
    conditionType: "CONDITION"
    id: 12
    identifier: "A1"
    superseded: false
    themeNames: "Star Wars"
    type here
  }
]

I want to get only 3 attributes from there (active, condtionText, id) and create a new array.

I tried filter method and but couldn’t get the result.
Any help regarding this highly appreciated.

Shopify: Select location and show my stocks based on warehouse/store

Good Day,

Asking for a help how can I pull out the inventory on my Shopify store based on my warehouse stocks.

My current progress is I created a Popup to select delivery location and get stocks on specific warehouse on Shopify

Here’s my store link for the demo:
https://t-dev-store.myshopify.com/
password: 12345

Here’s my current code for the Popup select

//CUSTOM JS
$(document).ready(function() {
    $('body').on('click', '[name="checkout"], [name="goto_pp"], [name="goto_gc"]', function() {
      if ($('#agree').is(':checked')) {
        $(this).submit();
      }
      else {
        alert("You must agree with the terms and conditions of sales to check out.");
        return false;
      }
    });
});
  // Get the modal
  var locationModal      = document.getElementById("location-myModal");
  // Set cookies
  var locationToday      = new Date();
  var locationExpiry     = new Date(locationToday.getTime() + 30 * 24 * 3600 * 1000); // plus 30 days

  // hide modal
  locationModal.style.display  = "none";

  function locationSetCookie(name, value){
    document.cookie=name + "=" + escape(value) + "; path=/; expires=" + locationExpiry.toGMTString();
  }

  function userLocationPickup(form){
    // locationSetCookie("userLocationPickup", form.value)
    locationSetCookie("userLocationPickup", form.elements[0].value)
    // locationSetCookie("userLocationPickup", document.getElementById('user-location-modal-store').value)

    closeModal(document.getElementById('confirmation-myModal'));

    for( var i=0; i<form.elements.length; i++ ) {
      console.log("balyu: "+form.elements[i].value);
    }

    // console.log("balyu: "+form);

    // return false;
  }
  function userDeliverPickup(value, pickup=""){
    locationSetCookie("userDeliverPickup", value)
    locationSetCookie("pickupLocation", pickup)
  }

  // Get cookies
  function getCookie(cname) {
    var name = cname + "=";
    //Error occured on non UTF-8 URL, patched needed
    var decodedCookie = decodeURIComponent(unescape(unescape(document.cookie)));
    var ca = decodedCookie.split(';');
    for(var i = 0; i <ca.length; i++) {
      var c = ca[i];
      while (c.charAt(0) == ' ') {
        c = c.substring(1);
      }
      if (c.indexOf(name) == 0) {
        return c.substring(name.length, c.length);
      }
    }
    return "";
  }

  function deleteCookie( name, value='') {
    if( getCookie( name ) ) {
     var locationExpiry     = new Date(locationToday.getTime() - 30 * 24 * 3600 * 1000); // minus 30 days
     document.cookie=name + "=" + escape(value) + "; path=/; expires=" + locationExpiry.toGMTString();
    }
  }

  // put cookies
  function locationPutCookie(userLocation, whichis){
    var clear_success = false;
    var optionValue='';
    var selBrgyModal = document.getElementById('user-location-modal-barangay');
    var selBrgy = document.getElementById('user-location-barangay');

    if(userLocation.id == 'user-location-modal-store' || userLocation.id == 'user-location-modal-store2') {
      var slugs = userLocation.value;
      console.log("slugs====> "+slugs);
        locationSetCookie("userLocationPickup", slugs)
        locationSetCookie('userLocation', slugs);
        optionValue = slugs;
        if(slugs == "pasig-bagong-ilog") {
          locationSetCookie('userLocationCity', 'pasig');
        }
    }

    if(userLocation.id == 'user-location')
      optionValue = userLocation.value + "-" + selBrgy.value;
    else
      optionValue = userLocation.value + "-" + selBrgyModal.value;

    var locationCookie = getCookie("userLocation");
    console.log(locationCookie);
    if (locationCookie != '' && locationCookie != optionValue){
      $.ajax({
        url: '/cart/clear.js',
        method: 'POST',
        async: false,
        dataType: 'json',
        success: function(){
          clear_success = true;
          //alert("Your cart has been cleared due to change in location");
          console.log("Your cart has been cleared due to change in location.");
        },
        error: function(x, status, error) {
          clear_success = false;
          //alert(error);
          console.log(error);
        }
      });
    }

    if(whichis == 0)
    {
      clear_success = true;
    }

    if(clear_success == true){
      locationSetCookie("userLocation", optionValue);
      locationSetCookie("userLocationCity", userLocation.value);

      if(userLocation.id == 'user-location-modal-store' || userLocation.id == 'user-location-modal-store2') {
        var slugs = userLocation.value;
        locationSetCookie('userLocation', slugs);
          if(slugs == "pasig-bagong-ilog") {
            locationSetCookie('userLocationCity', 'pasig');
          }
          if(slugs == "cebu-city-carreta") {
            locationSetCookie('userLocationCity', 'cebu-city');
          }
          if(slugs == "mandurriao-bakhaw") {
            locationSetCookie('userLocationCity', 'mandurriao');
          }
          if(slugs == "baguio-city-ambiong") {
            locationSetCookie('userLocationCity', 'baguio-city');
          }
          if(slugs == "marikina-barangka") {
            locationSetCookie('userLocationCity', 'marikina');
          }

          if(slugs == "makati-comembo") {
            locationSetCookie('userLocationCity', 'makati');
          }
          if(slugs == "parañaque-baclaran") {
            locationSetCookie('userLocationCity', 'parañaque');
          }
          if(slugs == "antipolo-dalig") {
            locationSetCookie('userLocationCity', 'antipolo');
          }
          if(slugs == "quezon-city-alicia") {
            locationSetCookie('userLocationCity', 'quezon-city');
          }
          if(slugs == "pasig-dela-paz") {
            locationSetCookie('userLocationCity', 'pasig');
          }
      }

      // if(userLocation.id == 'user-location') {
        locationSetCookie("userLocationBrgy", selBrgy.value);
      // }
       // <the brgy>
        if(userLocation.id == 'user-location-modal-store' || userLocation.id == 'user-location-modal-store2') {
        var slug = userLocation.value.trim().split('-');
        if(slug.length == 3) {
          var theBrgy = userLocation.value.trim().split('-')
          var theBrgyVal = theBrgy[2]
          locationSetCookie('userLocationBrgy',  theBrgyVal)
        }else if(slug.length == 2) {
          var theBrgy = userLocation.value.trim().split('-')
          var theBrgyVal = theBrgy[1]
          locationSetCookie('userLocationBrgy',  theBrgyVal)
        }

      }
      else {
        locationSetCookie("userLocationBrgy", selBrgyModal.value);
      }

      // When the user selects a location, close the modal
      locationModal.style.display = "none";

      var checkpathname = location.pathname.split('/');

      if(!checkpathname.includes('products')){
        if(!checkpathname.includes('search'))
        {
          localStorage.setItem("store", mapUserLocationToStoreTag());
          location.replace(location.pathname + "?_=pf&tag=" + encodeURIParamValue(mapUserLocationToStoreTag()));
        }
        else
        {
          var searchUrl   = location.href.substring(0, location.href.lastIndexOf('=') + 1);
          localStorage.setItem("store", mapUserLocationToStoreTag());
          location.replace(searchUrl + encodeURIParamValue(mapUserLocationToStoreTag()));
        }
      }
      else
      {
        var productUrl   = location.pathname.substring(location.pathname.lastIndexOf('/') + 1);
        var directorylink   = location.pathname.substring(0, location.pathname.lastIndexOf('/') + 1);
        if(productUrl.substring(productUrl.lastIndexOf('-') + 1) == 'bgc')
          var baseproductURL =  productUrl.slice(0, -19);
        else if(productUrl.substring(productUrl.lastIndexOf('-') + 1) == 'makati')
          var baseproductURL =  productUrl.slice(0, -20);
        else if(productUrl.substring(productUrl.lastIndexOf('-') + 1) == 'rizal')
          var baseproductURL =  productUrl.slice(0, -20);
        else if(productUrl.substring(productUrl.lastIndexOf('-') + 1) == 'shangrila')
          var baseproductURL =  productUrl.slice(0, -20);
        else if(productUrl.substring(productUrl.lastIndexOf('-') + 1) == 'rockwell')
          var baseproductURL =  productUrl.slice(0, -20);
        else if(productUrl.substring(productUrl.lastIndexOf('-') + 1) == 'katipunan')
          var baseproductURL =  productUrl.slice(0, -20);
        else if(productUrl.substring(productUrl.lastIndexOf('-') + 1) == 'morato')
          var baseproductURL =  productUrl.slice(0, -20);
        else if(productUrl.substring(productUrl.lastIndexOf('-') + 1) == 'libis')
          var baseproductURL =  productUrl.slice(0, -20);
 
          var baseproductURL =  productUrl.slice(0, -20);
        else
          var baseproductURL =  productUrl;
        if(bgc.includes(optionValue))
        {
        location.replace(directorylink + baseproductURL + '-tmp-central-square-bgc');
        }
        else if(century.includes(optionValue))
            location.replace(directorylink + baseproductURL + '-tmp-century-mall-makati');
        else if(rizal.includes(optionValue))

        }
    }
    else
    {
        //When the user selects a location, close the modal
      locationModal.style.display = "none";
        //reload page after changing location
    //  location.reload();
    }
  }

  // check cookies
  function checkLocationCookies() {
    var userLocation = getCookie("userLocation");
    var userLocationCity = getCookie("userLocationCity");
    var userLocationBrgy = getCookie("userLocationBrgy");

    //userLocationBrgy="";
    //userLocationCity="";

    // START REGION MODAL
    var userRegion = mapUserDistrict(userLocationCity).replace(/s+/g, '-').toLowerCase()
    var regionSel  = document.getElementById('user-location-region')
    var regionOpts = regionSel.options;

    for (var opt, j = 0; opt = regionOpts[j]; j++) {
      if (opt.value == userRegion) {
        regionSel.selectedIndex = j;
        break;
      }
    }

    if ((userLocation != "") && (userLocationCity != "") && (userLocationBrgy != ""))
    {
      var uRegionCity = mapUserRegion(userLocationCity);
      var uDistrictCity = mapUserDistrict(userLocationCity);
      var dropdownCity = $('#user-location');

      dropdownCity.empty();

      dropdownCity.prop('selectedIndex', 0);

      // Populate dropdown with list of cities
      $.getJSON('//cdn.shopify.com/s/files/1/0500/8421/6991/t/28/assets/barangaylist.json?v=184164207319321888941677587334', function (data) {
        var objList = (data[uRegionCity].province_list[uDistrictCity].municipality_list);
        $.each(objList, function (key) {
            dropdownCity.append($('<option></option>').attr('value', key.replace(/s+/g, '-').toLowerCase()).text(key));
        })

        var opts = document.getElementById('user-location').options

        for (var opt, j = 0; opt = opts[j]; j++) {
          if (opt.value == userLocationCity) {
            dropdownCity.prop('selectedIndex', j);
            break;
          }
        }
      });

    // END REGION MODAL



      // hide modal
      locationModal.style.display  = "none";

      var uRegion = mapUserRegion(userLocationCity);
      var uDistrict = mapUserDistrict(userLocationCity);
      var uCity = mapUserCity(userLocationCity);

      let dropdown = $('#user-location-barangay');;

      dropdown.empty();

      dropdown.prop('selectedIndex', 0);

      // Populate dropdown with list of barangays
      $.getJSON('//cdn.shopify.com/s/files/1/0500/8421/6991/t/28/assets/barangaylist.json?v=184164207319321888941677587334', function (data) {

        var objList = (data[uRegion].province_list[uDistrict].municipality_list[uCity].barangay_list);

        $.each(objList, function (key, entry) {
          dropdown.append($('<option></option>').attr('value', entry.replace(/s+/g, '-').toLowerCase()).text(entry));
        })

        var selbrgy = document.getElementById('user-location-barangay');
        var optsbrgy = selbrgy.options;

        for (var opt, j = 0; opt = optsbrgy[j]; j++) {
          if (opt.value == userLocationBrgy) {
            selbrgy.selectedIndex = j;
            break;
          }
        }
        const selectbrgy = document.getElementById("user-location-barangay");

        const selectedbrgyIndex = selectbrgy.selectedIndex;
        const selectedbrgyText = selectbrgy.options[selectedbrgyIndex].text;

        const selectcity = document.getElementById("user-location");

        const selectedcityIndex = selectcity.selectedIndex;
        const selectedcityText = selectcity.options[selectedcityIndex].text;

        var location_text = " Delivery location: " + selectedbrgyText + ", " + selectedcityText;

        // <for pickup label>
        var isDelivery = getCookie('userDeliverPickup');

        if(isDelivery == 'pickup') {
          var pickupLocation = getCookie('userLocationPickup');
          var theLocationPickup = '';

          if(pickupLocation == "pasig-bagong-ilog") {
            theLocationPickup = 'TMP AYALA THE 30TH PASIG';
          }


          $("#pku").val("pickup for "+theLocationPickup);
<!-- Select another location -->
<div id="location-selector-container" class="user-location-container location-selector-container">
  <i class="material-icons">location_on</i><span id="loc-text"> Delivery location: Resolving location...</span>
  <form style="display: inline;">
    <input hidden type="button" class="button-primary" value="Change Location" onclick="openModal(document.getElementById('confirmation-myModal'));">
  </form>
</div>

<!-- The Modal -->
<div id="location-myModal" class="location-modal">
  <!-- Modal content -->
  <div class="location-modal-content -delivery">

    <h4 class="modal-select-title">Select</h4>
    <div class="modal-select-buttons">
      <button class="modal-select-btn-del button-primary">Delivery</button>
      <button class="modal-select-btn-pick button-secondary">Pickup</button>
    </div>

    <h5>Please select delivery area</h5>
    <form>
<!--       START REGION MODAL -->
      <p style="text-align:left;margin-bottom: 0px;font-family:latio;"> Region or Province:</p>
      <select style = "font-family:lato;" class="" id="user-location-modal-region" name='user-location-region' onchange="getCityListModal(this)">
        <option disabled selected value style="color: #D3D3D3;">Select Region or Province...</option>
        <option value="benguet">BENGUET</option>
        <option value="cavite">CAVITE</option>
        <option value="cebu">CEBU</option>
        <option value="iloilo">ILOILO</option>
        <option value="laguna">LAGUNA</option>
        <option value="misamis-oriental">MISAMIS ORIENTAL</option>
        <option value="metro-manila">METRO MANILA</option>
        <option value="rizal">RIZAL</option>
      </select>

      <p style="text-align:left;margin-bottom: 0px;font-family:latio;"> City or Municipality:</p>
      <select style = "font-family:lato;" class="" id="user-location-modal" name='user-location' onchange="getBrgyListModal(this)">
        <option disabled selected value style="color: #D3D3D3;">Select City or Municipality...</option>
      </select>

      <!--       END REGION MODAL -->

      <p style="text-align:left;margin-bottom: 0px;font-family:latio;"> Barangay or District:</p>
      <select style = "font-family:lato;" class="" id="user-location-modal-barangay" name='user-location-barangay'>
        <option disabled selected value style="color: #D3D3D3;">Select Barangay or District...</option>
      </select>

      <small></small>

      <input disabled id="submit-modal" type="button" class="button-primary" value="Submit" onclick="locationPutCookie(document.getElementById('user-location-modal'), 0); userDeliverPickup('delivery');">
    </form>
  </div>

  <div class="location-modal-content -pickup -inactive">
    <h4 class="modal-select-title">Select</h4>
    <div class="modal-select-buttons">
      <button class="modal-select-btn-del button-secondary">Delivery</button>
      <button class="modal-select-btn-pick button-primary">Pickup</button>
    </div>
    <form>
      <p style="text-align:left;margin-bottom: 0px;font-family:latio;"> Select Store:</p>
      <select style = "font-family:lato;" class="" id="user-location-modal-store" name='user-location'>
        <option disabled selected value style="color: #D3D3D3;">Select Store...</option>
        <option value="cebu-city-carreta">CEBU - TMP GALLERIA CEBU</option>
        <option value="baguio-city-ambiong">BAGUIO CITY - TMP LEONARD WOOD BAGUIO</option>
        <option value="mandurriao-bakhaw">ILOILO - TMP FESTIVE WALK ILOILO</option>
        <option value="antipolo-dalig">RIZAL - TMP MILLE LUCE VC RIZAL</option>
        <option value="pasig-bagong-ilog">METRO MANILA - TMP AYALA 30TH</option>
        <option value="pasig-dela-paz">METRO MANILA - TMP EASTWOOD LIBIS</option>
        <option value="marikina-barangka">METRO MANILA - TMP KATIPUNAN</option>
        <option value="parañaque-baclaran">METRO MANILA - TMP MET LIVE PASAY</option>
        <option value="quezon-city-alicia">METRO MANILA - TMP TOMAS MORATO</option>
        <option value="makati-comembo">METRO MANILA - TMP VENICE GRAND MCKINLEY</option>
      </select>
      <small>Important Notice: Changing your pickup store will clear your cart. Product availability is different at each store so some items may not be in stock at the store you are selecting</small>

      <input type="button" class="button-secondary" value="Cancel" onclick="closeModal(document.getElementById('confirmation-myModal'));">
      <input id="submit-modal" type="button" class="button-primary" value="Confirm" onclick="userDeliverPickup('pickup', document.getElementById('user-location-modal-store')); locationPutCookie(document.getElementById('user-location-modal-store'), 0);">
    </form>
  </div>

</div>

<div style="display: none" id="confirmation-myModal" class="location-modal">
  <!-- Modal content -->
  <div class="location-modal-content -delivery">

    <h4 class="modal-select-title">Select</h4>
    <div class="modal-select-buttons">
      <button class="modal-select-btn-del button-primary">Delivery</button>
      <button class="modal-select-btn-pick button-secondary">Pickup</button>
    </div>


    <h5>Changing your delivery area will remove all current items in your cart. Do you want to proceed?</h5>
    <form>
      <!--       START REGION MODAL -->
      <p style="text-align:left;margin-bottom: 0px;font-family:latio;"> Region or Province:</p>
      <select style = "font-family:lato;" class="" id="user-location-region" name='user-location-region' onchange="getCityList(this)">
        <option disabled selected value style="color: #D3D3D3;">Select Region or Province...</option>
        <option value="benguet">BENGUET</option>
        <option value="cavite">CAVITE</option>
        <option value="cebu">CEBU</option>
        <option value="iloilo">ILOILO</option>
        <option value="laguna">LAGUNA</option>
        <option value="misamis-oriental">MISAMIS ORIENTAL</option>
        <option value="metro-manila">METRO MANILA</option>
        <option value="rizal">RIZAL</option>
      </select>

      <p style="text-align:left;margin-bottom: 0px;font-family:latio;"> City or Municipality:</p>
      <select style = "font-family:lato;" class="" id="user-location" name='user-location' onchange="getBrgyList(this)">
        <option disabled selected value style="color: #D3D3D3;">Select City or Municipality...</option>
      </select>

      <!--       END REGION MODAL -->

      <p style="text-align:left;margin-bottom: 0px;font-family:latio;"> Barangay or District:</p>
      <select style = "font-family:lato;" class="" id="user-location-barangay" name='user-location-barangay'>
        <option disabled selected value style="color: #D3D3D3;">Select Barangay or District...</option>
      </select>
      <small></small>
      <input type="button" class="button-secondary" value="Cancel" onclick="closeModal(document.getElementById('confirmation-myModal'));">
      <!-- <input id="submit-change" type="button" class="button-primary" value="Yes" onclick="locationPutCookie(document.getElementById('user-location'), 1); userDeliverPickup('delivery');"> -->
      <input id="submit-change" type="button" class="button-primary" value="Yes" onclick="locationPutCookie(document.getElementById('user-location'), 1); userDeliverPickup('delivery'); locationSetCookie('userLocationBrgy', document.getElementById('user-location-barangay').value);">
    </form>
  </div>

  <div class="location-modal-content -pickup -inactive">
    <h4 class="modal-select-title">Select</h4>
    <div class="modal-select-buttons">
      <button class="modal-select-btn-del button-secondary">Delivery</button>
      <button class="modal-select-btn-pick button-primary">Pickup</button>
    </div>
    <form>
      <p style="text-align:left;margin-bottom: 0px;font-family:latio;"> Select Store:</p>
      <select style = "font-family:lato;" class="" id="user-location-modal-store2" name='user-location'>
        <option disabled selected value style="color: #D3D3D3;">Select Store...</option>
        <option value="cebu-city-carreta">CEBU - TMP GALLERIA CEBU</option>
        <option value="baguio-city-ambiong">BAGUIO CITY - TMP LEONARD WOOD BAGUIO</option>
        <option value="mandurriao-bakhaw">ILOILO - TMP FESTIVE WALK ILOILO</option>
        <option value="antipolo-dalig">RIZAL - TMP MILLE LUCE VC RIZAL</option>
        <option value="pasig-bagong-ilog">METRO MANILA - TMP AYALA 30TH</option>
        <option value="pasig-dela-paz">METRO MANILA - TMP EASTWOOD LIBIS</option>
        <option value="marikina-barangka">METRO MANILA - TMP KATIPUNAN</option>
        <option value="parañaque-baclaran">METRO MANILA - TMP MET LIVE PASAY</option>
        <option value="quezon-city-alicia">METRO MANILA - TMP TOMAS MORATO</option>
        <option value="makati-comembo">METRO MANILA - TMP VENICE GRAND MCKINLEY</option>
      </select>
      <small>Important Notice: Changing your pickup store will clear your cart. Product availability is different at each store so some items may not be in stock at the store you are selecting</small>

      <input type="button" class="button-secondary" value="Cancel" onclick="closeModal(document.getElementById('confirmation-myModal'));">
      <input id="submit-modal" type="button" class="button-primary" value="Confirm" onclick="userDeliverPickup('pickup', document.getElementById('user-location-modal-store2')); locationPutCookie(document.getElementById('user-location-modal-store2'), 1);">
    </form>
  </div>

</div>

<div style="display: none" id="slot-myModal" class="location-modal">
  <!-- Modal content -->
  <div class="location-modal-content">
    <h5>There are no more available delivery dates, please try again tomorrow.</h5>
    <form>
      <input type="button" class="button-secondary" value="Ok" onclick="closeModal(document.getElementById('slot-myModal'));">
    </form>
  </div>
</div>

<input type="hidden" id="pku" name="properties[pku]">

Change mouse cursor when dragging in JavaScript

When drag something, a mouse cursor looks like this.

enter image description here

Is it possible to change the shape of the mouse cursor?

document.querySelector('.item').addEventListener('drag', function(event) {
  // I try to this in html or event.target but it didn't work.
  document.querySelector('html').style.cursor = url('something_I_want.jpg');
});

How to stringify an array with multiple layers of JSON and arrays?

I have an array in Javascript that has multiple JSON objects and arrays, some of which are inside other objects and arrays. How can I stringify the entire array?
Here’s what one item in the array looks like:

Chunk{
buffer: WebGLBuffer {}
faces: 830
generated: true
lazy: false
maxY: 41
minY: 0
optimized: true
populated: true
sections: Array(3)
0: Section{
arraySize: 4096
blocks: Int32Array(4096) [4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 17, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 18, 3, 3, 3, 3, 3, 3, 3, 18, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 22, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, …]
chunk: Chunk {x: -624, z: 496, maxY: 41, minY: 0, sections: Array(3), …}
faces: 0
hasVisibleBlocks: true
renderData: (103) [-1895234556, -1895103484, -1894186493, -1894055677, -1895166972, -1912011772, -1911880700, -1910963709, -1910832893, -1911944188, -1928788988, -1928657916, -1927740911, -1927610109, -1928721404, -1945566204, -1945435132, -1893137917, -1893007101, -1892023293, -1944518141, -1944387325, -1945498620, -1962343420, -1962212348, -1909915133, -1909784317, -1908800509, -1961295357, -1961164541, -1962275836, -1979120636, -1978989564, -1926692349, -1926561533, -1925577725, -1978072573, -1977941757, -1979053052, -1995897852, -1995766780, -1943469565, -1943338749, -1942354941, -1994849789, -1994718973, -1995830268, -2012675068, -2012543996, -1960246781, -1960115965, -1959132157, -2011627005, -2011496189, -2012607484, -2029452284, -2029321212, -1977023997, -1976893181, -1975909373, -2028404221, -2028273391, -2029384700, -2046229500, -2046098428, -1993801213, -1993670397, -1992686589, -2010578429, -2010447613, -2009463805, -2045181437, -2045050621, -2046161916, -2063006716, -2062875644, -2027355645, -2027224829, -2026241021, -2061958653, -2061827837, -2062939132, -2079783932, -2079652860, -2044132861, -2044002045, -2043018237, -2078735869, -2078605053, -2079716348, -2077613053, -2096561148, -2096430076, -2060910077, -2060779261, -2059795453, -2095513085, -2095382254, -2096493564, -2094398461, …]
renderLength: 103
size: 16
x: -624
y: 0
z: 496
}
1: Section {x: -624, y: 16, z: 496, size: 16, arraySize: 4096, …}
2: Section {x: -624, y: 32, z: 496, size: 16, arraySize: 4096, …}
}

infinite auto scroll not working on html and javascript

i have a json file where i store data for my frontend ecommerce site but i want the scrollable div to have an infinite auto scroll together with the button i used feature but i cant just get it right i have only four data from the json file how do i do it

the javascript

function scrollRight() {
  const scrollContainer = document.querySelector(".product-container");
  scrollContainer.scrollBy(300, 0);
}

function scrollLoop() {
  setInterval(scrollRight, 2000);
}

const productContainers = [...document.querySelectorAll(".product-container")];
const nxtBtn = [...document.querySelectorAll(".nxt-btn")];
const preBtn = [...document.querySelectorAll(".pre-btn")];

productContainers.forEach((item, i) => {
  let containerDimensions = item.getBoundingClientRect();
  let containerWidth = containerDimensions.width;

  nxtBtn[i].addEventListener('click', () => {
    item.scrollLeft += containerWidth;
  });

  preBtn[i].addEventListener('click', () => {
    item.scrollLeft -= containerWidth;
  });
});

// repeat auto scroll
const productsContainer = document.getElementById('product-container');
const productContainerScrollWidth = productsContainer.scrollWidth;

function isElementInViewport(el) {
  var rect = el.getBoundingClientRect();
  return rect.right > 0;
}

function autoScrollRepeat() {
  const first = document.querySelector('#product-container .product-card');

  if (!isElementInViewport(first)) {
    productsContainer.appendChild(first);
    productsContainer.scrollTo(productsContainer.scrollLeft - first.offsetWidth, 0);
  }

  if (productsContainer.scrollLeft === productContainerScrollWidth) {
    productsContainer.scrollTo(0, 0);
  } else {
    productsContainer.scrollTo(productsContainer.scrollLeft + 1, 0);
  }
}

// Call the functions after the DOM is loaded
document.addEventListener('DOMContentLoaded', () => {
  scrollLoop();
  setInterval(autoScrollRepeat, 15);
});

the html after the fetch

<section class="product">
        <h2 class="product-category">best selling</h2>
        <button class="pre-btn"><span> < </span></button>
        <button class="nxt-btn"><span> > </span></button>
        <div scrollLoop() autoScrollRepeat() class="product-container" id="product-container">
            <div class="product-card">
          <div class="product-image">
              <span class="discount-tag">50% off</span>
              <img src="${product.image}" alt="product image" class="product-thumb">
              <button class="card-btn">Add to wishlist</button>
          </div>
          <div class="product-info">
              <h2 class="product-brand">${product.brand}</h2>
              <p class="product-short-description">${product.description}</p>
              <span class="price">${product.newPrice}.00</span><span class="actual-price">${product.oldPrice}.00</span>
              <div class="product-review">
                <i class="font-13 fa fa-star"></i>
                <i class="font-13 fa fa-star"></i>
                <i class="font-13 fa fa-star"></i>
                <i class="font-13 fa fa-star-o"></i>
                <i class="font-13 fa fa-star-o"></i>
              </div>
              <ul class="swatches">
                <li class="swatch medium rounded"><img src="${product.image}" alt="image" /></li>
                <li class="swatch medium rounded"><img src="${product.image}" alt="image" /></li>
                <li class="swatch medium rounded"><img src="${product.image}" alt="image" /></li>
                <li class="swatch medium rounded"><img src="${product.image}" alt="image" /></li>
                <li class="swatch medium rounded"><img src="${product.image}" alt="image" /></li>
                <li class="swatch medium rounded"><img src="${product.image}" alt="image" /></li>
              </ul>
          </div>
         </div>
        </div>
    </section>

it was working fine with just the button and auto scroll but stopped scrolling when i try adding the repeat auto scroll function ,would be happy if anyone do this for me

How can I disable the bounce and page refresh on pulling down from the top of the screen on iOS?

I’m trying to develop a full-screen 3D web app and this is very undesirable behavior.

I’m using CSS and JavaScript.

There are already questions relating to this on StackOverflow but none of the solutions work – perhaps outdated?

I have tried
overscroll-behavior: none;
but it did nothing.

I have tried the techniques in this web page https://www.bram.us/2016/05/02/prevent-overscroll-bounce-in-ios-mobilesafari-pure-css/

They also did nothing.

Is it simply impossible?

change options of tabulator table dynamically

I am using tabulator in my Angular app to display some data. I have a parent component and a child component that displays 2 different tabulator tables using angular mat-tab. The tabulator tables are declared within the child component. I have an edit button on the parent component. Initially, the editMode is false but the on clicking the button, it is set to true.

Thus, initially while setting up the tabulator table I have set the movableRows and movableColumns options to false, however when the editMode is set to true I want to add a new column that serves as a row handle, the row context menus should be visible only in edit mode and also the rows and columns should become moveable.

Write now I am passing a property to the child component and here is the child component code-

ngOnChanges(simpleChanges: SimpleChanges): void {

    if (simpleChanges?.isEditMode) {

      if (this.tabulatorTable) {
        this.tabulatorTable.destroy();
      }
      this.movableRows = simpleChanges.isEditMode.currentValue;
      this.movableColumns = simpleChanges.isEditMode.currentValue;
      this.initializeTable(this.highlightRows, this.rowValidation); // this initailizes the tabulator table
    }
  }

The thing is the above code slows down the tabulator component significantly when I am in edit mode and switch tabs between the 2 components. Is there any other way I can achieve the desired functionality ?

How to get VSCode to suggest / autocomplete the React className, given my custom classes on a ?

I have a stylesheet with a bunch of classes. How can I get it to autocomplete in VSCode so that it autocompletes the className attribute on any div or native HTML element? Right now it’s not doing anything.

I have styles defined in styles/base.css like this:

.border-solid {
  border-style: solid;
}

.border-dotted {
  border-style: dotted;
}

.border-width-1 {
  border-width: 1px;
}

.border-width-2 {
  border-width: 2px;
}

.border-width-4 {
  border-width: 4px;
}

I am in a Next.js project with VSCode settings.json like this:

{
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "editor.formatOnSave": true,
  "eslint.format.enable": true,
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
  },
  "eslint.validate": ["javascript", "typescript"],
  "eslint.nodePath": "./node_modules/eslint",
  "emmet.showExpandedAbbreviation": "never"
}

Is there any possible way to wire it up so it can somehow pick up my class names and autocomplete them? If I need to duplicate my class names and put them somewhere else to get autocomplete working, I am fine with that too.

If it requires a lot of work (i.e. too much for an SO answer), if you could outline the key things that I would have to do that would be great.

Ideally it would show me a TypeScript error if I use an incorrect or undefined class name.

EJS JavaScript SPA

I’m wanting to build a single-page application using NodeJS and plain JavaScript so that it replicates what you can do with C# MVC and Kendo-UI’s router.

I have the following on my index.ejs:

<div id="views-users" hidden="hidden">
    <%- include('_search.ejs') %>
    <%- include('_create.ejs') %>
    <%- include('_update.ejs') %>
</div>

<div class="container-fluid">
    <h2><%= PageTitle %></h2>
    <div id="layout-users"></div>
</div>

<script>
    const router = {
        routes: {
            '#': () => {
                router.showIn('#view-users-search');
                modelUsersSearch.show();
            },
            '#/create': () => {
                router.showIn('#view-users-create');
                modelUsersCreate.show();
            },
            '#/update/:id': (id) => {
                modelUsersUpdate.id = id;
                router.showIn('#view-users-update');
                modelUsersUpdate.show();
            }
        },

        routeToRegex: route => {
            const escapedRoute = route.replace(/[.*+?^${}()|[]\]/g, '\$&');
            const regexRoute = escapedRoute.replace(/:[a-zA-Z0-9_]+/g, '([^/]+)');
            const finalRegex = `^${regexRoute}$`;

            return new RegExp(finalRegex);
        },
        showIn: viewSelector => {
            const layout = document.querySelector('#layout-users');
            const viewsContainer = document.querySelector('#views-users');
            const view = document.querySelector(viewSelector);
            if (layout.childNodes.length) {
                viewsContainer.append(...layout.childNodes);
            }
            layout.append(...view.childNodes);
        },

        change: () => {
            let hash = window.location.hash;
            if (!hash) {
                hash = '#';
            }
            if (hash === '#/') {
                hash = '#';
            }

            const routes = Object.keys(router.routes);
            const matchedRoute = routes.find(route => {
                const regexPattern = router.routeToRegex(route);
                const routeMatched = regexPattern.test(hash);
                return routeMatched;
            });
            if (!matchedRoute) {
                throw new Error(`Invalid route: ${hash}`);
            }

            const action = router.routes[matchedRoute];
            action();
        }
    };

    // routing events
    window.addEventListener('hashchange', function() {
        router.change();
    });
    window.addEventListener('DOMContentLoaded', function(ev) {
        router.change();
    });
</script>

And the following in one of my “partials” (_search.ejs):

<div id="view-users-search">
    <h2 class="h4">Search Users</h2>
    <div id="table-users"></div>
</div>

<script>
    const modelUsersSearch = {
        hasInit: false,

        init: () => {
            if (modelUsersSearch.hasInit) {
                return;
            }
            modelUsersSearch.hasInit = true;

            const datatable = utilities.initializeDataTable('#table-users', {
                columns: [
                    {
                        select: 3,
                        type: 'date',
                        format: 'YYYY/DD/MM hh:mm:ss',
                        sort: 'desc'
                    }
                ],
                data: {
                    headings: ['Full Name', 'Username', 'Email', 'Created', 'Status'],
                    data: [
                        [
                            // data retracted
                        ],
                        [
                            // data retracted
                        ],
                        [
                            // data retracted
                        ]
                    ]
                }
            });
        },
        show: () => {
            modelUsersSearch.init();
        }
    };
</script>

This works great on initial load, but if I navigate to a separate SPA route and then back to the search SPA route, the DOM in my layout is empty which leads me to believe that I’m losing my DOM when moving it from the layout to the hidden container but I cannot figure out why.

Uncaught (in promise) TypeError: data.forEach is not a function at index.html:25:18 [duplicate]

try {
  async function getPlanets() {
    let response = await fetch('https://swapi.py4e.com/api/planets/');
    let data = await response.json()
    return data;

  }
} catch (error) {
  error = console.log(error);
}
getPlanets().then(data => {
  data.forEach(planet => {
    const markup = `<li>${planet.name}</li>`;
    document.querySelector('ul').insertAdjacentHTML('beforeend', markup);
  });
});

the above code is throwing the error shown in the title, ive done some googling and I honestly just think ive been staring at this too long and need a second opinion, any thoughts on why it would do this? I kind of think its because the swapi is not returning an array so forEach isnt allowed, but if thats the case how do i get it to grab the array? because it is there i saw it in the console earlier its an array with 10 planets in it however its property is called “result” is this where I went wrong?

apologies if this was a bad question I am still new to this stuff and practical exercises seem to help BUT I BE STUCK ATM!

I tried looking through documentation, however I was following a short video on making an API call and then displaying that info to your webpage, but then it threw the error and at this point I feel like I’m just thinking about it the wrong way and its a simple fix.

I think it has something to do with the response being an object and not an array but again if that’s the case how do I grab the “result:” array so I can loop through it and display the planet name that’s the question at the very top