Handsontable editing nested array - javascript

I am using handsontable and am having trouble getting the "beforeChange" and "afterChange" events to fire consistently, which I'm hoping use to commit updates to the database. I am using the following code (version 0.16.1):
HTML:
<div id="table"></div>
<div id="output"></div>
JavaScript:
var data = [{
id: 5,
name: 'Sedan',
price: 2000,
tags: ['pink', 'purple']
}, {
id: 6,
name: 'Truck',
price: 1500,
tags: ['green', 'blue']
}, {
id: 6,
name: 'SUV',
price: 1500,
tags: null
}];
var writeMessage = function(msg) {
var output = document.getElementById("output");
var div = document.createElement('DIV');
div.innerHTML = msg;
output.insertBefore(div, output.firstChild);
console.log(msg);
};
var tableDiv = document.getElementById("table");
this.table = new Handsontable(tableDiv, {
data: data,
colHeaders: ["id", "name", "price", "tags"],
columns: [{
data: "id"
}, {
data: "name"
}, {
data: "price"
}, {
data: "tags"
}],
beforeChange: function(changes, source) {
writeMessage("beforeChange: " + changes + ": " + source);
},
afterChange: function(changes, source) {
writeMessage("After Change fired: " + changes);
if (!changes) {
return;
}
var i, idx, key, newVal, modelID;
for (i = 0; i < changes.length; i++) {
idx = changes[i][0];
key = changes[i][1];
newVal = changes[i][3];
modelID = this.getDataAtRow(idx)[0];
writeMessage("New value: " + key + ": " + newVal);
}
}
});
http://codepen.io/anon/pen/GjzrdX?editors=0010
The event handlers fire when I'm editing the text and number fields and for the when tags are null, but do not fire for data objects with tag arrays (e.g. pink,purple; green,blue). How do I get the events to fire for the tag cells without modifying the data structure? Any advice would be greatly appreciated!

I believe that you are facing a bug here when trying to put an Array in a Cell but I cannot find anywhere in the handsontable documentation or any thread in their GitHub mentioning this issue... IMO, putting an Array in a Cell is suppose to be use as Source and not Data, which result in a cell that you can't edit (hence the events afterChange/beforeChange not triggered). In your example the third line is working because of the 'null' value which is not an Array.
Anyway, the only workaround I managed to do for you is to modify your data after you define your data structure (in order to respect your condition, but I strongly advice do modify them anyway because you will need to do that eventually).
Assuming that your tags can only contain two values :
var data1 = [];
for (var i=0; i<data.length;i++) {
if (data[i].tags != null) {
var temp = data[i].tags[0];
temp = temp.concat(',');
temp = temp.concat(data[i].tags[1]);
} else var temp = null;
data1.push({ id: data[i].id, name: data[i].name, price: data[i].price, tags: temp });
}
If the length of your Arrays tags can be more than that, just do a second loop to cover it.
See your code here with this solution implemented
You then can use data1 to load your table. If you need to save your data after modification, you can use a similar function to reverse it into your original data structure.

Related

Display pictures based on specific student ID

So after pulling all the information from my sql tables, I put them into 2 variables called response1 and response2.Shown below are what's currently inside the arrays. My goal is to display the pictures, beside each student. If a picture doesn't exsist, it can just be blank for now. So i've gotten the picture from DB to display by using base64 encode, but i'm only currently doing it for the id:20000001, since i'm not sure how to do it for other ones.
I had an idea to maybe replace
student_image= findItemByID(response2,'20000001');
with
student_image= findItemByID(response2,element.id); ?
but not sure if this is correct logic?
Inside my Object.keys final loop, how should I fix html+='<img src="data:image/jpeg;base64,'+student_image.photo+'">' so that it adds the image of the specific id?
Response1 array of object:
0: {Class_Student_Group: 1, names: "Lastname1, Firstname1", id: 20000001}
1: {Class_Student_Group: 2, names: "Lastname2, Firstname2", id: 20000002}
2: {Class_Student_Group: 3, names: "Lastname3, Firstname3", id: 20000003}
3: {Class_Student_Group: 5, names: "Lastname4, Firstname4", id: 20000004}
4: {Class_Student_Group: 3, names: "Lastname5, Firstname5", id: 20000005}
Response2 array of objects:
0: {student_id: "20000001", photo: ""}
1: {student_id: "20000001", photo: "/9j/4AAQSkZJRgABAQAAAQABAAD/4QB6RXhpZgAASUkqAAgAAA…9a1EtFKAnd09f/rVlwfdFbVw7LPIoOAGNYwjc/SIaxR//2Q=="}
2: {student_id: "20000002", photo: ""}
3: {student_id: "20000003", photo: ""}
final:
1: Array(4)
0: {Class_Student_Group: 1, names: "Lastname1, Firstname1", id: 20000001}
1: {Class_Student_Group: 1, names: "Lastname9, Firstname9", id: 20000009}
2: {Class_Student_Group: 1, names: "Lastname15, Firstname15", id: 20000015}
3: {Class_Student_Group: 1, names: "Lastname19, Firstname19", id: 20000019}
length: 4
__proto__: Array(0)
2: (4) [{…}, {…}, {…}, {…}]
3: (4) [{…}, {…}, {…}, {…}]
output so far:
<script type="text/javascript">
$(document).ready(function() {
var response= <?php echo json_encode($table); ?>;
var response2= <?php echo json_encode($result); ?>;
function findItemByID(arr, id) {
return arr.find((photoObj) => {
return photoObj.photo.trim().length !== 0 && photoObj.student_id === id
})
}
var final = {}
response.forEach(function(element) {
final[element.Class_Student_Group] = final[element.Class_Student_Group] || [];
final[element.Class_Student_Group].push(element);
});
let student_image =''
Object.keys(final).forEach(function(Group) {
let html = '';
//add the table opening tag to the html variable
html += '<table>';
//Append the 'category' header
html += '<tr>';
html += '<td><h1>'+"Group"+ Group+'</h1></td>';
html += '</tr>';
// Loop through the results for this 'category'
student_image= findItemByID(response2,'20000001');
final[Group].forEach(function(element) {
var randomImage = images[Math.floor(Math.random() * images.length)];
var names = element.names;
var id=element.id;
var img = "<img src=' " + randomImage+ " ' />";
html += '<tr>';
html += '<td><p>' + names + '</p></td> ';
html += '<td><p>' + id + '</p></td> ';
html+='<img src="data:image/jpeg;base64,'+student_image.photo+'">'
html += '</tr>';
});
//add the table closing tag to the html variable
html += '</table>';
$('#tables_container').append(html);
});
});
</script>
OK. Quite a few things going on here.
Basically the reason(s) the findItemByID() function wasn't working was because A. (and I have to assume you knew this) you were passing in the same ID every time it was called, B. by putting it before you called final[Group].forEach(function(element) you couldn't pass in the right value for the id, as the id is a property of that element variable, and C. === is a strict equality operator, which means it only returns true if the values are the same and they are the same type. In one array of objects that value is a string and in the other it's a number.
You are doing a lot of things out of order. Take a step back and try to think about what this code is intended to do and write it one step at a time, and break your steps into functions whenever practical like I have done here. Look over the changes I have made to your code, and the comments I added. Generally when writing code you want to use functions to make things more readable, and break them apart so you are dealing with the least amount of complexity as possible at any given time. A table works like table - > row - > column -- write your code that way.
I don't know anything about using Base64 for images in HTML, but a look at this link suggests that the data you have supplied here won't work. Check out the decoder in the accepted answer.
I changed the way you were iterating over your final object. This is probably mostly a matter of taste, but to me it's more readable. You can learn more about it here
Use better names! It's so much easier when you're trying to figure out why something is broken if you aren't also trying to remember what data is in response vs response2. Just call them studentDataArray and imageDataArray or something.
$(document).ready(function () {
loadTable();
});
let array1 = [
{ Class_Student_Group: 1, names: "Lastname1, Firstname1", id: 20000001 }
, { Class_Student_Group: 2, names: "Lastname2, Firstname2", id: 20000002 }
, { Class_Student_Group: 3, names: "Lastname3, Firstname3", id: 20000003 }
, { Class_Student_Group: 5, names: "Lastname4, Firstname4", id: 20000004 }
, { Class_Student_Group: 3, names: "Lastname5, Firstname5", id: 20000005 }
]
let array2 = [{ student_id: "20000001", photo: "" }
, { student_id: "20000001", photo: "/9j/4AAQSkZJRgABAQAAAQABAAD/4QB6RXhpZgAASUkqAAgAAA…9a1EtFKAnd09f/rVlwfdFbVw7LPIoOAGNYwjc/SIaxR//2Q==" }
, { student_id: "20000002", photo: "" }
, { student_id: "20000003", photo: "" }];
function findItemByID(arr, id) {
return arr.find((photoObj) => {
return photoObj.photo.trim().length !== 0 && photoObj.student_id == id // because the id property in the student object is a number and in the photo array it's a string, change this to '==', which evaluates '2' as equal to 2. === only returns true if they are the same type
//also you have been checking for a student_id property here, which doesn't exist on the second array's objects, so this wasn't returning anything
})
}
function getFinalObject(response) {
var final = {}
response.forEach(function (element) {
final[element.Class_Student_Group] = final[element.Class_Student_Group] || [];
final[element.Class_Student_Group].push(element);
});
return final;
}
function appendHtmlToDoc(final, response2, images) {
let html = '';
//add the table opening tag to the html variable
html += '<table>';
for (let Group in final) { //this will loop through final's properties, in this case, the 'groups'
if (final.hasOwnProperty(Group)) { //use hasOwnProperty to make sure you aren't looping through irrevelant properties (https://stackoverflow.com/questions/8312459/iterate-through-object-properties)
//Append the 'category' header
html += '<tr>';
html += '<td><h1>' + "Group" + Group + '</h1></td>';
html += '</tr>';
// Loop through the results for this 'category'
final[Group].forEach(function (element) { //you already have the Group array in the Group variable
let student_image = findItemByID(response2, element.id); //search for the image data using this element's id property
// var randomImage = images[Math.floor(Math.random() * images.length)]; //should pass 'images' into this function, but I don't know where it came from and randomImage/img isn't used in the code you provided, so I removed it
var names = element.names;
var id = element.id;
// var img = "<img src=' " + randomImage + " ' />";
html += '<tr>';
html += '<td><p>' + names + '</p></td> ';
html += '<td><p>' + id + '</p>';
if (student_image) { //check if something was returned when you searched for the photo, otherwise you'll have a blank image here
html += '<td><img src="data:image/jpeg;base64, ' + student_image.photo + '"></td>'; //I don't know anything about using images in HTML with base64, but a look at this link (https://stackoverflow.com/questions/8499633/how-to-display-base64-images-in-html) suggests that what you have provided won't work
}
//I also added a td around your img tag
html += '</tr>';
});
}
}
//add the table closing tag to the html variable
html += '</table>';
//console.log(html)
$('#tables_container').append(html);
}
function loadTable() {
//move your php script calls here
let finalObj = getFinalObject(array1);
appendHtmlToDoc(finalObj, array2);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="tables_container"></div>
Try:
if(!photo.id) {
// There's no image
} else {
// There's an image
}

How to remove duplicate values from dynamic select in Javascript?

trying to figure this out with no such luck. Basically we populating a select with values from the service that it's being retrieved from. But there are duplicates in the select. Here's the code that's doing it. I'd like to remove the duplicates from what's getting returned that are in the "theProduct.name". I know this question has been asked before but I can't figure this out. The image attached is the select where it's happening. Thanks
function populateSearchProducts(data) {
var theData = data.data.results;
$field.empty().append('<option value=""> </option>');
for (var p in theData) {
var theProduct = theData[p];
$field.append('<option value="'+theProduct.id+'">'+theProduct.name+'</option>');
}
}
Try a filter to remove duplicates from the input data:
function populateSearchProducts(data) {
var theData = data.data.results.filter(function(item, pos, self) {
return self.indexOf(item) == pos;
});
$field.empty().append('<option value=""> </option>');
for (var p in theData) {
var theProduct = theData[p];
$field.append('<option value="'+theProduct.id+'">'+theProduct.name+'</option>');
}
}
function populateSearchProducts(data) {
data = data.data.results;
const dupes = new Set();
for(const {name, id} of Object.values(data)){
if(dupes.has(name)) continue;
$field.append(`<option value='${id}' > ${name} </option>`);
dupes.add(name);
}
}
You can keep track of the items already added to the DOM and then use a filter before adding new elements.
In the code below, the filter is looking at the id of each element to filter them out. If you want, you could use name (or any other attribute) to detect the duplicates and filter them out.
Something along the lines of:
var dataArray = [
{id: 1, name: 'one'},
{id: 2, name: 'two'},
{id: 3, name: 'three'},
{id: 4, name: 'four'},
{id: 2, name: 'two'}, // dupe
{id: 5, name: 'five'},
{id: 3, name: 'three'} // dupe
]
function populateSearchProducts(data, $field) {
//var theData = data.data.results;
var theData = data;
$field.empty().append('<option value=""> </option>');
// to keep track of the ones already in the drop-down
var alreadyAdded = [];
for (let p of theData) {
if(alreadyAdded.filter( item => item.id === p.id ).length <= 0 ){
$field.append('<option value="'+p.id+'">'+p.name+'</option>');
alreadyAdded.push(p);
}
}
}
// when docuemnt ready, populate the field
$(function(){
var field = $("#selOptions");
populateSearchProducts(dataArray, field);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<select id="selOptions" />
updated based on comments

How to loop over checked boxes and add to array

I am needing to loop over all of the checked check boxes of a specific name and add the values of that row to an array. My final array needs to look like this:
stmtData = {
sections: [
{ sectionCode: "AA", sectionName: "AA Test", amount: "33" },
{ sectionCode: "BB", sectionName: "BB Test", amount: "55" }
]
};
Looping over the checkboxes is the easy part:
var stmtData = [];
$.each($("input:checkbox[name='sectionElection']:checked"), function () {
// create sections array here
});
I'm getting the data like this, but there may be a better way?
stmtData["sectionCode"] = $(this).val();
stmtData["sectionName"] = $("#sectionElectionLbl_" + $(this).val()).text();
stmtData["amount"] = $("#sectionCost_" + $(this).val()).text();
You could improve it using map instead of each, bit rusty on jQuery but something like?
var stmtData = {}
stmtData.section = $("input:checkbox[name='sectionElection']:checked")
.map(function() {
var val = $(this).val();
var text = val.text();
return {
sectionCode: val,
sectionName = $("#sectionElectionLbl_" + text,
amount: text
}
});
Ok, I knew I had to use push somehow, figured it out and this works just fine.
var stmtData = [];
$.each($("input:checkbox[name='sectionElection']:checked"), function () {
// create sections array here
stmtData.push({
sectionCode: $(this).val(),
sectionName: $("#sectionElectionLbl_" + $(this).val()).text(),
amount: $("#sectionCost_" + $(this).val()).text()
});
});

drawing a table from a json structure coming as array using jquery or javascript

I am getting a JSON object. I have to print a tabular structure from that. After debugging , i have found the object is coming as
tableData = {title: "Schema", cellValues: Array[2], id: 1, name: "CSV", type: "table"}
When i do further debugging then i found following result
var tableRows = tableData.cellValues;
tableRows = [Array[4], Array[4]]
If i do a console.log(tableRows) it gives as --> c1,c2,c3,c4,DoubleType,DoubleType,DoubleType,DoubleType
I have to print it in a tabular form as
c1 count c1 count
DoubleType LongType DoubleType LongType
I am not able to understand how should i do that.How to manipulate this to get the table structure. Some help will be really appreciated. thanks..
I might try something like this. I've used three row of data including the heading data to broaden the demo.
var tableData = {
cellValues: [
['c1', 'c2', 'c3', 'c4'],
['DoubleType', 'longType', 'DoubleType', 'longType'],
['Bob', 'Dave', 'Bob', 'Dave']
]
}
Shift the first array in cellValues into headerData. bodyData is what remains.
var headerData = tableData.cellValues.shift();
var bodyData = tableData.cellValues;
Hashmap-based helper function that delivers lines of cells based on type.
function buildCells(data, type) {
return {
header: '<th>' + data.join('</th><th>') + '</th>',
row: '<td>' + data.join('</td><td>') + '</td>'
}[type];
}
Helper function that builds each data row.
function buildRow(data) {
return ['<tr>', buildCells(data, 'row'), '</tr>'].join('');
}
Build the set of row data.
var rows = bodyData.reduce(function(p, c) {
return p.concat(buildRow(c));
}, []);
Pad out the header, body, and finally, table with the appropriate tags
var header = ['<thead>', buildCells(headerData, 'header'), '</thead>'].join('');
var body = ['<tbody>', rows.join(''), '</tbody>'].join('');
var table = ['<table>', header, body, '</table>'].join('');
Now you can do what you like with the HTML. In the example I use insertAdjacentHTML to add it to an element.
DEMO

Jquery: Autocomplete with label

I am trying to learn website development.
While learning autocomplete feature of jquery, I tried to put in the labels.
function autocomplete (data) {
var data = data.toString();
var availableTags = data.split(',');
var autocompleteData = [];
for (var i = 0; i < availableTags.length; i++){
autocompleteData[i] = {};
autocompleteData[i].label = i.toString();
autocompleteData[i].value = availableTags[i];
}
$("#tags").autocomplete({
source: autocompleteData,
select: function (event, ui) {
printautocomplete(event, ui)
}
});
};
The autocomplete[i].value is a valid string.
autocompleteData[0]
Object {label: 0, value: "Peter"}
However, I do not see any suggestions.
What is wrong with the way I am using the API?
The API says:
"Array: An array can be used for local data. There are two supported formats:
An array of strings: [ "Choice1", "Choice2" ]
OR An array of objects with label and value properties: [ { label: "Choice1", value: "value1" }, ... ]
The label property is displayed in the suggestion menu. The value will be inserted into the input element when a user selects an item. If just one property is specified, it will be used for both, e.g., if you provide only value properties, the value will also be used as the label. "
Thank you.
$('#sidebarSearch').autocomplete(
{
source: function(query, result)
{
var query = $('#sidebarSearch').val ();
$.ajax(
{
url:"sidebarSearchFetch.php",
method:"POST",
data:{query:query},
dataType:"json",
success:function(data)
{
result($.map(data, function(item)
{
return {
label: item.name,
value: item.usrId
};
}));
}
})
},
appendTo: "#sidebar-form"
});
I am skeptical of line 2 in your code (var data = String()data;) I would use: var data = data.toString();
But if you are sure that the autocompleteData elements do indeed have valid strings, then my best guess would be that perhaps you forgot to give the '#tags' id to your html entry field element.
Finally, if this is not it, to troubleshoot, I would try removing the select: option from the object you are passing to autocomplete() in the line that begins: $("#tags").autocomplete(... so that only the source options is passed.
Another thing to check out is when the code is being run. It is possible that a document.ready() function is needed to ensure that that when the autocomplete feature is added to the DOM element with the id '#tags', that the element has already been created.
The autocomplete works fine. Instead of completing "value", it completes "label".
So when I type in "1", it suggests "1", "10", "11", etc.
Autocomplete applying value not label to textbox answers how to change to to by-value.

Categories

Resources