Dynamically expanding table from object array - javascript

As the title states, I'm trying to create a table that will dynamically expand or shrink depending on the number of objects in the array. Each of these objects has 10 properties which all need to be displayed in separate rows. I started writing the for loop to iterate over the array and display each property using JQuery's .html() and realized it would look messy when all said and done, but I do not know where to start.. The complete code is below, but I'm working in the showResults function now..
//scripts
//Customer Object constructor
function CustomerObject(fName, lName, mName, address, city, state, zip, age, gender, pizza) {
this.fName = fName;
this.lName = lName;
this.mName = mName;
this.address = address;
this.city = city;
this.state = state;
this.zip = zip;
this.age = age;
this.gender = gender;
this.pizza = pizza;
}
//Array of CustomerObjest
var CustomerArray = new Array();
$(document).ready(function () {
$("#TBFName").focus();
});
$("#BtnSave").click(function () {
//When Save button is pressed.
Validate();
});
$("#BtnReset").click(function () {
//When Clear button is pressed
ClearAllInfo();
});
$("#BtnDone").click(function () {
//When Done button is pressed
ShowResults();
});
$("#BtnMore").click(function () {
//When Enter More button is pressed
ShowResults();
$("#DivFormContainer").show();
$("#DivResults").hide();
});
$("#BtnMoreClearCustomers").click(function () {
//When Clear All Customers button is pressed
CustomerArray = new Array();
$("#DivFormContainer").show();
$("#DivResults").hide();
});
function Validate() {
var isValid = true;
var fstName = $("#TBFName").val();
var mdlName = $("#TBMI").val();
var lstName = $("#TBLName").val();
var addrs = $("#TBAdress").val();
var cit = $("#TBCity").val();
var stat = $("#DDState").val();
var zipCode = $("#TBZip").val();
var gend = $("input:radio[name='RGGender']:checked").val();
var old = $("#TBAge").val();
var pza = $("input:radio[name='RGLikePizza']:checked").val();
// Validation goes Here for All Fields
if (isValid == true) { //If isValid is still true, no errors
CustomerArray.push(new CustomerObject(fstName, lstName, mdlName, addrs, cit, stat, zipCode, old, gend, pza));
$("#DivMessage").show();
$("#DivMessage").html("Record Saved. Add a new record or press done to see results.");
$("#DivMessage").fadeOut(1600);
}
}
function ShowResults() {
ClearAllInfo();
$("#DivResults").show();
$("#DivFormContainer").hide();
//Code to display all customers information
for (var i = 0; i < CustomerArray.length; i++) {
$("#list").html(CustomerArray[i].fName + CustomerArray[i].lName + CustomerArray[i].mName);
//create table?
}
}
function ClearResultsDiv() {
$("#DivResults").html("");
}
function ClearAllInfo() {
$("input[type=text]").val("");
$("textarea").val("");
$("select").prop('selectedIndex', 0);
$(".error").html("");
$("input[type=radio]").attr('checked', false);
}

This is where direct DOM manipulation becomes messy and you are better off using an MVC framework. There are many to choose from: http://todomvc.com
I made a quick example using AngularJS:
http://codepen.io/anon/pen/nuGwz?editors=101
The nice thing is, you then only need to care about modifying your data (the customer array). The view (DOM) gets updated automatically. Like so:
$scope.CustomerArray.push({name: 'Joanna', age: '46', city: 'New York'});

As I understand it, you want to generate a table from an array containing row data.
Here's one way to make a table:
var maketable = (function(){
var elem, td, th, tr;
elem = function(elemtype, children){
var el = document.createElement(elemtype);
children.forEach(function(child){
el.appendChild(child);
});
return el;
};
td = function(s){
return elem('td', [document.createTextNode(s)]);
};
th = function(s){
return elem('th', [document.createTextNode(s)]);
};
tr = function(row){
return elem('tr', row.map(td));
};
return function(data){
var cols, rows, thead, tbody, table;
cols = Object.keys(data[0]);
rows = data.map(function(row){
return Object.keys(data[0]).map(function(key){
return row[key];
});
});
thead = elem('thead', [elem('tr', cols.map(th))]);
tbody = elem('tbody', rows.map(tr));
table = elem('table', [thead, tbody]);
return table;
};
}());
Example usage:
var data = [{a: 'a0', b: 'b0'}, {a: 'a1', b: 'b1'}, {a: 'a2', b: 'b2'}];
var table = maketable(data);
document.body.appendChild(table);

Related

Firebase .orderByChild().on runs more and more times every time it is called

I'm creating a guest list program that stores the guest list in Firebase RTDB and when I check people in and out my function runs several times more than it is supposed to. I've sent alerts to the console so I know how many times it has run. I have separate functions for both check in and check out operations so it may be that I am calling my db too many times?
//-------------------- Check In and Check In Helper Functions -------------------------
//Helper Function to Grab current List index
function printArray() {
var ref = database.ref('guestList')
ref.on('value', readData, errData);
}
function readData(data){
guestList=[];
var scores = data.val();
var keys = Object.keys(scores)
for (var i=0; i < keys.length; i++){
var k = keys[i]
var name = scores[k].name;
var inside = scores[k].Inside;
var timeIn = scores[k].TimeIn;
var timeOut = scores[k].TimeOut;
guestList[i] = {
name: name,
Inside: inside,
TimeIn: timeIn,
TimeOut: timeOut,
}
}
checkIn(guestList);
}
function errData(err){
console.log('Error!');
console.log(err);
}
//Helper Function to set text box to selected name
function checkInn(name){
console.log(name)
document.getElementById('checkIn').value = name;
}
//Check in
function checkIn(list) {
//Grabs current guest to be added or deleted from form text box
var name = document.getElementById('checkIn').value;
//Checks to see if user is in list of guests and isn't in the list of guest in the party
var guestsRef = firebase.database().ref("guestList/");
guestsRef.orderByChild("name").on("child_added", function(data) {
if (name == data.val().name) {
objIndex = list.findIndex((obj => obj.name == name));
guestsRef = firebase.database().ref("guestList/" + objIndex)
guestsRef.update({
Inside: "Yes",
TimeIn: getTime(),
})
guestsRef.off();
document.getElementById('checkIn').value = "";
alerts(name, true)
}
})
}
//------------------------- Check Out ------------------------------------------------------------
//Helper Function to Grab current List index
function printArrayy() {
var ref = database.ref('guestList')
ref.on('value', readOutData, errData);
}
function readOutData(data){
guestList=[];
var scores = data.val();
var keys = Object.keys(scores)
for (var i=0; i < keys.length; i++){
var k = keys[i]
var name = scores[k].name;
var inside = scores[k].Inside;
var timeIn = scores[k].TimeIn;
var timeOut = scores[k].TimeOut;
guestList[i] = {
name: name,
Inside: inside,
TimeIn: timeIn,
TimeOut: timeOut,
}
}
checkOut(guestList);
}
//Helper Function to set text box to selected name
function checkOutt(name){
console.log(name);
document.getElementById('checkOut').value = name;
}
//Check Out
function checkOut(list) {
//Grabs current guest to be added or deleted from form text box
var name = document.getElementById('checkOut').value;
//Checks to see if user is in list of guests and isn't in the list of guest in the party
var guestsRef = firebase.database().ref("guestList/");
guestsRef.orderByChild("name").on("child_added", function(data) {
if (name == data.val().name) {
objIndex = list.findIndex((obj => obj.name == name));
guestsRef = firebase.database().ref("guestList/" + objIndex)
guestsRef.update({
Inside: "No",
TimeOut: getTime(),
})
document.getElementById('checkOut').value = "";
guestsRef.off();
alerts(name, false)
}
})
}
//Placeholder to alert user when a succesful check in or check out function runs
function alerts(name, Boolean){
if(Boolean){
console.log(name + " has been checked in!")
}
else{
console.log(name + " has been checked out!")
}
}
Here is the screenshot of my output. Thanks in advance!
Edit: Forgot to mention and apologize for my excessive use of helper functions! My HTML form calls printArrayy() and printArray first for each function!
have you tried once instep on, i mean:
ref.once('value', readOutData, errData); }

refresh drop down list after button click in web app

I have a web app with one drop down list and 2 buttons. The drop down list get values from a sheet. The buttons write back in the sheet. The script I have works fine with that:
<script>
$(function() {
$('#txt1').val('');
google.script.run
.withSuccessHandler(updateSelect)
.getSelectOptions();
});
function updateSelect(opt)
{
var select = document.getElementById("sel1");
select.options.length = 0;
for(var i=0;i<opt.length;i++)
{
select.options[i] = new Option(opt[i],opt[i]);
}
}
function listS() {
const selectElem = document.getElementById('sel1')
const index = selectElem.selectedIndex;
if (index > -1) {
const e = document.getElementById("sel1");
const value = e.options[index].value;
const body = { index: index, value: value };
google.script.run.withSuccessHandler(yourCallBack).yourServerSideFunc(body);
}
}
document.getElementById("but1").addEventListener("click",listS);
function yourCallBack(response) {
}
</script>
In Java script:
function getSelectOptions()
{
var ss=SpreadsheetApp.openById('1onuWoUKh1XmvEAmKktwJekD782BFIru-MDA0omqzHjw');
var sh=ss.getSheetByName('Database');
var rg=sh.getRange(2,1,sh.getLastRow()-1,8);
var vA=rg.getValues();
var useremail = Session.getActiveUser().getEmail();
var opt=[];
for(var i=0;i<vA.length;i++)
{
if(vA[i][1] == "Pending Approval"){
if(vA[i][7]+"#xxx.com" == useremail || vA[i][7]+"#xxx.com" == useremail) {
opt.push(vA[i][3]+" REQ ID: "+vA[i][0]);
}
}
};
if (opt.length == 0) {opt.push("You do not have pending requests")};
return opt;
}
function doGet() {
var output = HtmlService.createHtmlOutputFromFile('list');
return output;
}
function yourServerSideFunc(body) {
var value = body["value"];
var ss = SpreadsheetApp.openById('1onuWoUKh1XmvEAmKktwJekD782BFIru-MDA0omqzHjw');
var sh = ss.getSheetByName('Database');
var rg=sh.getRange(1,1,sh.getLastRow()-1,4);
var vA=rg.getValues();
var str = "Approved";
for(var i=0;i<vA.length;i++)
{
if(vA[i][3]+" REQ ID: "+vA[i][0] == value) {
sh.getRange(i+1, 2).setValue(str);
}
};
return ContentService.createTextOutput(JSON.stringify({message: "ok"})).setMimeType(ContentService.MimeType.JSON);
Now I am trying to regenerate the drop down list values after the button is clicked. I tried to add
var output = HtmlService.createHtmlOutputFromFile('list');
return output;
in yourServerSideFunc(body) function to regenerate the HTML but does not work. I have tried to force a HTML refresh, but also did not work.
How can I easily re-trigger the generation of the drop down list items? Worst case scenario it is ok to refresh the whole page, but it should be simple to regenerate the drop down list since I have already the code for it.
I ended up with this work around.
function listS() {
const selectElem = document.getElementById('sel1')
const index = selectElem.selectedIndex;
if (index > -1) {
const e = document.getElementById("sel1");
const value = e.options[index].value;
const body = { index: index, value: value };
google.script.run.withSuccessHandler(yourCallBack).yourServerSideFunc(body);
//ADDED:
var select = document.getElementById("sel1");
select.options[index] = new Option("Approved! Please refresh","Approved! Please refresh");
selectElem.selectedIndex = index;
}
}
It does not really meet the original goal to refresh the list from the sheet. It would be great if someone else posted a solution to call the server function. I tried to add google.script.run.doGet() and similar, but it seems that it does not call the server side functions properly.

How do I figure out the error happening in the foreach loop?

I am following a tutorial to build a budget calculation using modules.For one of the function calcTotal in the module budgetController the guy uses the forEach method and I would like to rewrite it as a for loop.
In this function I am trying to calculate the total of all the expenses and income that the user input on the website. The value get passed to the data.object in the budgetController module. I insert different comments in the full code below to make it as easy as possible to understand.
calcTotal = function(type){
var sum = 0;
data.allItems[type].forEach(function(cur){
sum += cur.value;});
data.totals[type] = sum;
}
var data = {
allItems: {
exp: [],
inc: []
},
totals : {
exp: 0,
inc: 0
},
budget: 0,
percentage: -1
};
The code above is working fine but I tried to do the same with for loop and for some reason is not working.
Could anyone rewrite the forEach method in the function calcTotal as a for loop so I can see what I am doing wrong?
here is the full code:
var budgetController = (function(){
var Expense = function(id, description, value){
this.id = id;
this.description = description;
this.value = value;
};
var Income = function(id, description, value){
this.id = id;
this.description = description;
this.value = value;
};
calcTotal = function(type){
var sum = 0;
data.allItems[type].forEach(function(cur){
sum += cur.value;});
data.totals[type] = sum;
}
var data = {
allItems: {
exp: [],
inc: []
},
totals : {
exp: 0,
inc: 0
},
budget: 0,
percentage: -1
};
return{
addItem: function(type, des, val){
var newItem
var ID = 0;
if(data.allItems[type].length > 0 ){
ID = data.allItems[type][data.allItems[type].length - 1].id +1;
}
else{ID = 0};
//create new item based on exp or inc type
if (type === "exp"){
newItem = new Expense(ID, des, val)
}
else if(type === "inc"){
newItem = new Income(ID, des, val);
}
//Push it into our data structure
data.allItems[type].push(newItem);
//returning the new element
return newItem;
},
calculateBudget: function(){
//calculate total income and expenses
calcTotal("exp");
calcTotal("inc");
// calculate the totalBudget
data.budget = data.totals.inc - data.totals.exp;
// calculate the pecentage;
data.percentage = Math.round((data.totals.exp / data.totals.inc) * 100);
},
testing: function(){
console.log(data);
},
getBudget: function(){
return{
budget: data.budget,
expenses: data.totals.exp,
income: data.totals.inc,
percentage: data.percentege
}
}
}
})()
var UIcontroller = (function(){
getDOM = {
inputValue: ".add__value",
inputDescription: ".add__description",
inputType: ".add__type",
addButton: ".add__btn",
expensesList: ".expenses__list",
incomeList: ".income__list"
};
return {
getInput: function(){
return{
value: parseFloat(document.querySelector(getDOM.inputValue).value),
description: document.querySelector(getDOM.inputDescription).value,
type: document.querySelector(getDOM.inputType).value,
};
},
getDomStrings: function(){
return getDOM;
},
displayListItem: function(type, obj){
var html, newHtml, element
if(type === "exp"){
element = getDOM.expensesList;
html = '<div class="item clearfix" id="expense-%id%><div class="item__description">%description%</div><div class="right clearfix"><div class="item__value">%value%</div><div class="item__percentage">21%</div><div class="item__delete"><button class="item__delete--btn"><i class="ion-ios-close-outline"></i></button></div></div></div>';
}
else if(type === "inc"){
element = getDOM.incomeList;
html = '<div class="item clearfix" id="expense%id%"><div class="item__description">%description%</div><div class="right clearfix"><div class="item__value">%value%</div><div class="item__percentage">10%</div><div class="item__delete"><button class="item__delete--btn"><i class="ion-ios-close-outline"></i></button></div></div></div>';
}
newHtml = html.replace("%id%", obj.id);
newHtml = newHtml.replace("%description%", obj.description);
newHtml = newHtml.replace("%value%", obj.value)
document.querySelector(element).insertAdjacentHTML("beforeend", newHtml);
},
clearFields :function(){
var fields, arrayField
fields = document.querySelectorAll(getDOM.inputValue +"," + getDOM.inputDescription);
arrayField = Array.prototype.slice.call(fields);
fields.forEach(function(current, index, array){
current.value = "";
});
arrayField[0].focus();
}
}
})()
var controller = (function(budgetCntrl, cntrlUI){
var updateBudget = function(){
var budget
// Calculate the Budget
var calcBudget = budgetCntrl.calculateBudget();
// Return the Budget
budget = budgetCntrl.getBudget();
console.log(budget);
//Display the Budget in UI
}
var addItem = function(){
var input, newItem, addItems, clearFields
// Get the file input data
input = cntrlUI.getInput();
// add new Item to the budget Controller
newItem;
if(input.description !=="" && !isNaN(input.value) && input.value > 0){
newItem = budgetCntrl.addItem(input.type, input.description, input.value);
// display Items in the user interface
addItems = cntrlUI.displayListItem(input.type, newItem);
// clear Fields
clearFields = cntrlUI.clearFields();
updateBudget();
// calculate the budget
// display Budget in the user interface
}
}
var setupEventListener = function(){
var DOM = cntrlUI.getDomStrings();
document.querySelector(DOM.addButton).addEventListener("click", addItem);
}
return{
init: function(){
console.log("app has started");
setupEventListener();
}
}
})(budgetController, UIcontroller)
controller.init();
I hope I was clear.
This code:
calcTotal = function(type){
var sum = 0;
data.allItems[type].forEach(function(cur){
sum += cur.value;});
data.totals[type] = sum;
}
Could be re-written as:
function calcTotal(type){
var sum = 0;
for (let i = 0; i < data.allItems[type].length; i++) {
sum += data.allItems[type][i].value;
}
data.totals[type] = sum;
}
Full source: https://jsfiddle.net/gpj40raf/2/
However, may I give you some code review advice?
calcTotal depends on data defined in the enclosing environment. This is not a good idea. It will be better to pass data as a parameter (in certain cases using the closure is good, but this is not one of them). One of the bugs that you have in the complete code is that calcTotal depends on values defined bellow. This will work because of JavaScript hoisting, but is not a good practice.
Note that the forEach code depends that each value is in a value property, but the rest of the code is assuming that values are numbers (i.e. calculateBudget).
The calculation of the total could be abstracted easily without depending on a particular data "shape". For example: data.totals['type']=calcTotal(data.allItems['type']). This makes easy to understand what's going on.
Take a look to Array functions map/reduce/filter. What they do is to abstract certain patterns in a way that's more declarative. For example, to sum values you can use: values.reduce((total, value)=>total+value, 0) (in one line you can express the same as calcTotal).
Take a look to the ES6 constructs. All the new browsers support that today, and you'll be able to use const/let, string literals ,and class for Expense and Income.... the code will be shorter and easy to read.

How to dymically update object on input change?

I have a input tab that I want to update dynamically. When the user changes the value of the input, the new value should replace the old value of the object inside the array.
I am way off the mark here. Can somebody help me out.
function Skills () {
var Skills = this;
this.skill = new Array();
this.skill[0] = {
id: '1_skill_field',
value: 'Insert Skill',
//change function to be used when user changes the value.
change:function (input) {
Skills.skill[parseInt(input.id)["value"]=$("#"+input.id).val();
}
}
var create_section_field = function () {
var section_field = $('<div class="section_fields"></div>');
section_field.appendTo('#skill');
}
var create_fields = function () {
var input_field = $('<div class="input_fields"></div>');
input_field.appendTo('#skill .section_fields');
var skill_field=$('<input>', {
name: '1_skill_field',
id: Skills.skill[0]["id"],
value: Skills.skill[0]["value"],
type: 'text',
//onChange uses function to change saved value of object inside array
onChange: Skills.skill[0].change(this)
});
skill_field.appendTo($('#skill .input_fields'));
}
}
i made what you were doing...here take a look:
http://jsfiddle.net/V4HLz/1/
it was kinda fun. :D
var type = $('#type'),
input = $('#input'),
btn = $('#update'),
show = $('#out'),
stats = $('.skills');
var value, sType, skills={};
btn.click(function(){
value = parseFloat(input.val().trim());
sType = type.val();
if (!value || !sType) return;
skills.update(sType,value);
updateInput();
});
type.change(updateInput);
function updateInput() {
input.val(skills.data[type.val()]);
}
skills.update = function(t,v){ this.data[t] = v; };
skills.data = {
eating:0,
fighting:0,
flying:0,
reg:0
};
show.click(function(){
$.each( skills.data, function(k,v){
stats.find('#'+k).children('span').text(v);
});
});

Extracting data form another section with same ID (Appcelerator)

I have the following JSON: (its simplified a bit for you)
{ returnJSON = {
studentDataVOs = {
finalGrades = (
{
grade = A;
percent = 100;
sectionid = 7744;
reportingTermId = 801;
},
{
grade = B+;
percent = 89;
sectionid = 7745;
reportingTermID = 801;
});
reportingTerms = (
{
id = 801;
title = S1;
},
{
id = 802;
title = S2;
});
sections = (
{
id = 7744;
termID = 801;
courseTitle = Physics;
courseCode = 88A;
},
{
id = 7745;
termID = 801;
courseTitle = Government;
courseCode = 90B;
});
};
};
}
I am building an app using Appcelerator Titanium that displays a table view with the data hopefully showing the following:
Physics (88A) - S1 (Grade: A, 100%)
Government (90B) - S1 (Grade: B+, 89%)
...and so on...
I have the table view set up and the following code extracts the data from the sections and puts it in the labels of the table view:
var response = JSON.parse(response);
var sections = response.returnJSON.studentDataVOs.sections;
for (i=0;i<sections.length;i++) {
var courseName = sections[i].courseTitle;
var courseCode = sections[i].courseCode;
}
What I cannot figure out is how to go about fetching the grade, and term title for each individual class. As you can see, the data for each section contains an ID and termID, which direct me to a section in the finalGrades and reportingTerms that contains the ID or termID, where I need to fetch the final grades, percents, and term titles.
Can anyone help me with this? I have been trying on and off for two days trying to figure this out...
You should create indexes for each field you've listed. It's pretty simple:
//for example you have user list
var users = [{
id : 1, email : 'user#gmail.com',
nickname : 'John'
}, {
id : 2, email : 'user#stackoverflow.com',
nickname : 'Peter'
}];
//and you need to query user by his email, id and nickname
//first, create 3 index list
var usersIdIndex = {}, usersEmailIndex = {},
usersNicknameIndex = {};
//then fill them
users.forEach(function(user){
usersIdIndex[user.id] = user;
usersEmailIndex[user.email] = user;
usersNicknameIndex[user.nickname] = user;
});
//now you can get users by any of this fields
//for example
console.log(usersIdIndex[2].nickname== 'Peter');
console.log(usersNicknameIndex['John'].email == 'user#gmail.com');
Sections -> Final Grades
In the sections list, I would pass a variable in the tablerow that I could use to query for the next set of data. You need the section id to associate the data with the data in the final grade, so I would add it to my table row.
When creating your table:
// This loop to loop through the data.
function getSections(_args){
var response = JSON.parse(response);
var sections = response.returnJSON.studentDataVOs.sections;
for (i=0;i<sections.length;i++) {
createMyRow({
courseName: sections[i].courseTitle,
courseCode: sections[i].courseCode,
sectionId: sections[i].id
});
}
}
// This function creates the rows for the table
function createMyRow(_args){
// the sectionId is now included in the tableRow and can be used later for your
// next query.
// allows the sectionId value is now can be queried when a user clicks it
// through e.rowData.
var tableRow = Ti.UI.createTableViewRow({
sectionId: _args.sectionId
});
var title = Ti.UI.createLabel({
text: _args.courseName
});
tableRow.add(title);
return tableRow;
}
var tableView = Ti.UI.createTableView();
var data = [];
function refreshTable()
data = getSections();
var rows = [];
for( var i = 0; i<data.length; i++){
rows.push(createMyRow(data[i]);
}
tableView.setData(rows);
}
// this passes the value of the sectionId so you can use it to look up your next section.
tableView.addEventListener('click', function(e){
getFinalGrades({
sectionId: e.rowData.sectionId
})
});
function getFinalGrades(_args){
var finalGrades = response.returnJSON.studentDataVOs.finalGrades
var data = [];
for (i=0;i<finalGrades.length;i++) {
if(finalGrades[i].sectionId == _args.sectionId){
data.push({
grade: finalGrades[i].grade,
percent: finalGrades[i].percent
});
}
}
return data;
}
I hacked this together rather quickly looking at some of my code examples. At the end of the getFinalGrades function, you would have an array of finalGrades that were only from sectionId you clicked.

Categories