-3

I'm creating a custom, table-based HtmlElement (HTML5 Component). The HtmlElement has a custom columnConfiguration attribute to specify the database name, alias (if any) to display instead, and width (in percent) of each column to be displayed using an array of arrays:

<my-html-element columnConfiguration="['Id', 'Id', '0'], ['Description', 'Desc', '80'], ['Abbreviation', 'Abbrv', '20']"></my-html-element>

The custom element successfully reads the attribute value and passes it to a method that builds the table dynamically:

this.displayData(data, table, this.getAttribute('columnConfiguration'));

I'm also using the columnConfiguration attribute to filter out columns that are returned from the database but that I don't want to include in the table, so I want to check whether each returned column is included in the attribute value.

I select the first row of JSON data as firstItem from the returned dataset to create the table header using its property names:

Object.keys(firstItem).forEach(attributeName => {
  if (columnConfiguration.map(item => item[0]).includes(attributeName)) {
    const headerCell = headerRow.insertCell();
    headerCell.textContent = item[1];
    headerCell.style.cssText = 'width: ${item[2]}';
    }
  }
});

But columnConfiguration is coming through as a string, and the .map() call is failing.

I've tried casting columnConfiguration to Array, but I get an array of individual characters:

Array(columnConfiguration)

I've tried creating an array using Array.from() with the same result:

Array.from(columnConfiguration)

I've tried reversing the single and double quotes in the columnConfiguration attribute declaration:

<my-html-element columnConfiguration='["Id", "Id", "0"], ["Description", "Desc", "80"], ["Abbreviation", "Abbrv", "20"]'></my-html-element>

I've also tried including the enclosing square brackets of the "outer" array:

<my-html-element columnConfiguration="[['Id', 'Id', '0'], ['Description', 'Desc', '80'], ['Abbreviation', 'Abbrv', '20']]"></my-html-element>

And I've tried creating an new Array, using the attribute string:

new Array(columnConfiguration)

There doesn't seem to be a way to convert a string literal containing a JavaScript array of arrays to an actual array of arrays short of the dreaded (and horrifically insecure) eval().

What am I missing?

Note: I'm not interested at all in other ways to get this to work. I want to understand why this way isn't working and what I should do to get the result I want this way.

16
  • 2
    Can it just be JSON (i.e. combining double-quotes and containing-brackets, [["Id", ...)? Otherwise you're basically parsing arbitrary JavaScript. Commented May 20 at 15:33
  • 1
    Generally HTML5 and databases shouldn't mix: If you make your DB available for queries in the browser, users at the very least can use the console to get all of your data. At the very most, users could damage data or create or modify tables. Commented May 20 at 15:36
  • 2
    That's because the Array() constructor doesn't accept a string. You seem to be confusing it with JSON.parse() Commented May 20 at 15:49
  • 3
    Your question and comments keep circling around, and the conversation is basically: 'Why doesn't this person understand the German I'm speaking?', 'Because they speak Chinese. Speak to them in Chinese and they will understand you', 'But I want to speak German'. There's already been multiple examples of the best approach to take to solve your issue. Commented May 21 at 8:41
  • 2
    Are you getting hung up on the meaning of JSON? JSON.parse is able to handle array strings and parse them: console.log(JSON.parse(`[["Id", "Id", "0"], ["Description", "Desc", "80"], ["Abbreviation", "Abbrv", "20"]]`)) works just fine. Your string does not have to be enclosed in {}. Commented May 21 at 16:20

2 Answers 2

2

You have to use JSON parsing, I dont think there is another way?

This should help you get it working.

This should get the attribute value.

const columnConfigString = this.getAttribute('columnConfiguration') // This will get the attribute value.

If the array is in the correct format then this is should be used to change you strings to an array

const columnConfiguration = JSON.parse(columnConfigString) If the array is in the correct format then this is should be used to change you strings to an array

You have to use a valid JSON array, so make sure the data is in "", Put everything in [],

Your main issue is that the JSON data is totally wrong.

All the best, but work with this and it should work but may take some adjustements.

Sign up to request clarification or add additional context in comments.

11 Comments

I do appreciate your desire to help, and I may indeed end up trying JSON, but I said pretty clearly that I was trying to do this with JavaScript arrays and provided several examples to that effect. Before I move on to something else, could someone please confirm or deny that nested JavaScript arrays, as I've shown, are not going to work and please explain why?
It is because it is Javascript still treats it as a plain text string as that is that is the ouput, with your method, you are getting string, not an array. The nested array itself is perfectly fine, but html doesn't store JavaScript objects, it only stores them in text, so you must manually convert it back into a real JavaScript object, using JSON.parse())..Your JavaScript will just get a useless string instead of a sproper array. Li,ke I said, I am pretty sure it is the only way with your provided code and issue
If your array is constructed correctly, JSON.parse will convert valid JSON string into a JavaScript object, JSON is not a JavaScript array it's a text based data format th at is universally used. const jsonString = '[["Id", "Id", "0"], ["Description", "Desc", "80"]]'; Code to convert and use: const array = JSON.parse(jsonString); console.log(array) This will make into a proper usual array.
@DougWilson of course it's possible to parse JavaScript from a string, but it's not clear why think that's what Array.from does. You apparently want neither a safe, easily parsed form (JSON) nor a generic parser (eval).
@DougWilson write a parser, or use one the language offers (eval, maybe new Function - stackoverflow.com/q/14014371/3001761 - or use JSON).
|
0

class MyHtmlElement extends HTMLElement {
    constructor() {
        super();
        this.table = document.createElement('table');
        this.table.setAttribute('border', '1');
        this.appendChild(this.table);
    }
    
    generateAbbreviation(description) {
        const words = description.split(' ');
        
        // The last and this gets the first character from the description
        return words.map(word => word.charAt(0).toUpperCase()).join('');
    }
    
    // Custom method
    ourCallBack() {
        // Original data without Abbreviation
        const originalData = [
            { Id: 1, Description: 'First item description', Hidden: 'Not shown' },
            { Id: 2, Description: 'Second item details', Hidden: 'Hidden' }
        ];
        
        // Add Abbreviation field based on Description
        const data = originalData.map(item => ({
            ...item,
            Abbreviation: this.generateAbbreviation(item.Description)
        }));
        
        // Log to show generated data
        console.log('Generated data:', data);
        
        // Parse columnConfiguration attribute
        const columnConfigStr = this.getAttribute('columnConfiguration');
        let columnConfiguration = JSON.parse('[' + columnConfigStr.replace(/'/g, '"') + ']');
        
        // To create html page, you will have to use and change to suit, this creates table header
        const headerRow = this.table.createTHead().insertRow();
        const firstItem = data[0];
        
        // To create html page, you will have to use and change to suit, this creates headers for columns
        Object.keys(firstItem).forEach(attributeName => {
            if (columnConfiguration.map(item => item[0]).includes(attributeName)) {
          const item = columnConfiguration.find(config => config[0] === attributeName);
                if (item[2] !== '0') {
                    const headerCell = headerRow.insertCell();
                headerCell.textContent = item[1]; // This will use "Desc" and "Abbrv"
                headerCell.style.width = `${item[2]}%`;
                }
            }
        });
        
        // To create html page, you will have to use and change to suit, this creates the table
        const tbody = this.table.createTBody();
        
        // To create html page, you will have to use and change to suit, this add the rows
        data.forEach(item => {
            const row = tbody.insertRow();
            
            // To create html page, you will have to use and change to suit, this takes the item data
            Object.keys(item).forEach(key => {
                const config = columnConfiguration.find(conf => conf[0] === key);         
             if (config && config[2] !== '0') {
                    const cell = row.insertCell();
                    cell.textContent = item[key];
                }
            });
        });
    }
    
    // IMPORTANT, this is what makes it all work
    connectedCallback() {
        document.getElementById('renderButton').addEventListener('click', () => {
            this.ourCallBack();
        });
    }
}

 // IMPORTANT, this is what makes it all work, well to update your page anyway
customElements.define('my-html-element', MyHtmlElement);
<html>
<head>
    <title>Simple Example</title>
</head>
<body>
    <my-html-element columnConfiguration="['Id', 'Id', '0'], ['Description', 'Desc', '80'], ['Abbreviation', 'Abbrv', '20']"></my-html-element>
    <button id="renderButton">Show examples</button>
    <script src="simple.js"></script>
</body>
</html>

This is the last time I will attempt to give an answer(sorry) of which I think works. I think it should work and good other comments on how it should or could work, really hope it works pal, Also quite important, is the xtra hidden field is added like this and shouldnt be changed.

Excluded column: Hidden (Not in configuration)

This is the issue you described in your problem i think, im sorry if I have changed my response a few time but again, i hope it works

class MyHtmlElement extends HTMLElement {
    constructor() {
        super();
        this.table = document.createElement('table');
        this.table.setAttribute('border', '1');
        this.appendChild(this.table);
    }
    
        generateAbbreviation(description) {
        // // Get first character from desc, Split description into words
        const words = description.split(' ');
        
        // This retuen first letterd
        return words.map(word => word.charAt(0)).join('');
    }
    
       ourCallBack() {
        // You data without Abbrv, just example below
        const originalData = [
            { Id: 1, Description: 'First item description', Hidden: 'Not shown' },
            { Id: 2, Description: 'Second item details', Hidden: 'Hidden' }
        ];
        
        // Add Abbrv to array field
        const data = originalData.map(item => ({
            ...item,
            Abbreviation: this.generateAbbreviation(item.Description)
        }));
        
        // Parse columnConfiguration attribute
        const columnConfigStr = this.getAttribute('columnConfiguration');
        let columnConfiguration = JSON.parse('[' + columnConfigStr.replace(/'/g, '"') + ']');
        
        // Create table header
        const headerRow = this.table.createTHead().insertRow();
        const firstItem = data[0];
        
        // Create headers for columns in configuration with non-zero width
        Object.keys(firstItem).forEach(attributeName => {
            if (columnConfiguration.map(item => item[0]).includes(attributeName)) {
                const item = columnConfiguration.find(config => config[0] === attributeName);
                if (item[2] !== '0') {
                    const headerCell = headerRow.insertCell();
                    headerCell.textContent = item[1];
                    headerCell.style.width = `${item[2]}%`;
                }
            }
        });
        
        // Purely for out put
        const tbody = this.table.createTBody();
        
        //  Purely for out put
        data.forEach(item => {
            const row = tbody.insertRow();
            
            //  Purely for out putFor each property in the data item
            Object.keys(item).forEach(key => {
                //  Purely for out put but important to find the matching configuration
                const config = columnConfiguration.find(conf => conf[0] === key);
                
                //  Purely for out put but important to only add cell if column is in configuration and width is not 0
                if (config && config[2] !== '0') {
                    const cell = row.insertCell();
                    cell.textContent = item[key];
                }
            });
        });
        
        // Output the generated data to console for reference
        console.log('Generated data with abbreviations:', data);
    }
    
    // Method to listen for data and make changes
    connectedCallback() {
        document.getElementById('renderButton').addEventListener('click', () => {
            this.ourCallBack();
        });
    }
}

// You need this to register all elements
customElements.define('my-html-element', MyHtmlElement);
<html>
<head>
    <title>Simple Example</title>
</head>
<body>
 <my-html-element columnConfiguration="['Id', 'Id', '0'], ['Description', 'Desc', '80'], ['Abbreviation', 'Abbrv', '20']"></my-html-element>
 <button id="renderButton">Render Table</button>
 <script src="simple.js"></script>
</body>
</html>

2 Comments

I do appreciate the time and effort you put into this, @JulesUK, but the answer uses JSON, not a JavaScript Array, which I specifically said it needed to do. Giving up and going to ask AI. Thanks again though.
No need to apologize. Your first answer was a perfectly reasonable interpretation of, and solution to, the problem.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.