1

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?

6
  • you only want to use native javascript for this ? Commented May 30, 2019 at 16:08
  • If possible yes, what would you recommend? Commented May 30, 2019 at 16:14
  • there are various libraries for that datatables.net/examples/data_sources/js_array.html Commented May 30, 2019 at 16:19
  • Hey, I was working on typing up an answer but I'm not sure what kind of answer you are looking for. Are you looking for the algorithm to display the flashcards and allow them to switch/edit the value? That is likely a lot of code to write. A library that you might appreciate for this type of DOM manipulation would be VueJS - it's pretty lightweight. Commented May 30, 2019 at 16:19
  • Yes, I would like to display the flashcards, and allow users to edit values, and then apply the changes to the array. I'll check out Vue! thanks Commented May 30, 2019 at 16:29

3 Answers 3

1

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>
Sign up to request clarification or add additional context in comments.

Comments

1

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>

Comments

0

i think you can use In-Place Editing System and there's a good tutorial i found Create an In-Place Editing System

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.