Creating an editable HTML table from 2D array - javascript

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>

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

Related

Display product details when click on button

<script>
//<body>
//<div class="prod"></div>
//<div class="details"></div>
//</body>
const product=document.querySelector(".prod");
var ourRequest=new XMLHttpRequest();
ourRequest.open('GET','http://inec.sg/assignment/retrieve_records.php');
ourRequest.onload=function(){
var data=JSON.parse(ourRequest.responseText);
renderHTML(data);
};
ourRequest.send();
function renderHTML(data){
for(var i=0;i<data.songs.length;i++){
product.innerHTML+=`
<div><img src="${data.songs[i].image}" height="200px"></div>
<div><h2>${data.songs[i].name}</h2></div>
<div><h2>$${data.songs[i].price}</h2></div>
<button id="">Details</button>
`;
}
}
//how do i display prod details when click on button? Able to hide the prod listing div, show the details in details div?
// details eg. data.songs.image, data.songs.brand, data.songs.style, data.songs.discount, data.songs.price
</script>
How do I display prod details when click on button? Able to hide the product listing div, show the details in details div?
Show these in details:
data.songs.image, data.songs.brand, data.songs.style, data.songs.discount, data.songs.price
This is what I came up with:
<button class="load">Load List</button>
<button class="hide">Hide List</button>
<div class="prod"></div>
<div class="details"></div>
<script>
const product = document.querySelector(".prod");
const details = document.querySelector(".details");
const loadBtn = document.querySelector(".load");
const hideBtn = document.querySelector(".hide");
loadBtn.addEventListener("click", () => { // on click of load button
// send request to get data
var ourRequest = new XMLHttpRequest();
ourRequest.open(
'GET',
'http://inec.sg/assignment/retrieve_records.php'
);
ourRequest.onload = function () {
var data = JSON.parse(ourRequest.responseText);
renderHTML(data);
};
ourRequest.send();
function renderHTML(data) {
product.innerHTML = ""; // empty product element
for (var i = 0; i < data.songs.length; i++) {
product.innerHTML += `
<div><img src="${data.songs[i].image}" height="200px"></div>
<div><h2>${data.songs[i].name}</h2></div>
<div><h2>$${data.songs[i].price}</h2></div>
<button data-index="${i}" class="detailsBtn">Details</button>
`; // data-index represents the index of the item in the songs array
}
let detailsBtns = document.querySelectorAll(".detailsBtn"); // get button element(s) previously created
for (var i = 0; i < detailsBtns.length; i++) {
let detailsBtn = detailsBtns[i];
detailsBtn.addEventListener("click", () => { // add on click event for individual details btn
// load details
let index = detailsBtn.dataset.index; // get index of song item
let song = data.songs[index];
details.innerHTML = `<h2>${song.image}</h2><h2>${song.brand}</h2><h2>${song.style}</h2><h2>${song.discount}</h2><h2>${song.price}</h2>`;
});
}
}
});
hideBtn.addEventListener("click", () => { // hide details when hide details button is clicked
product.innerHTML = ""; // empty product element
details.innerHTML = ""; // empty details element
});
</script>
First I declared and assigned common elements like product, details, hideBtn, and loadBtn.
I used the EventTarget.addEventListener() function to bind click events to loadBtn, hideBtn, and detailsBtn .
I used arrow functions for the callback functions of the events, but ES5 functions could certainly be used.
I used the HTMLElement.dataset attribute to link a song index to each product, so I can link the HTML of the product item(s) to their corresponding JS event functionality.
There are many other ways this could be done, but this is how I solved the problem.

Can't access/update class object properties after creation - javascript

The problem is I get "Uncaught ReferenceError: holder is not defined" when trying to access object properties after object creation.
I'm doing the To Do List project for the the Odin Project.
JSFIDDLE: https://jsfiddle.net/appredator/8tcg7yo9/312/
Essentially, I want to click the add button, gather the values from the input fields, and save the values to their correct properties in a corresponding object and then display them as well.
//HTML
<div class="grid-container">
<h2 class="projectName">
Project
</h2>
<h2 class="task">
Task
</h2>
<h2 class="dueDate">
Due Date
</h2>
<h2 class="status">
Status
</h2>
</div>
<!-- End Header Div -->
<!-- Begin To Do List Grid Rows -->
<div class="projects">
</div>
<!-- Button at bottom of page -->
<button id="addProject">
Add To Do
</button>
//JS
class Project {
constructor(name) {
this.name = name;
}
}
class ToDo {
constructor(projectName, task, dueDate, status) {
this.projectName = projectName;
this.task = task;
this.dueDate = dueDate;
this.status = status;
this.list =
[{
task: "",
status: "",
dueDate: "",
}];
}
}
// Add Project Button Logic
const addButton = document.querySelector("#addProject");
addButton.addEventListener('click', addProject);
function addProject(){
let counter = 0;
//Select bottom div for updates etc
var content = document.querySelector(".projects");
//Save projectNameField contents when enter is pressed on the ProjectName Field
const projectNameField = document.createElement("input");
projectNameField.classList.add('projectNameCell');
content.appendChild(projectNameField);
projectNameField.addEventListener('keydown', function onEvent(e) {
if (e.key === 'Enter' || e.key === 'Tab') {
//Get and save project name
/* var project = projectNameField.value; */
//Instantiate Project constructor with DOM value
let project = new Project(projectNameField.value);
console.log(e.key)
projectNameField.remove();
//Use constructor to display project name in text
const projectNameDisplay = document.createElement("h3");
projectNameDisplay.classList.add('projectNameCell');
projectNameDisplay.innerHTML = project.name;
content.appendChild(projectNameDisplay);
}
});
//Save titleField contents when enter is pressed on the ProjectName Field
const taskField = document.createElement("input");
taskField.classList.add('taskCell');
content.appendChild(taskField);
taskField.addEventListener('keydown', function onEvent(e) {
if (e.key === 'Enter' || e.key === 'Tab') {
//Get and save title
//Instantiate toDo constructor with DOM value
let holder = new ToDo(taskField.value);
holder.projectName = document.querySelector(".projectNameCell").innerHTML;
holder.task = taskField.value;
console.log(holder.task)
//Use constructor to display project name in text
const taskDisplay = document.createElement("h3");
taskDisplay.classList.add('titleCell');
taskDisplay.innerHTML = holder.task;
taskField.remove();
content.appendChild(taskDisplay);
}
});
const dueDateField = document.createElement("input");
dueDateField.classList.add('dueDateCell');
content.appendChild(dueDateField);
dueDateField.addEventListener('keydown', function onEvent(e) {
if (e.key === 'Enter' || e.key === 'Tab') {
//Instantiate toDo constructor with DOM value
holder.dueDate = dueDateField.value;
/* console.log(task.list.dueDate) */
dueDateField.remove();
//Use constructor to display project name in text
const dueDateDisplay = document.createElement("h3");
dueDateDisplay.classList.add('dueDateCell');
dueDateDisplay.innerHTML = holder.dueDate;
content.appendChild(dueDateDisplay);
}
});
const statusField = document.createElement("input");
statusField.classList.add('statusCell');
content.appendChild(statusField);
statusField.addEventListener('keydown', function onEvent(e) {
if (e.key === 'Enter' || e.key === 'Tab') {
//Instantiate toDo constructor with DOM value
task.status = statusField.value;
console.log(task.dueDate)
statusField.remove();
//Use constructor to display project name in text
const statusDisplay = document.createElement("h3");
statusDisplay.classList.add('statusCell');
statusDisplay.innerHTML = task.status;
content.appendChild(statusDisplay);
}
});
counter++;
}
**I have tried adding get / set methods to the ToDo class with no luck. I also tried factory functions...
This is a scoping problem right? The properties are local scoped to the holder object?... I cut my code down on this post, but the full code is at jsfiddle. The reference error happens after you hit tab or enter on the 3rd input field.... Please advise. I'm kind of puzzled why objects would be so hard to access and update after creation lol. I created the object so I could use it. I know it's something trivial about scope and when to use constructors/classes/factories, but I cant figure it out.**

How to create an onclick function that retrieves the object ID on the HTML and attach the ID onto the API to be fetched and used?

i'm a beginner developer in javascript trying to make a website to impress recruiters. I've succesfully retrieve information from my API and used it on my website which took me ages to figure out. Now the next problem is how do i make an onclick event that gets the ID i hardcoded onto the specific movie poster on my HTML, to be used in javascript to change the API address and get the data for that specific movie.
Notes: i have a modal onclick to host all the movie data on that modal.
My Javascript and HTML is below.
HTML
<div class="poster" onclick="toggleModal(); getimdbID()" id="tt4154796">
<div id="imdbID">tt4154796</div>
<img
src="https://m.media-amazon.com/images/M/MV5BMTc5MDE2ODcwNV5BMl5BanBnXkFtZTgwMzI2NzQ2NzM#._V1_SX300.jpg"
alt=""
class="poster__img"
/>
<p class="movie__title">Avengers: Endgame</p>
<p class="year">2019</p>
</div>
Javascript
async function getMovie() {
const movies = await fetch(`https://www.omdbapi.com/?i=tt4154796&apikey=4148fa0f`);
const movieData = await movies.json();
const { Title, Year, Runtime, Genre, Director, Actors, Plot, Awards, Poster, Metascore, imdbRating, imdbVotes, BoxOffice, } = movieData
document.getElementById('title').textContent = Title;
document.getElementById('year').textContent = Year;
document.getElementById('genre').textContent = Genre;
document.getElementById('runtime').textContent = Runtime;
document.getElementById('plot').textContent = Plot;
document.getElementById('director').textContent = Director;
document.getElementById('actors').textContent = Actors;
document.getElementById('awards').textContent = Awards;
document.getElementById('metascore').textContent = Metascore;
document.getElementById('boxoffice').textContent = BoxOffice;
document.getElementById('imdbRating').textContent = imdbRating;
document.getElementById('imdbVotes').textContent = imdbVotes;
document.getElementById('poster').src = Poster;
}
getMovie()
// ToggleModal
let isModalOpen = false;
function toggleModal() {
if(isModalOpen) {
isModalOpen = false;
return document.body.classList.remove("modal--open");
}
isModalOpen = true;
document.body.classList += " modal--open";
}
I've tried to create a function onclick to get the value of the ID onclick but that didn't work.
function getimdbID() {
let x = document.querySelector("#imdbID").value
console.log(x)
}
Im guessing you want your function to return the "tt4154796" thats contained within the div. If this is the case you'll want to use document.querySelector("#imdbID").innerText.
Since using .value is typically used to retrieve or change values entered into input fields.

Why when i am searching for something else is deleting the previous contents

Why when you are searching for something else is deleting the previous contents ?For example first you search for egg and show the contents but then when you search for beef the program deletes the egg and shows only beef.Code :
const searchBtn = document.getElementById('search-btn');
const mealList = document.getElementById('meal');
const mealDetailsContent = document.querySelector('.meal-details-content');
const recipeCloseBtn = document.getElementById('recipe-close-btn');
// event listeners
searchBtn.addEventListener('click', getMealList);
mealList.addEventListener('click', getMealRecipe);
recipeCloseBtn.addEventListener('click', () => {
mealDetailsContent.parentElement.classList.remove('showRecipe');
});
// get meal list that matches with the ingredients
function getMealList(){
let searchInputTxt = document.getElementById('search-input').value.trim();
fetch(`https://www.themealdb.com/api/json/v1/1/filter.php?i=${searchInputTxt}`)
.then(response => response.json())
.then(data => {
let html = "";
if(data.meals){
data.meals.forEach(meal => {
html += `
<div class = "meal-item" data-id = "${meal.idMeal}">
<div class = "meal-img">
<img src = "${meal.strMealThumb}" alt = "food">
</div>
<div class = "meal-name">
<h3>${meal.strMeal}</h3>
Get Recipe
</div>
</div>
`;
});
mealList.classList.remove('notFound');
} else{
html = "Sorry, we didn't find any meal!";
mealList.classList.add('notFound');
}
mealList.innerHTML = html;
});
}
It's because you are replacing the contents in the mealList element every time.
A simple workaround would be to retrieve the the innerHTML values before you update it.
Something like
let html = mealList.innerHTML;
rather than starting off empty every time you call the function should do the trick.

getting more than one items from local storage using javscript

I am currently working on a project and struggling to implement Local Storages on my work.
About My Project:
I am trying to do a basic FILM LIST Project.
I have 3 inputs. I saved them but I am not able to get all items from storage when the "DOMContentLoaded" listener run.
I created a LocalStorage class and imported it to my app.js
I created a FILM class and imported it to my app.js
I created a UI class and imported it to my app.js
I am going to post my important functions and classes for you to check,
Film.js
class Film {
constructor(title, director, url) {
this.title = title;
this.director = director;
this.url = url;
}
}
export default Film;
UI.js
class UI {
constructor() {
}
static addFilmToUI(film) {
//console.log(film.title, film.director, film.url)
let html = ` <tr>
<td><img src="${film.url}" class="img-fluid img-thumbnail"></td>
<td>${film.title}</td>
<td>${film.director}</td>
<td>Delete Film</td>
</tr>`
document.getElementById("films").innerHTML += html;
} ...
LocalStorage.js
class LStorage {
constructor() {
}
static getAllItemsFromLocalStorage() {
let films;
if (localStorage.getItem("films") === null) {
films = [];
} else {
films = JSON.parse(localStorage.getItem("films"));
console.log(films)
}
return films;
}
static addItemsToLocalStorage(film) {
let films = this.getAllItemsFromLocalStorage();
films.push(film);
localStorage.setItem("films",JSON.stringify(films));
}}
App.js
const form = document.querySelector("form");
form.addEventListener("submit", createFilm);
document.addEventListener("DOMContentLoaded",function(){
let films = LS.getAllItemsFromLocalStorage();
Array.prototype.forEach.call(films,(element)=>{
UI.addFilmToUI(element)
})
})
function createFilm(e) {
e.preventDefault()
const title = document.getElementById("title");
const director = document.getElementById("director");
const url = document.getElementById("url");
let informations = [title.value, director.value, url.value];
if (informations.every((values) => values != "")) {
const createdFilm = new FILM(title.value, director.value, url.value);
UI.addFilmToUI(createdFilm);
LS.addItemsToLocalStorage([title.value,director.value,url.value]);
} else{
alert("Cant be empty!")
}
UI.clearInputs(title,director,url)
}
When I refresh my page the only thing I get is undefined.
When i upload only film the console output is:
[Array(3)]
0: (3) ["Avatar", "James Cameron", "js.png"]
length: 1
proto: Array(0)
Ah, I found my mistake. I send multiple params to a single-valued param function also when I calling my data from storage, I did kind of override I guess. I tried to invoke my data from where I put them into UI. That's why I confused. Therefore I created one function more which called loadAllData().
And also i adjust my eventListener function as well.
- LS.addItemsToLocalStorage(createdFilm)
document.addEventListener("DOMContentLoaded",function(){
let films = LS.getAllItemsFromLocalStorage();
UI.loadAllItems(films)
})
static loadAllItems(films){
const filmList = document.getElementById("films");
films.forEach((film)=>{
filmList.innerHTML += ` <tr>
<td><img src="${film.url}" class="img-fluid img-thumbnail"></td>
<td>${film.title}</td>
<td>${film.director}</td>
<td>Filmi Sil</td>
</tr>`
})
}

Categories