Managing/saving a (nested?) 2d data array with Angular - javascript

Let's say I have the following arrays:
var a = ["Sample A", "Sample B"];
var b = ["X", "Y", "Z"];
Now let's say I have a button that, when clicked, generates the header of a 2D table, like this:
Sample ASample BXYZ
...and another button that adds a new row to the table:
Sample ASample BXYZ
How would I do the following things:
save/restore data from/to the table
manage insertions/deletions from any of the arrays that form the header
Notes:
the elements of the arrays that form the header can be deleted in any order, but new elements can be appended at the end.
... however, I expect specs to change slightly so that the elements in those arrays can change their position.
EDIT: As suggested by users, I'm adding a plunkr example: http://plnkr.co/edit/FMIgwqKKDbZOCjNTXrrh?p=preview
I know this is flawed and wrong because it's tracking the data by it's index (which is error prone).

Related

Google Apps Script - how to reference object in loop in function

I am trying to create a small invoicing organization system using google sheets/drive. I have one sheet I call "tasks", where I plan to control everything from. Some of my columns include, "Client", "Project", "Requirements", "Details", "subcontractor"... As I acquire new tasks/clients, i'd find and append information respective of the task ("Project", "Requirements") to other sheets or, if none exist, create the folders, sheets, and append the respective necessary information from the "tasks" sheet to the new sheets. Some of the sheets will be sent to subcontractors, dependent on whether or not their tasks were updated or new ones were assigned to them in the original "tasks" sheet.
Within the sheets I send to subcontractors, there will be fields for them to fill out (rate, eta..), once filled, I will send that info to a third sheet to apply some margins, extra fees, and then send the info back to the original "tasks" sheet where it will fill appropriate cells.. Once all of the necessary information in a row is filled, it will be prepared and organized into an invoice for the client specified in the "client" column...
Anyways, i've been trying to learn javascript to implement all of this. As I plan to create folders, sheets, and append information based on the values entered in the rows and columns of the "tasks" sheet... I've placed a for loop in an onEdit function that does the following:
function onEdit(e) {
var ss = e.range.getSheet().getParent();
var sheet = e.range.getSheet();
var row = e.range.getRow();
var columns = [1, 2, 3, 4, 5, 6, 7, 8, 9];
//assign titles as 'keys' to array
var titles = []
//assign values of edited row to array
var values = []
//create an object to associate the title to the new edited values
var task = {}
for(var i in columns){
titles.push(sheet.getRange(1, columns[i]).getValue()); //push titles
values.push(sheet.getRange(row, columns[i]).getValue()); //push values of updated row
task[titles[i]] = values[i]; //add the values to their property names in task object
}
This works, and I can reference task["Client"], but i'd like to put this loop in a function so that I can use it again. I suppose I could do without it, but array "columns" only represents the columns I will be inputting on the "first round" --- when im sending information out...I will be inputting new information to columns 10-15, then 16-20, as the tasks progress.. and i'd like to run the for loop for those columns without having to create separate loops. To do this i've created the GetInfo function below:
function GetInfo(row,column){
for(var i in column){
titles.push(sheet.getRange(1, column[i]).getValue()); //push titles
values.push(sheet.getRange(row, column[i]).getValue()); //push values of updated row
this.task[titles[i]] = values[i]; //add the values to their task
}
}
What I am trying to accomplish is similar to what is outlined here. However, the "for(var..in..") is not mentioned in the examples and I think im missing something. In attempt to use the function for the first array of columns ive done this:
var list = new GetInfo(row,columns);
i'd like to reference the task as follows
list.task["client"]
Or var.task["name"], but the above doesn't work. When I toast list.task["Client] or try to append it to another cell, nothing happens - its blank. What am I doing wrong? How do I accomplish this correctly? What should I do?
Any help or guidance would be greatly appreciated. Please.
(other toasts are working, and the respective cell is not blank, without the function the for var in works)

Incorporating localStorage in a more complicated scenario (save multiple arrays)

I'm trying to implement local storage into my program.
Initially, the program was simple and had only 1 array to save. That array served as the container and the container then fed to #1 - the display output and to #2 - the console output...
old simpler program
pseudo-code to make the red box go away...
The above link eventually worked - as you can see on line 54 of the JS, as soon as a new entry is entered into the input box, the updated container was then saved to localStorage. And then upon returning to the site, per line 11 - if upon the initial loading of the program the saved localStorage container doesn't match the initial array, then the saved container is used.
However, I've changed the program drastically so that there are now multiple options to choose from and thus multiple arrays. Whichever option/array is chosen becomes the main container, and the main container still feeds out to the display output and the console output. See the updated program below...
multiple arrays = more complicated program
pseudo-code
The problem is that I am now having a difficult time figuring out how to implement localStorage on this new updated program. It was much easier previously when I only had 1 array to test against. Now, I can't just save the container any time a new entry is inputted (or removed) because the container can be any 1 of the 6 options.
Does anyone have any general ideas/pointers/suggestions as to what I can do?
The easiest (maybe not the most efficient) option would be to use an array of arrays. If before you had:
var container = ["a", "b", "c"];
localStorage.setItem("container", JSON.stringify(container));
You'd now have:
var container = [
["a", "b", "c"],
["d", "e", "f"]
];
localStorage.setItem("container", JSON.stringify(container));
Then for getting a single one in your new code, you'd just do:
var container = JSON.parse(localStorage.getItem('container')); //The same as before
var firstItem = container[0];
Hope this helps. Cheers
Why not just create a JSON object with multiple arrays, and save the entire stringified object in a localStorage variable instead of that single-dimensional array. It could even have a parameter referencing the selected container, and could be structured something like:
var containers = {
selectedContainer: "container1",
container1: ["text","contents","of","container1"],
container2: ["text","contents","of","container2"],
container3: ["text","contents","of","container3"],
container4: ["text","contents","of","container4"],
container5: ["text","contents","of","container5"],
container6: ["text","contents","of","container6"]
}
Content could then be pushed and popped to each of these container arrays, and the entire object saved in the single localStorage variable:
containers.container1.push("additional contents");
localStorage['containers'] = JSON.stringify(containers);
obj = JSON.parse(localStorage['containers']);
You could then reference the selected array with the selectedContainer parameter:
var container = (obj[obj.selectedContainer])

DataTables: How to bypass the filtering rules?

How can I exempt a single row in a DataTables.js table from DataTables' builtin filtering, so thta it is always shown?
Background: I'm building a table editing component using the jQuery-based DataTables.js library. Instead of using dialogs or overlays, I wanted to present editing controls right within the datatable, like this:
This works like a charm, even with active filters: I keep the original, unchanged data in the record while it is being edited, so I can use that data for the 'sort' and 'filter' modes of mDataProp, and my row stays in place and visible until editing is finished.
A bigger problem arises when I add a new row: There is no data to use for filtering, so if a filter is active, my row won't be visible. This breaks the workflow where the user searches through the dataset, sees that some record is missing, and (without clearing the filter) presses the "Add" button, waiting for an empty row with edit controls to appear:
How can I exempt this special row from DataTables' filtering?
After reading through the source code of DataTables.js for some time, I came to the conclusion that there is no way to hook into the filtering in the desired way. There are hooks for custom filters, but they can only be used to hide stuff, not to show stuff.
However, there's a 'filter' event which is triggered after filtering, but before the table is rendered. My solution installs an handler for this event:
$('table#mydatatable').bind('filter', function() {
var nTable = $(this).dataTable();
var oSettings = nTable.fnSettings();
//collect the row IDs of all unsaved rows
var aiUnsavedRowIDs = $.grep(oSettings.aiDisplayMaster, function(iRowID) {
var oRowData = nTable.fnGetData(iRowID);
return is_unsaved(oRowData);
});
//prepare lookup table
var oUnsavedRecordIDs = {};
$.each(aiUnsavedRowIDs, function(idx, iRowID) {
oUnsavedRecordIDs[iRowID] = true;
});
//remove unsaved rows from display (to avoid duplicates after the
//following step)
for (var i = oSettings.aiDisplay.length; i >= 0; i--) {
//iterate backwards, because otherwise, removal from aiDisplay
//would mess up the iteration
if (oUnsavedRecordIDs[ oSettings.aiDisplay[i] ]) {
oSettings.aiDisplay.splice(i, 1);
}
}
//insert unsaved records at the top of the display
Array.prototype.unshift.apply(oSettings.aiDisplay, aiUnsavedRowIDs);
//NOTE: cannot just say oSettings.aiDisplay.unshift(aiUnsavedRowIDs)
//because this would add the array aiUnsavedRowIDs as an element to
//aiDisplay, not its contents.
});
What happens here? First, I find all unsaved rows by looking through oSettings.aiDisplayMaster. This array references all rows that are in this DataTable, in the correct sorting order. The elements of aiDisplayMaster are integer indices into DataTables' internal data storage (one index per row).
The filtering process goes through the rows in aiDisplayMaster, and places the row IDs of all matching rows in oSettings.aiDisplay. This array controls which rows will be rendered (after this event handler has finished!). The whole process looks like this:
[1, ..., numRows]
|
| sorting
v
oSettings.aiDisplayMaster
|
| filtering
v
oSettings.aiDisplay
|
| rendering
v
DOM
So after having located all unsaved records in aiDisplayMaster (using custom logic that I wrapped in an is_unsaved() function for the sake of this snippet), I add them all to aiDisplay (after removing existing instances of these rows, to avoid duplicates).
A side-effect of this particular implementation is that all unsaved rows appear at the top of the table, but in my case, this is actually desirable.

many hidden divs or replacing content of single div?

When the user moves their cursor over different regions of an image on my page I am updating the content of a table which gives more detail to them. This table content is generated server side.
At the moment, I am storing the 50 or so different tables within their own divs which are hidden until the respective mouseover event.
Is this the best way of achieving this? Is it better to use javascript to just replace the table content of a single div?
Thanks, A.
Well, if it's 50 identical tables (structure-wise) with unique content, I'd probably settle for a custom object or something like that, instead of hiding 50 tables (50 tables = overhead in comparison versus 1 table).
Try the following (somewhat pseudo) using jQuery:
var data_rows = $('#table').children('tr');
var region_information = {
0: { name: "Foo", location: "Loo"},
1:{ name: "Bar", location: "Car" },
2{ name: "Car", location: "Garage"}
};
$('.regions').hover(
function() {
//Store the region for *performance*
var this_region = $('this');
/*
Set the values in the table by getting the field corresponding to the substring
The format = region-{n}, where {n} is a positive digit
Repeat this `n` times, according to how many "values" you need to display per table
*/
data_rows.children('field1').text(
region_information[this_region.attr('name').substring(7, 1)]
);
}
);
Did this make sense? AS I don't know what your table looks like or anything, I can't quite tell you exactly how to do it. I can however provide you with a pseudo-like code (like the one above), so I hope it helps you out!

Strategy to extract structured data with xpath

Is there a pattern to extract structured data from an HTML page using XPath? I'm trying to extract data from one or more HTML tables on a page. XPath makes it easy to find the table(s), but I'm struggling once I've got that far.
I'm currently doing the following:
Iterate the tables (there may be more than one)
Iterate the rows within that table
Iterate the cells within that row
(Then probably put them in an array and parse the contents)
My code is something like this:
var tables = mydoc.evaluate( "//table", mydoc, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null );
table = tables.iterateNext();
while (table)
{
var rows = mydoc.evaluate("tbody/tr", table, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
row = rows.iterateNext();
while (row)
{
var tds = mydoc.evaluate("td", row, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null)
td = tds.iterateNext()
while(td)
{
// TODO: store content in an array to process later
print('*' + td.textContent);
td = tds.iterateNext();
}
row = rows.iterateNext();
}
table = iterator.iterateNext();
}
This seems a little nasty as all the XPath examples seem to do their processing in one step. There appear to be few non-trivial examples where two types of data (e.g. labels and values in a table) are selected and combined. I can use the following selectors, but I end up with two lists with no structure:
//table/tbody/tr/td[#class='label']
//table/tbody/tr/td/a[#class='value']
(I know I'm using XPath for HTML parsing for which it wasn't really intended, but it seems to work so far.)
There appear to be few non-trivial
examples where two types of data (e.g.
labels and values in a table) are
selected and combined. I can use the
following selectors, but I end up with
two lists with no structure:
//table/tbody/tr/td[#class='label']
//table/tbody/tr/td/a[#class='value']
Use:
//table/tbody/tr/td[#class='label']
|
//table/tbody/tr/td/a[#class='value']
This single XPath expression selects all the wanted nodes (all XPath engines I am aware of return the selected nodes in document order). The | (union) operator produces the set union of its arguments.
If the (x)Html document has regular structure, you may expect in the returned result every selected td element (label) to be followed by its corresponding a element (value)
If it's on the main HTML page, you could just do:
for(var tables=document.getElementsByTagName("table"),i=0;i<tables.length;++i)
for(var rows=tables[i].getElementsByTagName("tr"),j=0;j<rows.length;++j)
for(var cells=rows[j].getElementsByTagName("td"),k=0;k<cells.length;++k)
print("*"+cells[i].textContent);
getElementsByTagName does /not/ return an array - it returns a live NodeList similar to ORDERED_NODE_ITERATOR_TYPE.

Categories