从2D数组创建可编辑的HTML表

时间:2019-05-30 16:05:15

标签: javascript html arrays

因此,我试图建立一个抽认卡网站,用户可以在其中添加,编辑和删除抽认卡。有两张牌-正面和背面。用户已经可以添加单词,但是不能编辑或删除它们。出于这个问题的目的,我将使用一个示例数组:

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

但是我想以一种更加用户友好的方式来编辑抽认卡,最好使用这样的表:

<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>

因此,他们可以更新其抽认卡,以编辑输入字段,或单击“添加更多”以创建新行。单击“保存更改”会将数组更新为表的内容。

我不介意它本身不是HTML表,而是易于用户编辑的东西。

我只是想不出解决此问题的最佳方法。有什么建议吗?

3 个答案:

答案 0 :(得分:1)

我认为您可以使用就地编辑系统,而且我找到了一个很好的教程 Create an In-Place Editing System

答案 1 :(得分:1)

我已经推荐了VueJS-它确实是解决此问题的不错的工具。无论如何,我已经使用香草JavaScript键入了一个基本解决方案。对于编辑部分,它使用contenteditable HTML属性,该属性允许最终用户双击元素并更改其textContent。 html显示是基本的,因此您可以根据自己的需要进行更改

<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>

答案 2 :(得分:1)

我使用了纯原生javascript和数据驱动的方法,整理了一个工作示例。您可以查看并了解在大型Js应用程序中如何处理和使用数据的方式。

这里的重点是尽可能地隔离数据和逻辑。

希望获得帮助。

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>