简体   繁体   中英

Creating an editable HTML table from 2D array

So I am trying to make a flashcards website, where users can add, edit, and delete flashcards. There are two cards - front and back. The user can already add words, but cannot edit or delete them. For the purposes of this question I will use an example array:

var flashcards = [["Uomo", "Man"],["Donna", "Woman"],["Ragazzo", "Boy"]]

But I would like a more user-friendly way to edit the flashcards, preferably using a table like this:

<table>
  <tr>
    <th>Front</th>
    <th>Back</th> 
  </tr>
  <tr>
    <td><input type="text" name="flashcard" value="Uomo"> </td>
    <td><input type="text" name="flashcard" value="Man"></td>
  </tr>
    <tr>
    <td><input type="text" name="flashcard" value="Donna"></td>
    <td><input type="text" name="flashcard" value="Woman"></td>
  </tr>
      <tr>
    <td><input type="text" name="flashcard" value="Ragazzo"></td>
    <td><input type="text" name="flashcard" value="Boy"></td>
  </tr>
</table>

<button type="button">Add more</button>
<br>
<button type="button">Save changes</button>

So they can update their flashcards editing the input fields, or clicking "add more" and it creating a new row. Clicking "save changes" updates the array to the content of the table.

I don't mind it not being a HTML table per se, but something that is easy to edit for the user.

I just cannot figure out the best way to approach this. Any advice?

我认为你可以使用就地编辑系统,我找到了一个很好的教程创建一个就地编辑系统

I already recommended VueJS - it really is a pretty good tool for this problem. Regardless, I have typed up a basic solution using vanilla JavaScript. For the editing part it uses the contenteditable HTML attribute which allows the end-user to double click an element and change it's textContent. The html display is basic so you can change it however to fit your needs

<div id=style="width: 100%;">
  <ul id="table" style="list-style-type: none; display: inline-block;">

  </ul>
</div>
<script>
var flashcards = [["Uomo", "Man"],["Donna", "Woman"],["Ragazzo", "Boy"]];
var displayedCard = []; //Using a parallel array to keep track of which side is shown
for(var i = 0; i < flashcards.length; i++){
    displayedCard.push(0);
}
function renderFlashcardTable(){ //This will do the initial rendering of the table
    let ulTable = document.getElementById("table");
    for(var i = 0; i < flashcards.length; i++){
        let card = flashcards[i];
        let indexOfSideShown = displayedCard[i];
        let li = document.createElement("li");
        let cardValueSpan = document.createElement("span");
        cardValueSpan.innerHTML = card[indexOfSideShown]; //Get the value of the side of the card that is shown
        cardValueSpan.setAttribute("contenteditable", "true"); 
        cardValueSpan.oninput = function(e){ //This method gets called when the user de-selects the element they have been editing
            let li = this.parentElement;
            let sideIndex = parseInt(li.getAttribute("side-index"));
            card[sideIndex] = this.textContent;
        }
        li.appendChild(cardValueSpan);
        li.appendChild(getFlipSidesButton(li));
        li.setAttribute("side-index", indexOfSideShown);
        li.setAttribute("card-index", i);
        ulTable.appendChild(li);
    }
}
function getFlipSidesButton(listItem){//This is generated for each card and when clicked it "flips the switch"
    let btn = document.createElement("button");
    btn.innerHTML = "Flip card";
    btn.onclick = function(e){
        let card = flashcards[listItem.getAttribute("card-index")];
        let index = parseInt(listItem.getAttribute("side-index"));
        let nextSide = (index == 1) ? 0 : 1;
        listItem.setAttribute("side-index", nextSide);
        listItem.children[0].innerHTML = card[nextSide];
    }
    return btn;
}

renderFlashcardTable();
</script>

I've put together a working sample using pure native javascript with a data-driven approach. You can have a look and understand the way how data should be manipulated and worked with in large Js application.

The point here is to isolate the data and logic as much as possible.

Hope this help.

Codepen: https://codepen.io/DieByMacro/pen/rgQBPZ

 (function() { /** * Default value for Front and Back */ const DEFAULT = { front: '', back: '', } /** * Class Card: using for holding value of front and back. * As well as having `update` method to handle new value * from input itself. */ class Card { constructor({front, back, id} = {}) { this.front = front || DEFAULT.front; this.back = back || DEFAULT.back; this.id = id; } update = (side, value) => this[side] = value; } /** * Table Class: handle rendering data and update new value * according to the instance of Card. */ class Table { constructor() { this.init(); } /** Render basic table and heading of table */ init = () => { const table = document.querySelector('#table'); const thead = document.createElement('tr'); const theadContent = this.renderRow('th', thead, { front: 'Front', back: 'Back' }) const tbody = document.createElement('tbody'); table.appendChild(theadContent); table.appendChild(tbody); } /** Handling add event from Clicking on Add button * Note the `update: updateFnc` line, this means we will refer * `.update()` method of Card instance with `updateFnc()`, this is * used for update value Card instance itself. */ add = ({front, back, id, update: updateFnc }) => { const tbody = document.querySelector('#table tbody'); const row = document.createElement('tr'); const rowWithInput = this.renderRow('td', row, {front, back, id, updateFnc}); tbody.appendChild(rowWithInput); } renderInput = (side, id, fnc) => { const input = document.createElement('input'); input.setAttribute('type','text'); input.setAttribute('name',`${side}-value-${id}`) input.addEventListener('change', e => this.onInputChangeHandler(e, side, fnc)); return input; } renderRow = ( tag, parent, { front, back, id, updateFnc }) => { const frontColumn = document.createElement( tag ); const backColumn = document.createElement( tag ); /** Conditionally rendering based on `tag` type */ if ( tag === 'th') { frontColumn.innerText = front; backColumn.innerText = back; }else { /** Create two new inputs for each Card instance. Each handle * each side (front, back) */ const inputFront = this.renderInput('front', id, updateFnc); const inputBack = this.renderInput('back', id, updateFnc); frontColumn.appendChild(inputFront); backColumn.appendChild(inputBack); } parent.appendChild(frontColumn) parent.appendChild(backColumn) return parent; } /** Getting new value and run `.update()` method of Card, now referred as `fnc` */ onInputChangeHandler = (event, side, fnc) => { fnc(side, event.target.value); } } class App { /** * Holding cards data * Notice this is an object, not an array * Working with react for a while, I see most of the times data as an object works best when it comes to cRUD, this means we don't have to iterate through the array to find the specific element/item to do the work. This saves a lot of time */ cards = {}; constructor(){ this.domTable = new Table(); this.domAdd = document.querySelector('#btn-add'); this.domResult = document.querySelector('#btn-result'); this.domAdd.addEventListener('click', this.onClickAddHandler ); this.domResult.addEventListener('click', this.onClickResultHandler ); } onClickAddHandler = () => { const id = uuid(); const newCard = new Card({id}); this.cards[id] = newCard; this.domTable.add(newCard) } onClickResultHandler = () => { /** * Using `for ... in ` with object. Or you can use 3rd party like lodash for iteration */ for (const id in this.cards) { console.log({ front: this.cards[id].front, back: this.cards[id].back, id: this.cards[id].id }); } }; } // Start the application const app = new App(); })(); 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/node-uuid/1.4.8/uuid.min.js"></script> <div id="table"></div> <button id="btn-add">Add</button> <button id="btn-result">Result</button> 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM