Handling special character input with JavaScript on Android Chrome

I’m designing a web app that features an editable HTML table. I want to allow the user to input a single value in the range 1-9 only. I want the user to be able to change the input as needed or remove it altogether with backspace. I’m trying to handle this with JavaScript using the onkeydown argument to the td tags. Here is the HTML with JavaScript that I’m using. On desktop this works great, and only allows a single value input, replaces the value when a new one is pressed, and allows the value to be removed using the backspace (keyCode === 8). However, on Android something very weird happens – the numeric keypad that comes up with inputmode=numeric contains a few special characters, namely ,, ., -, . For some reason these do not get picked up and handled in the same way as other inputs. Trying to debug this, I’ve found that the keyCode for all of these inputs is 229, and the key attribute is undefined. I’ve tried adding a handler for keyCode === 229 but this doesn’t stop these keys changing the input. I’ve also added a screenshot of the input when these special keys are pressed.

<head>   
    <meta charset="UTF-8">
</head>   
<body>   
</script>
    <style>
        table { border-collapse: collapse; font-family: Calibri, sans-serif; }
        td { border: solid thin; height: 2.3em; width: 2.3em; text-align: center; padding: 0; }
    </style>
    <table id="table" class="centre">
        <tbody>
        <tr> <td contentEditable inputmode=numeric onkeydown="return testCharacter(event);"></td>
    </table>
    <script>
        function testCharacter(evt) {
            var charCode = (evt.which) ? evt.which : evt.keyCode, self = evt.target;
            if ((evt.keyCode >= 49 && evt.keyCode <= 57)) {
                self.innerText = charCode - 48;
                return false;
            } else if (evt.keyCode === 8) {
                self.innerText = null;
                return false;
            } else {
                return false;
            };
        };
    </script>
</body>

Screenshot of input of special characters

Avoid hearing the carrier prompts while call rejection in twilio

I’m trying to achieve a use case in twilio, where the person A makes the call to person B without knowing the actual number of person B.

I’m using twiml.dial to make the call to person B, But in case when the person B rejects the call it goes straight away to the carrier’s voicemail and it reveals the identity of person B. I tried several approaches to prevent the control going to carrier but unfortunately couldn’t make it work. When the control goes to the carrier’s voicemail, the call status which I receive is the ringing which makes it impossible to catch.

I’m listing the approaches which I’ve tried so far.

Approaches

1 . Using normal twilio call twiml.dial

2 . Using conference call

2.a Used the approach where we created conference call with user A, and did separate call to User B and when the User B accepts the call, We're trying to merge it to conference. But this made the User A left the conference abruptly.

3 . Tried the AMD detection as well but there the requirement was that phone call needs to be answered first to get the proper response.

Can anyone please guide me on this, how to approach this problem

Thanks

Why isn’t my Audit Log event working with my Discord bot?

I am trying to get audit logs to work with my Discord bot. I’ve set up my bot according to the instructions on discordjs.guide. I am starting with trying to log when a message is deleted but it’s just not doing anything. The following will show my index.js file and my auditLog.js event file.

Here is index.js:

const fs = require('node:fs');
const path = require('node:path');
const { Client, Collection, GatewayIntentBits } = require('discord.js');
const { token } = require('./config.json');

const client = new Client({ 
    intents:[
        GatewayIntentBits.Guilds,
        GatewayIntentBits.GuildMessages,
        GatewayIntentBits.MessageContent,
        GatewayIntentBits.GuildMembers,
        GatewayIntentBits.GuildVoiceStates,
        GatewayIntentBits.GuildMessageReactions,
        GatewayIntentBits.DirectMessages,
        GatewayIntentBits.GuildMessageTyping,
        GatewayIntentBits.DirectMessageTyping
    ],
    partials:[Partials.Message, Partials.Channel, Partials.Reaction]
});

client.cooldowns = new Collection();
client.commands = new Collection();
const foldersPath = path.join(__dirname, 'commands');
const commandFolders = fs.readdirSync(foldersPath);

for (const folder of commandFolders) {
    const commandsPath = path.join(foldersPath, folder);
    const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js'));
    for (const file of commandFiles) {
        const filePath = path.join(commandsPath, file);
        const command = require(filePath);
        if ('data' in command && 'execute' in command) {
            client.commands.set(command.data.name, command);
        }
        else {
            console.log(`[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`);
        }
    }
}

const eventsPath = path.join(__dirname, 'events');
const eventFiles = fs.readdirSync(eventsPath).filter(file => file.endsWith('.js'));

for (const file of eventFiles) {
    const filePath = path.join(eventsPath, file);
    const event = require(filePath);
    if (event.once) {
        client.once(event.name, (...args) => event.execute(...args));
    }
    else {
        client.on(event.name, (...args) => event.execute(...args));
    }
}

client.login(token);

Here is auditLog.js

const { AuditLogEvent, Events } = require('discord.js');

module.exports = {
    name: Events.GuildAuditLogEntryCreate,
    async execute(auditLog) {
        const { action, channel, executorId, targetId } = auditLog;
        console.log("Audit log event triggered");

        if (action !== AuditLogEvent.MessageDelete) return;
        console.log(`Action: ${action}, ExecutorId: ${executorId}, TargetId: ${targetId}`);

        const executor = await client.users.fetch(executorId);
        const target = await client.users.fetch(targetId);

        console.log(`A message by ${target.tag} was deleted by ${executor.tag} in ${channel}.`);
    }
};

I have also tried adding the event directly in index.js to see if it would work like so:

const fs = require('node:fs');
const path = require('node:path');
const { Client, Collection, GatewayIntentBits, AuditLogEvent, Events } = require('discord.js');
const { token } = require('./config.json');

const client = new Client({ 
    intents:[
        GatewayIntentBits.Guilds,
        GatewayIntentBits.GuildMessages,
        GatewayIntentBits.MessageContent,
        GatewayIntentBits.GuildMembers,
        GatewayIntentBits.GuildVoiceStates,
        GatewayIntentBits.GuildMessageReactions,
        GatewayIntentBits.DirectMessages,
        GatewayIntentBits.GuildMessageTyping,
        GatewayIntentBits.DirectMessageTyping
    ],
    partials:[Partials.Message, Partials.Channel, Partials.Reaction]
});

client.cooldowns = new Collection();
client.commands = new Collection();
const foldersPath = path.join(__dirname, 'commands');
const commandFolders = fs.readdirSync(foldersPath);

for (const folder of commandFolders) {
    const commandsPath = path.join(foldersPath, folder);
    const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js'));
    for (const file of commandFiles) {
        const filePath = path.join(commandsPath, file);
        const command = require(filePath);
        if ('data' in command && 'execute' in command) {
            client.commands.set(command.data.name, command);
        }
        else {
            console.log(`[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`);
        }
    }
}

const eventsPath = path.join(__dirname, 'events');
const eventFiles = fs.readdirSync(eventsPath).filter(file => file.endsWith('.js'));

for (const file of eventFiles) {
    const filePath = path.join(eventsPath, file);
    const event = require(filePath);
    if (event.once) {
        client.once(event.name, (...args) => event.execute(...args));
    }
    else {
        client.on(event.name, (...args) => event.execute(...args));
    }
}

// Audit log event
client.on(Events.GuildAuditLogEntryCreate, async (auditLog) => {
    const { action, extra: channel, executorId, targetId } = auditLog;

    if (action !== AuditLogEvent.MessageDelete) return;

    const executor = await client.users.fetch(executorId);
    const target = await client.users.fetch(targetId);

    console.log(`A message by ${target.tag} was deleted by ${executor.tag} in ${channel}.`);
});

client.login(token);

As you can see I added debug logs console.log("Audit log event triggered"); and console.log(`Action: ${action}, ExecutorId: ${executorId}, TargetId: ${targetId}`); but these are not logging either. I’m not sure what I am doing wrong at this point. I do have the “View Audit Log” permission enabled on the bot, I’ve included a screenshot for reference. Granted Permissions

Add plot.ly to shopify blog

I created an interactive plot with plot.ly and want to integrate it into a shopify blog article.

What I would do:

  1. save the code into an .js in assets
window.PLOTLYENV=window.PLOTLYENV || {};                                    
if (document.getElementById("xxxxxxxxxxxxxxxx")) {                    
  Plotly.newPlot(                        
    "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",                        
    [{"hovertemplate":".......................................................
  1. link it in the blog html or in a liquid with
<script type="text/javascript" src="{{ 'myplot.js' | asset_url | tag_script }}"></script>

What am I missing?

Unable to Retrieve and Read .eml Attachments in Gmail Using Google Apps Script

`I am facing an issue while using Google Apps Script to automate the retrieval of .eml file attachments from specific emails in my Gmail inbox. My script is designed to search for emails with a specific subject line, log the messages, and identify any .eml attachments present. However, despite confirming that the emails contain .eml files, my script does not seem to locate or read them properly.

I have added logging statements throughout the script to monitor the messages and attachments being processed, but I am not receiving any output related to .eml attachments. This leads me to believe that there may be an issue with how I am accessing or filtering the attachments.

I need assistance in troubleshooting this issue to ensure that I can effectively retrieve and read the contents of the .eml attachments from the targeted emails.

function findSpecificEmail() {
  var threads = GmailApp.search('subject:"Your Email Subject Here"'); // Change the subject to match your email
  var messages = GmailApp.getMessagesForThreads(threads);
  
  for (var i = 0; i < messages.length; i++) {
    for (var j = 0; j < messages[i].length; j++) {
      var message = messages[i][j];
      var attachments = message.getAttachments();
      
      for (var k = 0; k < attachments.length; k++) {
        var attachment = attachments[k];
        Logger.log('Found attachment: ' + attachment.getName() + ' of type ' + attachment.getContentType());
        
        if (attachment.getName().endsWith('.eml')) {
          var emlContent = attachment.getDataAsString();
          Logger.log('EML Content: ' + emlContent);
        }
      }
    }
  }
}

Why aren’t these images displaying in an animated slider? [closed]

When using the Ollie wordpress theme (as this site currently is), these 4 images are displaying separately, rather than one after another as a slider, in the same location, as intended.

Furthermore, the shortcode that’s supposed to generate the slider appears as plain text. When I switch to the Twenty Twenty-Four theme, the slider operates correctly.

Here is the page in question (after switching back to the Ollie theme):

https://cometogethercreative.com/staging/home-test-9-30/

…and here is the page where i got the concept and the code:

https://www.wppagebuilders.com/create-image-slider-gutenberg/

(I’m attempting to use Method One: Creating an Image Slider Using a Cover Block)

Devtools shows the following: screen.js:1 Uncaught TypeError: Cannot read properties of null (reading 'getContext') at screen.js:1:754 at screen.js:1:4630

I assume this is connected with the issue, but I don’t know javascript. I’ve searched my install for “screen.js”, but zero results. ChatGPT suspects that a canvas element is connected, but I haven’t been able to get anywhere with that either.

Desired behavior: i’d like for the combination of the shortcode and the code block to generate an animated slider using the four images currently shown on the page

Specific problem: the code block is apparently not loading at all, and the shortcode is displaying as plain text on the page

Shortest code necessary to reproduce the problem: just visit the URL and load the page – the same error manifests using several different browsers

How to hide or remove an attribute on condition from an input tag in AngularJS

We are using AngularJS (version 1). How can I remove/hide an attribute from an input tag using condition?

<input type="number" ng-class="{'ng-invalid' : function(param)}"  min="1" max="5" ng-model=value custom-attribute="" key="{{anotherFunction(param)}}">

Here, there is a function call with parameter that checks if the input number is invalid and give true if its invalid. There is another function for key thats custom made that will create a yellow box. When its invalid, I dont have to show the yellow box. Even if null is there as value for key, that yellow box will come. So, I have to totally remove that attribute if its invalid. I have to totally remove key and custom-attribute when function(param) returns true. How to do this using angularJS

I have tried using ng-attr but have’nt got any luck.

Preload JS unable to be found when packagin an electron vue vite application

I’m trying to build a vue 3 app, compiled using vite and packaged using electron without any plugins. Here is what I have so far:

Folder structure:

Project
 |    
 +-- electron
 |  |  
 |  +-- main.js
 |  +-- preload.js
 |    
 +-- src
 |  |  
 |  +-- main.js
 |  +-- App.vue
 |    
 +-- package.json
 |    
 +-- vite.config.js

electron/main.js

import { app, BrowserWindow } from "electron";
import path from "path"
const __dirname = path.resolve();

const createWindow = () => {
    const mainWindow = new BrowserWindow({
        width: 800,
        height: 600,
        webPreferences: {
            contextIsolation: true,
            enableRemoteModule: false,
            preload: path.join(app.getAppPath(), 'preload.js'),
        },
    });

    if (app.isPackaged) {
        try {
            mainWindow.loadFile('app://./index.html').catch(e => console.log(e));
        }
        catch (e) {
            console.log(e);
        }
    }
    else {
        mainWindow.loadURL('http://localhost:3000');
    }
};

app.on('window-all-closed', () => {
    if (process.platform !== 'darwin') app.quit()
})

app.on('activate', () => {
    if (BrowserWindow.getAllWindows().length === 0) createWindow();
});

app.whenReady().then(createWindow);

preload.js

window.addEventListener('DOMContentLoaded', () => {
    console.log('Preload script loaded');
});

src/main.js

import { createApp } from 'vue'
import './style.css'
import App from './App.vue'

createApp(App).mount('#app')

package.json

{
  "name": "testme",
  "version": "0.0.1",
  "description": "demo app",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "electron:start": "electron electron/main.js",
    "start": "vite & electron electron/main.js",
    "buildElectron": "npx electron-builder build --mac"
  },
  "dependencies": {
    "vue": "^3.5.10"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^5.1.4",
    "electron": "^33.0.0-beta.8",
    "electron-builder": "^25.1.7",
    "vite": "^5.4.8"
  },
  "main": "electron/main.js",
  "type": "module",
  "build": {
    "appId": "com.ex.me",
    "productName": "TEST 1",
    "files": [
      "dist/**/*",
      "electron/**/*",
      "preload.js"
    ],
    "directories": {
      "buildResources": "assets"
    },
    "mac": {
      "target": [
        {
          "target": "zip",
          "arch": ["x64"]
        }
      ]
    }
  }
}

vite.config.js

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path';

export default defineConfig({
  plugins: [vue()],
  base: './',
  server: {
    port: 3000,
  },
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
    },
  },
  build: {
    outDir: 'dist',
    sourcemap: true,
    rollupOptions: {
      input: {
        main: 'src/main.js',
        preload: './electron/preload.js',
      },
      external: ['electron'],
    },
  },
});

The app works flawlessly when i run npm start. In other words vite & electron electron/main.js works just fine. But when i run buildElectron, it sucessfully builds the app, but I get two errors:

  1. Unable to load preload script: and Error: ENOENT, preload.js not found in /Users/xxx/Desktop/Projects/abc/new/dist/mac/Test 1.app/Contents/Resources/app.asar
  2. Not allowed to load local resource: file:///Users/xxx/Desktop/Projects/abc/…

So it seems that __dirname is causing some problems maybe? As preload.js is not being found. And there seems to be issues with locating or opening index.html when packaged. Anyone have any idea how to debug this? ive tried many many combinations of trying to make preload visible to no avail.

how do you handle a promise as JSON value after fetch?

I’m trying to fetch JSON data from a local asp.net core site, but where I expect the JSON to be it’s a promise with the state fulfilled, and I don’t know what to do with it.

When I debug I can see my value is in there, but if I try to get it from the JSON property I get undefined.

enter image description here

So the request in it self works I get no errors, I just don’t know how to get the result.

The FetchAsync function:

static FetchAsync = async (url, body = {}, method = "get", credentials = "same-origin", mode = "same-origin") => await fetch(url, {
    method: method,
    headers: {
        "Content-Type": "application/json",
        "Accept": "application/json"
    },
    credentials: credentials.toLowerCase(),
    mode: mode.toLowerCase(),
    body: body && Object.keys(body).length > 0 ? JSON.stringify(WebRequestUtility.ProcessBodyData(body)) : null
}).then(response => {
    if (response.ok) {
        return new WebRequestResult(response);
    } else {
        throw new Error("Request failed: " + response.status + ". Status: " + response.statusText + ". URL: " + response.url);
    }
}).catch(error => {
    throw new Error("Request error: " + error.statusText);
});

The WebRequestResult class:

class WebRequestResult {
    constructor(response) {
        this.Response = response;
        this.Success = response.ok;
        this.Status = response.status;
        this.StatusText = response.statusText;
        this.Type = response.type;
        this.JSON = response.json();
    }

    get obj() {
        return JSON.parse(this.JSON);
    }
}

DUCKDB – WASM Scalar UDF with Utf-8 return type not working as expected

In my project, I am utilizing duckdb wasm. I encountered a need for a custom user-defined function (UDF), so I created one with a return type of UTF-8. However, it consistently returns u0000.

 // Test udf
    function stringTest(a) {
      try {
        return a + "hello";
      }catch(ex){
        console.log(ex)
      }
    }

    conn.createScalarFunction('stringTest', new arrow.Utf8, stringTest);

Code for element print log

    const result = await conn.query(`SELECT * FROM '${tableName}'`);
        console.log('result: ', result);
        // Get columns and rows
        const columns = result.schema.fields.map(field => field.name);
        let rows = result.toArray();
      
        
        console.log('columns: ', columns);
        
      
        rows.forEach(item => console.log(item));

Image of dubugged value in browser
enter image description here

There are many use cases involving string return types. Can you please assist me with this problem?

How to covert binary data string in file? [closed]

I receveid this from the backend:
response of backend

I try convert the binary string with the code:

  const u8 = new Uint8Array(data.length);
                for (let i = 0; i < data.length; i++) {
                  u8[i] = data[i].charCodeAt(0);
                }

                const blob = new Blob([u8], {
                  type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
                });
                console.log(blob);

but i console the blob and receveid it:

Blob {size: 3838, type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'}

size
:
3838
type
:
“application/vnd.openxmlformats-officedocument.spreadsheetml.sheet”

how i do this work? the response of backend is binary string, i try convert the blob for base64, but don’t work too. the file returns empty to me.

Submenu Hiding Behind Sidebar in Laravel Project (z-index Issue)

In my Laravel project, I have a sidebar on the homepage that contains several icons. When I hover over a specific icon, I want to display a submenu related to that icon. However, the submenu is partially hidden behind the sidebar—it’s not fully visible.

What I’ve Tried:
I’ve tried adjusting the z-index values for both the sidebar and the submenu.
I set a high z-index (e.g., 9999) on the submenu, but it still hides behind the sidebar.
I’m using position: relative for the icon container and position: absolute for the submenu, which is supposed to make the submenu float over the page.

Expected Behavior:
When I hover over an icon, the submenu should float over the sidebar and be fully visible, rather than partially hidden or cut off.

Even with these z-index adjustments, the submenu is still being partially hidden behind the sidebar. How can I make the submenu fully visible, floating over the sidebar when I hover over an icon?

Dynamically loading audio from the backend and playing it on the website

There is a website, it is connected to the OpenAI Speech API on the server, the server accepts a POST request from the frontend, which contains the text that needs to be voiced, the API voices it and the server returns to the frontend a link to an audio file in mp3 format. The Speech API has a function for streaming audio in real time, the voiceover is transmitted in parts – part voiced, transmitted, part voiced, transmitted.

Everything works for me in the request part, the POST request is sent, I take the link back, but the link does not want to be voiced. If you output it to the console and click on it, you can see that it is partially loaded and fully ready to listen about three seconds after returning from the server.

I’ve written this code in which the getPromiseFromEvent function wraps the EventListener in a promise and waits for it to be executed. This code works, for example, with a click, if I hang a click on the same button in this way, then the code patiently waits for the click and then continues working on the section below, outputs console.log(‘canplay’) to the console. But if we are talking about an Audio object event listener, then the “canplaythrough” listener does not work at all.

speech.addEventListener('click', async (e) => {
        e.preventDefault();
        let speechText = currentAnswer.replace(/[^p{L}ds.,-+!?:;]/gu, '');
        
        if (paidVoice === 2) {
            
            function getPromiseFromEvent(item, event) {
              return new Promise((resolve) => {
                const listener = () => {
                  item.removeEventListener(event, listener);
                  resolve();
                }
                item.addEventListener(event, listener);
              })
            }
            
            async function waitForAudioLoad() {
              const audio = new Audio();
              audioURL = await getOpenaiVoice(voiceAPI, voiceID, speechText);

              audio.src = audioURL;  
              
              audio.addEventListener("progress", () => {
                  if (audio.readyState > 0) {
                      console.log('Audio is loading');
                  }
              })
              
              if (audio.readyState > 0) {
                      console.log('Audio is loading-2');
                }
              
              console.log(audio.readyState);
              
              await getPromiseFromEvent(audio, "canplaythrough");
              audio.play();
              
              
              console.log('canplay')
            }
            waitForAudioLoad();
        }

I tried to ‘Blob’ the audio, tried to add event listener without functions, nothing works, when I used Blob I got an error “Failed to load because no supported source was found.”

Only thinks that works for me is wrapping the audio.src = audio URL in setTimeout for 3 seconds, then the code works as it should and the voiceover starts and audio.readyState changes its state to full load, like that

 async function waitForAudioLoad() {
              const audio = new Audio();
              audioURL = await getOpenaiVoice(voiceAPI, voiceID, speechText);
              
              setTimeout(() => {
                audio.src = audioURL;   
              }, 3000)

              
              audio.addEventListener("progress", () => {
                  if (audio.readyState > 0) {
                      console.log('Audio is loading');
                  }
              })
              await getPromiseFromEvent(audio, "canplaythrough");
              audio.play();

If I do this, audio wait 3 seconds after click and perfectly working. I think if I replace setTimeout with a competent download completion listener, everything will work. But I don’t know how to do it, so hope for your help.

This is a getOpenaiVoice function, just in case. I don’t think problem is here.

async function getOpenaiVoice(voiceAPI, id, answer) {
          
          const requestOptions = {
              method: "POST",
              headers: {
                  "Content-Type": "application/json",
                  "Accept": "application/json"
              },
              body: JSON.stringify({
                  "id_answer": id.toString(),
                  "answer": answer,
              }),
          };

          const response = await fetch(voiceAPI, requestOptions);
          if (!response.ok) {
            const error = await response.json();
            return error;
          }
          const responseJson = await response.json();
          const audioURL = responseJson.audio_url;
          
          return audioURL;
      }

How to efficiently use a large JavaScript file as a question bank in Flutter instead of JSON?

I am working on a Flutter project where I need to implement a large quiz feature. Previously, I was using a JSON file to store questions, answers, and options. However, now I want to switch to using a JavaScript file (Questions.js) which contains around 9000 lines of data structured similarly to JSON. The challenge I face is efficiently loading and using this JavaScript data in my Flutter Dart code.

Since the file is huge, I am unable to directly convert the entire content into JSON and I am looking for a way to utilize it as is within my Flutter application.

What I have tried:
I attempted to manually convert parts of the JavaScript file to JSON but given the file size, it became impractical.
I considered fetching the data from the JavaScript file but could not figure out how to parse it correctly in Dart.
I researched alternatives to handling large datasets within Flutter but most suggestions were focused on using APIs or databases, which is not applicable in my case.

What I expect:

I am looking for a way to load and parse the data from my JavaScript file in Flutter, ideally without converting the entire file into JSON. The solution should allow me to efficiently use the questions and options for my quiz functionality.
Question.js javascript file

Play sound notification when browser tab is inactive

Motivation: Let’s assume that I have a marketplace. Let’s assume that user posted advertisement/poster and went to other website to read something or watch. Still, he would like to receive sound notification to answer to potential buyer/applicant and return back to other duties. Earlier implementing this functionality was easy. Still, I was not able to find working solution in 2024.

I checked existing answers and seems that they are not working in 2024.

It is due to:

In modern browsers autoplay is disabled by default, so you can’t do new Audio(src).play(). I experimented alot, but I didn’t find reliable way to play sound notifications. In other modern websites it seems that sound notifications are missing in 30-50% of cases.

Question: Is it possible to play sound notification from inactive browser tab or when browser is minimized? If it is – then what is the simplest way to achieve this?