Tridimensional array solution javascript - javascript

I am reading information from a database and I want the following structure: continent->Country->City. I am getting the data throughout PHP. I want to store that data in a tridimensional javascript array (or atleast that is what i am trying to do since yesterday).
I don't know if the following is posible:
var triArray[0] = ["Africa"];
var triArray[0][0] = ["Niger"];
var triArray[0][0][0] = ["Abuya"];
The idea is to make those arrays through PHP and use the data to fill them.
I "need" (I think, I am not and expert) a tridimensional to then see which city belongs to which country and where that country is located, using a for loop.
<ul
<li>Africa
<ul>
<li>Niger
<ul>
<li>Abuya</li>
</ul>
<li>
</ul>
</li>
</ul>
I don't know if you get what I want, thanks in advance.

Consider a more object-oriented approach - this should be easy, since continents/countries/cities are things.
function Continent(name) {
this.name = name;
this.countries = [];
}
Continent.prototype.addCountry = function(country) {
if( !(country instanceof Country)) throw new Error("Not a country");
this.countries.push(country);
// may want to add logic for duplicate checking
}
You can build function Country and function City in a similar way.
Now that that's done, and you've built your structure, you can output it. Maybe something like:
Continent.prototype.toString = function() {
var list = "<li>" + this.name,
l = this.countries.length, i;
if( l > 0) {
list += "<ul>";
for( i=0; i<l; i++) {
list += this.countries[i]; // toString implicitly called
}
list += "</ul>";
}
list += "</li>";
return list;
}
Add similar functions to Country and City.
Now you can output the continent, say in someULelement.innerHTML, and it will render as desired.
You may want to have a function World to act as an overall container.
Object-oriented code can make tasks like this much easier to understand visually.
// ------------------ BEGIN CLASS DEFINITIONS ---------------------
function World() {
this.continents = [];
}
World.prototype.addContinent = function(continent) {
if (!(continent instanceof Continent)) throw new Error("Not a continent");
this.continents.push(continent);
}
World.prototype.toString = function() {
var list = "<ul>",
l = this.continents.length,
i;
if (l > 0) {
for (i = 0; i < l; i++) {
list += this.continents[i]; // toString implicitly called
}
}
list += "</ul>";
return list;
}
function Continent(name) {
this.name = name;
this.countries = [];
}
Continent.prototype.addCountry = function(country) {
if (!(country instanceof Country)) throw new Error("Not a country");
this.countries.push(country);
// may want to add logic for duplicate checking
}
Continent.prototype.toString = function() {
var list = "<li>" + this.name,
l = this.countries.length,
i;
if (l > 0) {
list += "<ul>";
for (i = 0; i < l; i++) {
list += this.countries[i]; // toString implicitly called
}
list += "</ul>";
}
list += "</li>";
return list;
}
function Country(name) {
this.name = name;
this.cities = [];
}
Country.prototype.addCity = function(city) {
if (!(city instanceof City)) throw new Error("Not a city");
this.cities.push(city);
}
Country.prototype.toString = function() {
var list = "<li>" + this.name,
l = this.cities.length,
i;
if (l > 0) {
list += "<ul>";
for (i = 0; i < l; i++) {
list += this.cities[i]; // toString implicitly called
}
list += "</ul>";
}
list += "</li>";
return list;
}
function City(name) {
this.name = name;
}
City.prototype.toString = function() {
return "<li>" + this.name + "</li>";
}
// ------------------ END CLASS DEFINITIONS ---------------------
var world = new World(),
africa = new Continent('Africa'),
niger = new Country('Niger'),
abuya = new City('Abuya');
world.addContinent(africa);
africa.addCountry(niger);
niger.addCity(abuya);
document.body.innerHTML = world;

This is not possible, if triArray[0] is a string it can't be an array of strings.
You should go for a structure like this :
{
continents : [
{
name:"Africa",
countries : [
{
name : "Niger",
cities : [
"Abuya" , "", ...
]
},
...
]
},
...
]
}
And you could access it like this :
var continents = data["continents"];
for (var i =0,len = continents.length; i<len;i++){
var continentData = continents[i];
var continentName = continentData["name"];
var listCountries = continentData["countries"];
for (var y =0,leny = listCountries.length; y<leny;y++){
var countryData = listCountries[y];
var countryName = countryData["name"];
// List of cities
var cities = countryData["cities"];
}
}
Edit:
added for informations, this is possible but imply some javascript knowledges and its string representation would be different than a simple array:
var world = [];
world.name = "world";
world.push([])
world[0].name = "Africa";
world[0].push([]);
world[0][0].name = "Niger";
world[0][0].push("Abuya");
document.write(world[0].name);
document.write(world[0][0].name);
document.write(world[0][0][0]);

Related

JavaScript build nested array from string values

From my data source I am getting values like;
USA |Arizona
USA |Florida
UK |England |Northamptonshire
UK |England |Derbyshire
UK |Wales |Powys
Switzerland|Lucern
These are flat text values that repeat in a column.
I need to build them dynamically into nested array
source: [
{title: "USA", children: [
{title: "Arizona"},
{title: "Florida"}
]}
],
As per https://github.com/mar10/fancytree/wiki/TutorialLoadData
Unfortunately my brain has stopped working today I am can't see a elegant way.
Any pointers would be most gratefully appreciated.
So I solved this eventually using a post from Oskar
function getNestedChildren(arr, parent) {
var out = []
for(var i in arr) {
if(arr[i].parent == parent) {
var children = getNestedChildren(arr, arr[i].id)
if(children.length) {
arr[i].children = children
}
out.push(arr[i])
}
}
return out
}
http://oskarhane.com/create-a-nested-array-recursively-in-javascript/
This builds the nested array.
To ensure inferred values were present (e.g. USA which is in the hierarchy but is not a unique value).
var CountryArray = CountryText.split("|");
// Variables to hold details of each section of the Country path being iterated
var CountryId = '';
var CountryParentPrefix = '';
var CountryParent = '';
// Iterate each section of the delimeted Country path and ensure that it is in the array
for(var i in CountryArray)
{
var CountryId = CountryParentPrefix+CountryArray[i];
// Find the Country id in the array / add if necessary
var result = FlatSource.filter(function (Country) { return Country.id == CountryId });
if (result.length == 0) {
// If the Country is not there then we should add it
var arrCountry = {title:CountryArray[i], parent:CountryParent, id:CountryId};
FlatSource.push(arrCountry);
}
// For the next path of the heirarchy
CountryParent = CountryId;
CountryParentPrefix = CountryId+'|';
}
I did not use Sven's suggestion but I suspect that it is equally valid.
Turn it to JSON:
var str = '"USA|Arizona","USA|Florida","UK|LonelyIsland","UK|England|Northamptonshire","UK|England|Derbyshire","UK|Wales|Powys","UK|England|London|Soho","Switzerland|Lucern';
var jsonStr = "[[" + str.replace(/,/g,'],[') + "\"]]";
jsonStr = jsonStr.replace(/\|/g,'","');
var nested = JSON.parse(jsonStr);
Then play with parents and children.
function findObject(array, key, value) {
for (var i=0; i<array.length; i++) {
if (array[i][key] === value) {
return array[i];
}
}
return null;
}
function obj(arr){
this.title = arr.shift();
}
obj.prototype.addChild = function(arr){
var tmp = new obj(arr);
if(typeof this.children === 'undefined'){
this.children = new Array();
result = this.children[this.children.push(tmp)-1];
}else{
result = findObject(this.children, 'title', tmp.title);
if(!result)
result = this.children[this.children.push(tmp)-1];
}
return result;
}
obj.prototype.addChildren = function(arr){
var obje = this;
while(arr.length>0)
obje = obje.addChild(arr);
}
var finArr = [];
for(i=0; i<nested.length; i++){
var recc = new obj(nested[i]);
if(oldObj = findObject(finArr, 'title', recc.title)){
oldObj.addChildren(nested[i]);
}else{
if(nested[i].length>0)
recc.addChildren(nested[i]);
finArr.push(recc);
}
}
console.log('------------------------------------------')
console.log(JSON.stringify(finArr));
console.log('--------------------The End---------------')

Array of objects to database and back

I have array of functions/objects which I want to store to database, like this example:
function classPerson(firstName, lastName, activeStatus) {
this.firstName = firstName;
this.lastName = lastName;
this.activeStatus = activeStatus;
this.identify = function () {
return this.firstName + " " + this.lastName;
}
} // element
var persons = [];
var personsFromDatabase = [];
// define persons
var personOne = new classPerson('Bruce', 'Lee', true);
var personTwo = new classPerson('Chuck', 'Norris', false);
var personThree = new classPerson('Steven', ' Seagal', true);
// add persons to array
persons.push(personOne);
persons.push(personTwo);
persons.push(personThree);
// show persons data
for (var i = 0; i < persons.length; i++) {
alert(persons[i].identify());
}
// store to database
var toDatabase = JSON.stringify(persons);
alert(toDatabase);
// retrieve from database
var personsFromDatabase = JSON.parse(toDatabase);
// show persons data parsed from database
for (var i = 0; i < personsFromDatabase.length; i++) {
alert(personsFromDatabase[i].identify());
}
I transform persons array to string with JSON.stringify command and successfully store it to database.
When I load same string from database and transform back with JSON.parse to JS function/object I get list of simple objects (and error
TypeError: personsFromDatabase[i].identify is not a function
) instead of classPerson function/object and in console I can see that difference, like on picture below:
How can I achieve to get array of functions/objects instead of simple JS objects?
Fiddler link with example
You cannot save function in JSON, because function does not exist in JSON
But you can use second argument of stringify function to replace the function with value.
Like
var json = JSON.stringify(obj, function(key, value) {
if (typeof value === 'function') {
return value.toString();
} else {
return value;
}
});
As mentioned above, JSON has no functions as data types. You can only serialize strings, numbers, objects, arrays, and booleans (and null):
I have altered your example to provide a method to serialize and deserialize - which can be be as a basic template:
function ClassPerson(firstName, lastName, activeStatus) {
this.firstName = firstName;
this.lastName = lastName;
this.activeStatus = activeStatus;
this.identify = function () {
return this.firstName + " " + this.lastName;
}
} // element
ClassPerson.prototype.toJson = function() {
var data = {};
for(var prop in this) {
if(this.hasOwnProperty(prop) && (typeof this[prop] !== 'function')) {
data[prop] = this[prop];
}
}
return JSON.stringify(data);
};
ClassPerson.fromJson = function(json) {
var data = JSON.parse(json); // Parsing the json string.
if(data) {
var firstName = data.hasOwnProperty('firstName') ? data.firstName : "";
var lastName = data.hasOwnProperty('lastName') ? data.lastName : "";
var activeStatus = data.hasOwnProperty('activeStatus') ? data.activeStatus : "";
return new ClassPerson(firstName, lastName, activeStatus);
}
return {};
};
function serializeClassPersons(personArray) {
var serialised = [];
for (var i = 0; i < personArray.length; i++) {
serialised.push(persons[i].toJson());
};
return JSON.stringify(serialised);
};
function deserializeClassPersons(personsJsonString) {
var jsonStringArray = JSON.parse(personsJsonString); // this is an array
var persons = [];
for (var i = 0; i < jsonStringArray.length; i++) {
persons.push(ClassPerson.fromJson(jsonStringArray[i]));
};
return persons;
};
// add persons to array
var persons = [
new ClassPerson('Bruce', 'Lee', true),
new ClassPerson('Chuck', 'Norris', false),
new ClassPerson('Steven', ' Seagal', true)
];
var personsFromDatabase = [];
// show persons data
console.log('Using ClassPerson.identify():');
for (var i = 0; i < persons.length; i++) {
console.log(persons[i].identify());
};
console.log('Using ClassPerson toJson() and fromJson()');
for (var i = 0; i < persons.length; i++) {
var jsonPerson = persons[i].toJson();
console.log("json", jsonPerson);
var personFromJson = ClassPerson.fromJson(jsonPerson);
console.log("identify: ", persons[i].identify());
};
console.log('Serialize Persons Array to Json String');
var personsJson = serializeClassPersons(persons);
console.log(personsJson);
console.log('DeSerialize Json Persons String to Array');
var personsFromDatabase = deserializeClassPersons(personsJson);
console.log(personsFromDatabase);
The Output of this is:

Emulate string split() from Javascript Object Oriented Programing Stoyan Stefanov book

Im learning JS OOP from Stoyan Stefanov's book. I got problem with exercise 4 in chapter 4:
Imagine the String()constructor didn't exist. Create a constructor
function MyString()that acts like String()as closely as possible.
You're not allowed to use any built-in string methods or properties,
and remember that String()doesn't exist. You can use this code to test
your constructor:
Below is my attempt for creating String split() like method. Could you guide me how to make it work ?
function MineString(string){
this.lengthS = string.length;
//this[1] = "test";
for(var i = 0; i < string.length;i++){
this[i] = string.charAt(i);
}
this.toString = function(){
return string;
}
this.split = function(char){
var splitedArray = [];
var foundedChar = [];
var string2 = [];
for (var i = 0; i < this.lengthS ; i++){
foundedChar.push(string[i].indexOf(char));
}
for (var j = 0; j < foundedChar.length; j++){
if(foundedChar[j] === -1){
//splitedArray[0] += string.charAt(j);
//splitedArray[j] = string.charAt(j);
string2 += string.charAt(j);
//splitedArray.push(string2);
splitedArray[foundedChar.length] = string2;
}else if (foundedChar[j] === 0){
//splitedArray.push(string.charAt(j));
}
}
return splitedArray;
}
}
var text = new MineString("hello");
text.split("e");
So text.split("e"); should display something like this:
var text = new MineString("hello");
text.split("e");
["h","llo"]
Your split method looks somehow overly complicated. I simplified it and rewrote the other parts of your class so that they adhere to the task of not using string methods. See jsfiddle or the code below.
New JS-Code:
function MineString(str){
this.str = str;
this.addChar = function(c) {
this.str += c;
}
this.length = function() {
return this.str.length;
}
this.toString = function(){
return this.str;
}
this.split = function(char){
var out = [],
current = new MineString(""),
addCurrent = function() {
if (current.length() > 0) {
out.push(current.toString());
current = new MineString("");
}
};
for (i = 0; i < this.str.length; i++) {
if (this.str[i] == char) {
addCurrent();
} else {
current.addChar(this.str[i]);
}
}
addCurrent();
return out;
}
}
var text = new MineString("hello");
console.log(text.split("e"));
Outputs:
["h", "llo"]

Split array by tag and delete all similar element

I have some html page with text and need to output all inner HTML from tag b by alphabetical order in lower case. I'm just a begginer, so don't be strict.
My code is here (text is just for example): http://jsfiddle.net/pamjaranka/ebeptLzj/1/
Now I want to: 1) save upper case for inner HTML from tag abbr; 2) delete all similar element from the array (as MABs).
I was trying to find the way to split the array by tag, but all that I've done is:
for(var i=0; i<allbold.length; i++){
labels[i] = allbold[i].innerHTML;
}
var searchTerm = ['abbr'];
var abbr = [];
var keywordIndex;
$.each(labels, function(i) {
$.each(searchTerm, function(j) {
var rSearchTerm = new RegExp('\\b' + searchTerm[j] + '\\b','i');
if (labels[i].match(rSearchTerm)) {
keywordIndex = i;
for(var j=0; j<labels.length; j++){
abbr[i] = labels[i];
}
}
});
});
Vanilla JS solution (no library required, see jsFiddle):
var allbold = document.querySelectorAll("b"),
words = document.querySelector("#words"),
labels = {}, i, word, keys, label;
// first, collect all words in an object (this eliminates duplicates)
for(i = 0; i < allbold.length; i++) {
word = allbold[i].textContent.trim();
if (word === 'Labels:') continue;
labels[word.toLowerCase()] = word;
}
// then sort the object keys and output the words in original case
keys = Object.keys(labels).sort();
for(i = 0; i < keys.length; i++){
label = document.createTextNode("SPAN");
label.textContent = labels[keys[i]];
words.appendChild(label);
// add a comma if necessary
if (i < keys.length - 1) {
words.appendChild(document.createTextNode(", "));
}
}
with one helper:
String.prototype.trim = function () {
return this.replace(/^\s+|\s+$/g, "");
};
jQuery solution (see jsFiddle):
$(".content b").map(function () {
return $("<span>", {text: $.trim(this.textContent)})[0];
}).unique(function () {
return lCaseText(this);
}).sort(function (a, b) {
return lCaseText(a) < lCaseText(b) ? -1 : 1;
}).appendTo("#words");
with two helpers:
$.fn.extend({
unique: function (keyFunc) {
var keys = {};
return this.map(function () {
var key = keyFunc.apply(this);
if (!keys.hasOwnProperty(key)) {
keys[key] = true;
return this;
}
});
}
});
function lCaseText(element) {
return element.textContent.toLowerCase();
}
use the mapping element Is THIS FIDDLE for all upper case else this fiddle after your comment what you need
var maplabels = [];
for(var i=0; i<allbold.length; i++){
if (allbold[i].innerHTML != "Labels:") {
if(maplabels.indexOf(allbold[i].innerHTML) == -1){
maplabels.push(allbold[i].innerHTML);
labels.push('<i>' + allbold[i].innerHTML.toUpperCase() + '</i>');
}
}
}

Convert Indented Text List to HTML List (jQuery)

I am attempting to create a jQuery script which will convert an indented text list of arbitrary length and depth into a properly formatted HTML list. The lists on which I will be running this script are simple tree structures for directories. Within the tree structures, folders are denoted by a semicolon following the folder name (and files have no ending punctuation). Given this, I would like to attach a <span class="folder"></span> or <span class="file"></span> to the lines as appropriate.
I've found it to be fairly easy to generate most of the structure, but I cannot seem to get the recursion (which I suspect will be necessary) down to ensure that the tags are properly nested. The page on which this will be implemented will include the most recent (i.e., 3.0.3) version of Bootstrap, so feel free to use any of its functionality. I have about two dozen (generally abortive) fragments of code which I've tried or which I'm currently attempting to tweak to produce the desired result. Instead of posting a mass of (likely unhelpful) code, I've created a JSFiddle with the basic form which will be used for input/output, a bit of jQuery, and an example list and some external libraries loaded.
Any help or suggestions will be greatly appreciated.
Try this. I copied it to your fiddle and it seems to work.
var indentedToHtmlList = function indentedToHtmlList (text, indentChar, folderChar, listType, showIcons) {
indentChar = indentChar || '\t';
folderChar = folderChar || ':';
listType = listType || 'ul';
showIcons = !!showIcons;
var lastDepth,
lines = text.split(/\r?\n/),
output = '<' + listType + '>\n',
depthCounter = new RegExp('^(' + indentChar + '*)(.*)');
for (var i = 0; i < lines.length; i++) {
var splitted = lines[i].match(depthCounter),
indentStr = splitted[1],
fileName = splitted[2],
currentDepth = (indentStr === undefined) ? 0 : (indentStr.length / indentChar.length),
isFolder = (fileName.charAt(fileName.length - 1) === folderChar);
if (isFolder) {
fileName = fileName.substring(0, fileName.length -1);
}
if (lastDepth === currentDepth) {
output += '</li>\n';
} else if (lastDepth > currentDepth) {
while (lastDepth > currentDepth) {
output += '</li>\n</' + listType + '>\n</li>\n';
lastDepth--;
}
} else if (lastDepth < currentDepth) {
output += '\n<' + listType + '>\n';
}
output += '<li>';
if (showIcons) {
output += '<span class=" glyphicon glyphicon-' +
(isFolder ? 'folder-open' : 'file') +
'"></span> ';
}
output += fileName;
lastDepth = currentDepth;
}
while (lastDepth >= 0) {
output += '\n</li>\n</' + listType + '>';
lastDepth--;
}
return output;
};
You could use spans and classes to denote files and folders, but you should consider using ul and li elements, they were built for that.
The whole list should be enclosed within an ul element. Each entry on the top level list should create an li element inside of the main element. If the element is a folder, then it should also append another ul. This is where you'll need recursion to allow proper nesting.
However, if you intend to use indentation (no pun indented) the tab and or whitespace parsing is a problem by itself which I'm not solving in this answer. For the sake of this example, I'll just pretend you have a magic function that turns text into a parsed list called MyList, and that files that belong to a folder are whatever lies after the first semicolon of each list element.
var turnTextIntoList=function(AText) {
//magic stuff;
return SomeList;
};
var populateList=function(AList) {
var mainelement=jQuery('<ul></ul>');
for(element in AList) {
var the_li=jQuery('<li></li>');
if(element.indexOf(';')!=-1) {
the_li.append('<span class="file">'+element+'</span>');
} else {
var thefolder=element.split(';')
the_li.append('<span class="folder">'+thefolder[0]+'</span>');
the_li.append(populateList(turnTextIntoList(thefolder[1])));
}
mainelement.append(the_li);
}
return mainelement;
};
var MyList=turnTextIntoList(MyText);
jQuery('#targetdiv').append(populateList(MyList));
See, the recursion part is where you do
the_li.append(populateList(turnTextIntoList(thefolder[1])));
which will keep drilling into nesting levels until it reaches a file so it can start its way back.
It appears that someone already created a script which does this. Unfortunately, that script is in CoffeeScript, not JavaScript. However, there are a number online converters which will convert from CoffeeScript to JavaScript. Thanks to #charlietfl who provided a link to a working converter, supra.
Here is the converted, working code:
var bind, blank, convert, index, li, lineToMap, linesToMaps, parse, parseTuples, ptAccum, runConvert, tabCount, ul, ulEnd;
convert = function(text) {
return parse(text.split('\n'));
};
li = function(t) {
var html;
html = "<li>" + t['line'] + "</li>";
ptAccum.push(html);
return html;
};
ul = function(t) {
return ptAccum.push("<ul>" + (li(t)));
};
ulEnd = function() {
return ptAccum.push("</ul>");
};
ptAccum = [];
index = 0;
parse = function(lines) {
var ts;
ts = linesToMaps(lines);
ptAccum = ["<ul>"];
index = 0;
parseTuples(ts, 0);
ulEnd();
return ptAccum.join("\n");
};
parseTuples = function(tuples, level) {
var stop, _p, _results;
stop = false;
_p = function() {
var curLevel, t;
t = tuples[index];
curLevel = t['level'];
index++;
if (curLevel === level) {
return li(t);
} else if (curLevel < level) {
index--;
return stop = true;
} else {
ul(t);
parseTuples(tuples, level + 1);
return ulEnd();
}
};
_results = [];
while (!stop && index < tuples.length) {
_results.push(_p());
}
return _results;
};
tabCount = function(line) {
var c, count, i, inc, isTab, tc;
tc = 0;
c = '\t';
count = 0;
if (line) {
count = line.length;
}
i = 0;
isTab = function() {
return c === '\t';
};
inc = function() {
c = line.charAt(i);
if (isTab()) {
tc++;
}
return i++;
};
while (isTab() && i < count) {
inc();
}
return tc;
};
lineToMap = function(line) {
return {
line: line,
level: tabCount(line)
};
};
blank = function(line) {
return !line || line.length === 0 || line.match(/^ *$/);
};
linesToMaps = function(lines) {
var line, _i, _len, _results;
_results = [];
for (_i = 0, _len = lines.length; _i < _len; _i++) {
line = lines[_i];
if (!(blank(line))) {
_results.push(lineToMap(line));
}
}
return _results;
};
runConvert = function() {
var result;
result = convert($('#textarea-plain-text').val());
$('#textarea-converted-text').val(result);
return $('#div-converted-text').html(result);
};
bind = function() {
return $('#list-conversion-button').click(runConvert);
};
$(bind);
JSFiddle

Categories

Resources