i am new to javascript and i currently have an object printed to console when i use the following code:
clickEvents: {
click:function(target) {
console.log(target);
}
}
when i view console i can see the following object:
i am banging my head against a wall to write code that takes the object and prints it to a div using the .append() method. i am extermely new to working with javascript objects, and would appreciate any help trying to tease out an object and/or print the object data.
is events the object name? would i tease out the eventDate using something like events->eventDate?
I've made this over ~15 minutes so it's imperfect; there are types and edge cases surely unaccounted for and the design of the function could be better - not to mention that performing all of this as a giant string and then setting that as HTML is likely bad practice (I'm used to React now, ha!). Regardless, this will iterate over any array or object you pass to it and print it all in a big <ul> recursively.
const targetEl = document.querySelector('.js-target')
if (!targetEl) return
// Small helper functions
const isObj = data => typeof data === 'object' && !Array.isArray(data) && data !== null
const isArr = data => Array.isArray(data)
const dataToHTML = (data, noNode = false) => {
if (isObj(data)) {
const accumulator = Object.entries(data).reduce((acc, set) => acc + `<li><strong>${set[0]}</strong>: ${dataToHTML(set[1], true)}</li>`, '')
return `<ul>${accumulator}</ul>`
}
else if (isArr(data)) {
const accumulator = data.reduce((acc, item) => acc + dataToHTML(item), '')
return `<ul>${accumulator}</ul>`
}
else return noNode ? data : `<li>${data}</li>`
}
const logHTML = dataToHTML(exampleData)
targetEl.innerHTML = logHTML
Assuming that your data/variable is named exampleData.
Any questions pop them in the comments :-)
I'm not sure if you have a div that you want to append to already, but you would do something like this ->
document.getElementById("toBeAppendedTo").innerHTML = target.events[0].eventDate; where toBeAppendedTo is the id of the div you're trying to add this text to.
append() is a jquery function, not a javascript function.
That won't have any formatting and will just be the string value 07-28-2017 in a div.
In the following code the compiler (at 'compile' time) makes no complaints about groups.shift() but complains that depths.shift() is not a function. What am I being blind to? (I tried renaming depths, retyping, etc.)
const tag1x = (elem, content, groups = ['?','?','?'], depths = ['?','?'], optional = true, level = 0) => {
let option = optional ? '?' : '';
let template = `
${'\t'.repeat(level)}(${groups.shift()}:<$1[^>]*?DDD(${depths.shift()}:[0-9]+)[^>]*>)$3
${'\t'.repeat(level)}(${groups.shift()}:$2)
${'\t'.repeat(level)}(${groups.shift()}:</$1[^>]*?DDD(${depths.shift()}:[0-9]+)[^>]*>)$3
`;
return form(template, elem, content, option);
}
However, if I use shift generically it works fine on all counts:
const tag1x = (elem, content, groups = ['?','?','?'], depths = ['?','?'], optional = true, level = 0) => {
let option = optional ? '?' : '';
let template = `
${'\t'.repeat(level)}(${groups.shift()}:<$1[^>]*?DDD(${[].shift.call(depths)}:[0-9]+)[^>]*>)$3
${'\t'.repeat(level)}(${groups.shift()}:$2)
${'\t'.repeat(level)}(${groups.shift()}:</$1[^>]*?DDD(${[].shift.call(depths)}:[0-9]+)[^>]*>)$3
`;
return form(template, elem, content, option);
}
The above is fully functional.
I misread the situation. The error was occurring at runtime, and thus pretty clearly because of a string input instead of an array input. A string input will get fixed up by [].shift.call(myAccidentallyAString) while, of course, a shift() call directly on the string is not a function.
It acts like Array.isArray(myStuff) ? myStuff.shift() : [myStuff].shift(), which makes sense because (I'll guess) myStuff is getting boxed to an object then called on by shift().
I'm trying to create a dictionary from a .txt file in the shape of a tree. On every line of the text file there's a word, I extract all those words in an array.
Now regarding the tree, Each node contains a letter, if it's the last letter of a word, it contains a definition, and each node have an array Children that contains letters from all others words starting the same way.
So I have nodes defined this way:
function Node(letter,definition,children) {
this.letter = letter,
this.definition = "",
this.children = []
};
I have an array Dictionary that will contain all the nodes. Every Node will be organized (so that we know 'a' is in Dictionary[0] and 'b' in Dictionary[1] and so on).
I defined some functions to help build the dictionary:
check if Dictionary contains the first letter of the word we have (c is the character, dictio is the dictionary array and ascii is the ascii-97 value of the character)
function checkChar(c,dictio,ascii){
if(dictio[ascii].letter == c ){
return true;
}
return false;
};
create a node with the given character
function createChar(c){
var noeud = {
letter: c,
def: '',
children: []
};
return noeud;
};
Add the character to the dictionary
function addChar(c,dictio,ascii){
dictio.children[ascii] = createChar(c);
};
And I'm having trouble on the biggest function: the main on that adds the word and calls all of these small functions I've written. Which I'm having trouble making.
I don't even know if what I'm doing is right or wrong, if anyone could point me to the right direction or suggest a method in javascript or php to do dictionary from a TXT file that would be great.
Ok...
so this is an example of txt file containing words
//words.txt
hello
world
foo
bar
word_dictionary.php for parsing txt file and has method for checking if a word exists in tree/dictionary
<?php
//word_dictionary.php
class Node{
private $letter;
private $definition = '';
private $children = array();
function __construct($letter){
$this->letter = $letter;
}
function hasChild($letter){
return array_key_exists($letter,$this->children);
}
function addChild($letter){
$this->children[$letter] = new Node($letter);
return $this->children[$letter];
}
function getChild($letter){
return $this->children[$letter];
}
function setDefinition($definition){
$this->definition = $definition;
}
function getDefinition(){
return $this->definition;
}
function hasDefinition(){
return (bool)$this->definition;
}
}
// method for getting a word definition from tree/dictionary.
// if word exists return definition, else return false
function getDefinition($word,$tree){
$node = $tree;
$length = strlen($word);
foreach(str_split($word) as $index => $letter){
if($node->hasChild($letter)){
$node = $node->getChild($letter);
}
else{ // word not exists
return false;
}
if(($index+1) == $length){ // means last letter in word
return ($node->hasDefinition()) ? $node->getDefinition() : false;
}
}
}
// Start build your tree/dictionary. This part is execute ONCE only for building tree.
$anchor = new Node('');
$handle = fopen('words.txt','r');
while(($word = fgets($handle))){
$word = rtrim($word);
$length = strlen($word);
$node = $anchor;
foreach(str_split($word) as $index => $letter){
if($node->hasChild($letter)){
$node = $node->getChild($letter);
}
else{
$node = $node->addChild($letter);
}
if(($index+1) == $length ){
//print 'definition for word: '.$word."\n";
$node->setDefinition('definition for world: '.$word);
}
}
}
//use this function when a user type a word that you want to check if exists and return the definition to user. this flow should be in AJAX request from client
print getDefinition('bar',$anchor)."\n";
hope it help a bit ;)
first of all, you're asking if you are going in the right direction. Well, i think you are. This may not be the best implementation of the year but still all the things you said are coherent with each other and it seems pretty solid.
I don't think giving you a direct solution to your question would be didactic since you're working with trees and it seems you don't have much experience with them.
But i can give you some hint and references. A very convenient way to implement your "biggest function :)" would be to use a recursive function which would call itself on each children.
I suggest you take a look a this wikipedia article. It shows example of trees that look a bit like yours and implement a full search algorithm that you could adapt to your needs without too much problem.
Hope the english wasn't that bad, and that it will help you
Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
We don’t allow questions seeking recommendations for books, tools, software libraries, and more. You can edit the question so it can be answered with facts and citations.
Closed 2 years ago.
Improve this question
I'm attempting map HTML into JSON with structure intact. Are there any libraries out there that do this or will I need to write my own? I suppose if there are no html2json libraries out there I could take an xml2json library as a start. After all, html is only a variant of xml anyway right?
UPDATE: Okay, I should probably give an example. What I'm trying to do is the following. Parse a string of html:
<div>
<span>text</span>Text2
</div>
into a json object like so:
{
"type" : "div",
"content" : [
{
"type" : "span",
"content" : [
"Text2"
]
},
"Text2"
]
}
NOTE: In case you didn't notice the tag, I'm looking for a solution in Javascript
I just wrote this function that does what you want; try it out let me know if it doesn't work correctly for you:
// Test with an element.
var initElement = document.getElementsByTagName("html")[0];
var json = mapDOM(initElement, true);
console.log(json);
// Test with a string.
initElement = "<div><span>text</span>Text2</div>";
json = mapDOM(initElement, true);
console.log(json);
function mapDOM(element, json) {
var treeObject = {};
// If string convert to document Node
if (typeof element === "string") {
if (window.DOMParser) {
parser = new DOMParser();
docNode = parser.parseFromString(element,"text/xml");
} else { // Microsoft strikes again
docNode = new ActiveXObject("Microsoft.XMLDOM");
docNode.async = false;
docNode.loadXML(element);
}
element = docNode.firstChild;
}
//Recursively loop through DOM elements and assign properties to object
function treeHTML(element, object) {
object["type"] = element.nodeName;
var nodeList = element.childNodes;
if (nodeList != null) {
if (nodeList.length) {
object["content"] = [];
for (var i = 0; i < nodeList.length; i++) {
if (nodeList[i].nodeType == 3) {
object["content"].push(nodeList[i].nodeValue);
} else {
object["content"].push({});
treeHTML(nodeList[i], object["content"][object["content"].length -1]);
}
}
}
}
if (element.attributes != null) {
if (element.attributes.length) {
object["attributes"] = {};
for (var i = 0; i < element.attributes.length; i++) {
object["attributes"][element.attributes[i].nodeName] = element.attributes[i].nodeValue;
}
}
}
}
treeHTML(element, treeObject);
return (json) ? JSON.stringify(treeObject) : treeObject;
}
Working example: http://jsfiddle.net/JUSsf/ (Tested in Chrome, I can't guarantee full browser support - you will have to test this).
It creates an object that contains the tree structure of the HTML page in the format you requested and then uses JSON.stringify() which is included in most modern browsers (IE8+, Firefox 3+ .etc); If you need to support older browsers you can include json2.js.
It can take either a DOM element or a string containing valid XHTML as an argument (I believe, I'm not sure whether the DOMParser() will choke in certain situations as it is set to "text/xml" or whether it just doesn't provide error handling. Unfortunately "text/html" has poor browser support).
You can easily change the range of this function by passing a different value as element. Whatever value you pass will be the root of your JSON map.
htlm2json
Representing complex HTML documents will be difficult and full of corner cases, but I just wanted to share a couple techniques to show how to get this kind of program started. This answer differs in that it uses data abstraction and the toJSON method to recursively build the result
Below, html2json is a tiny function which takes an HTML node as input and it returns a JSON string as the result. Pay particular attention to how the code is quite flat but it's still plenty capable of building a deeply nested tree structure – all possible with virtually zero complexity
const Elem = e => ({
tagName:
e.tagName,
textContent:
e.textContent,
attributes:
Array.from(e.attributes, ({name, value}) => [name, value]),
children:
Array.from(e.children, Elem)
})
const html2json = e =>
JSON.stringify(Elem(e), null, ' ')
console.log(html2json(document.querySelector('main')))
<main>
<h1 class="mainHeading">Some heading</h1>
<ul id="menu">
<li>a</li>
<li>b</li>
<li>c</li>
</ul>
<p>some text</p>
</main>
In the previous example, the textContent gets a little butchered. To remedy this, we introduce another data constructor, TextElem. We'll have to map over the childNodes (instead of children) and choose to return the correct data type based on e.nodeType – this gets us a littler closer to what we might need
const TextElem = e => ({
type:
'TextElem',
textContent:
e.textContent
})
const Elem = e => ({
type:
'Elem',
tagName:
e.tagName,
attributes:
Array.from(e.attributes, ({name, value}) => [name, value]),
children:
Array.from(e.childNodes, fromNode)
})
const fromNode = e => {
switch (e?.nodeType) {
case 1: return Elem(e)
case 3: return TextElem(e)
default: throw Error(`unsupported nodeType: ${e.nodeType}`)
}
}
const html2json = e =>
JSON.stringify(Elem(e), null, ' ')
console.log(html2json(document.querySelector('main')))
<main>
<h1 class="mainHeading">Some heading</h1>
<ul id="menu">
<li>a</li>
<li>b</li>
<li>c</li>
</ul>
<p>some text</p>
</main>
Anyway, that's just two iterations on the problem. Of course you'll have to address corner cases where they come up, but what's nice about this approach is that it gives you a lot of flexibility to encode the HTML however you wish in JSON – and without introducing too much complexity
In my experience, you could keep iterating with this technique and achieve really good results. If this answer is interesting to anyone and would like me to expand upon anything, let me know ^_^
Related: Recursive methods using JavaScript: building your own version of JSON.stringify
json2html
Above we go from HTML to JSON and now we can go from JSON to HTML. When we can convert between two data types without losing data, this is called an isomorphism. All we are essentially doing here is writing the inverses of each function above -
const HtmlNode = (tagName, attributes = [], children = []) => {
const e = document.createElement(tagName)
for (const [k, v] of attributes) e.setAttribute(k, v)
for (const child of children) e.appendChild(toNode(child))
return e
}
const TextNode = (text) => {
return document.createTextNode(text)
}
const toNode = t => {
switch (t?.type) {
case "Elem": return HtmlNode(t.tagName, t.attributes, t.children)
case "TextElem": return TextNode(t.textContent)
default: throw Error("unsupported type: " + t.type)
}
}
const json2html = json =>
toNode(JSON.parse(json))
const parsedJson =
{"type":"Elem","tagName":"MAIN","attributes":[],"children":[{"type":"TextElem","textContent":"\n "},{"type":"Elem","tagName":"H1","attributes":[["class","mainHeading"]],"children":[{"type":"TextElem","textContent":"Some heading"}]},{"type":"TextElem","textContent":"\n "},{"type":"Elem","tagName":"UL","attributes":[["id","menu"]],"children":[{"type":"TextElem","textContent":"\n "},{"type":"Elem","tagName":"LI","attributes":[],"children":[{"type":"Elem","tagName":"A","attributes":[["href","/a"]],"children":[{"type":"TextElem","textContent":"a"}]}]},{"type":"TextElem","textContent":"\n "},{"type":"Elem","tagName":"LI","attributes":[],"children":[{"type":"Elem","tagName":"A","attributes":[["href","/b"]],"children":[{"type":"TextElem","textContent":"b"}]}]},{"type":"TextElem","textContent":"\n "},{"type":"Elem","tagName":"LI","attributes":[],"children":[{"type":"Elem","tagName":"A","attributes":[["href","/c"]],"children":[{"type":"TextElem","textContent":"c"}]}]},{"type":"TextElem","textContent":"\n "}]},{"type":"TextElem","textContent":"\n "},{"type":"Elem","tagName":"P","attributes":[],"children":[{"type":"TextElem","textContent":"some text"}]},{"type":"TextElem","textContent":"\n"}]}
document.body.appendChild(toNode(parsedJson))
I got few links sometime back while reading on ExtJS full framework in itself is JSON.
http://www.thomasfrank.se/xml_to_json.html
http://camel.apache.org/xmljson.html
online XML to JSON converter : http://jsontoxml.utilities-online.info/
UPDATE
BTW, To get JSON as added in question, HTML need to have type & content tags in it too like this or you need to use some xslt transformation to add these elements while doing JSON conversion
<?xml version="1.0" encoding="UTF-8" ?>
<type>div</type>
<content>
<type>span</type>
<content>Text2</content>
</content>
<content>Text2</content>
Thank you #Gorge Reith. Working off the solution provided by #George Reith, here is a function that furthers (1) separates out the individual 'hrefs' links (because they might be useful), (2) uses attributes as keys (since attributes are more descriptive), and (3) it's usable within Node.js without needing Chrome by using the 'jsdom' package:
const jsdom = require('jsdom') // npm install jsdom provides in-built Window.js without needing Chrome
// Function to map HTML DOM attributes to inner text and hrefs
function mapDOM(html_string, json) {
treeObject = {}
// IMPT: use jsdom because of in-built Window.js
// DOMParser() does not provide client-side window for element access if coding in Nodejs
dom = new jsdom.JSDOM(html_string)
document = dom.window.document
element = document.firstChild
// Recursively loop through DOM elements and assign attributes to inner text object
// Why attributes instead of elements? 1. attributes more descriptive, 2. usually important and lesser
function treeHTML(element, object) {
var nodeList = element.childNodes;
if (nodeList != null) {
if (nodeList.length) {
object[element.nodeName] = [] // IMPT: empty [] array for non-text recursivable elements (see below)
for (var i = 0; i < nodeList.length; i++) {
// if final text
if (nodeList[i].nodeType == 3) {
if (element.attributes != null) {
for (var j = 0; j < element.attributes.length; j++) {
if (element.attributes[j].nodeValue !== '' &&
nodeList[i].nodeValue !== '') {
if (element.attributes[j].name === 'href') { // separate href
object[element.attributes[j].name] = element.attributes[j].nodeValue;
} else {
object[element.attributes[j].nodeValue] = nodeList[i].nodeValue;
}
}
}
}
// else if non-text then recurse on recursivable elements
} else {
object[element.nodeName].push({}); // if non-text push {} into empty [] array
treeHTML(nodeList[i], object[element.nodeName][object[element.nodeName].length -1]);
}
}
}
}
}
treeHTML(element, treeObject);
return (json) ? JSON.stringify(treeObject) : treeObject;
}
I had a similar issue where I wanted to represent HTML as JSON in the following way:
For HTML text nodes, use a string
For HTML elements, use an array with:
The (tag) name of the element
An object, mapping attribute keys to attribute values
The (inlined) list of children nodes
Example:
<div>
<span>text</span>Text2
</div>
becomes
[
'div',
{},
['span', {}, 'text'],
'Text2'
]
I wrote a function which handles transforming a DOM Element into this kind of JS structure. You can find this function at the end of this answer. The function is written in Typescript. You can use the Typescript playground to convert it to clean JavaScript.
Furthermore, if you need to parse an html string into DOM, assign to .innerHtml:
let element = document.createElement('div')
element.innerHtml = htmlString
Also, this one is common knowledge but if you need a JSON string output, use JSON.stringify.
/**
* A NodeDescriptor stands for either an (HTML) Element, or for a text node
*/
export type NodeDescriptor = ElementDescriptor | string
/**
* Array representing an HTML Element. It consists of:
*
* - The (tag) name of the element
* - An object, mapping attribute keys to attribute values
* - The (inlined) list of children nodes
*/
export type ElementDescriptor = [
string,
Record<string, string>,
...NodeDescriptor[]
]
export let htmlToJs = (element: Element, trim = true): ElementDescriptor => {
let convertElement = (element: Element): ElementDescriptor => {
let attributeObject: Record<string, string> = {}
for (let { name, value } of element.attributes) {
attributeObject[name] = value
}
let childArray: NodeDescriptor[] = []
for (let node of element.childNodes) {
let converter = htmlToJsDispatch[node.nodeType]
if (converter) {
let descriptor = converter(node as any)
let skip = false
if (trim && typeof descriptor === 'string') {
descriptor = descriptor.trim()
if (descriptor === '') skip = true
}
if (!skip) childArray.push(descriptor)
}
}
return [element.tagName.toLowerCase(), attributeObject, ...childArray]
}
let htmlToJsDispatch = {
[element.ELEMENT_NODE]: convertElement,
[element.TEXT_NODE]: (node: Text): string => node.data,
}
return convertElement(element)
}
I have a problem with the jQuery.param function.
jQuery uses + instead of %20 to URL-encode spaces
var obje = {
'test': 'tester 2'
}
console.log($.param(obje));
returns "test=tester+2"
so I thought about overriding this core function:
(function($){
$.fn.param = function( a, traditional ) {
console.log('custom $.param');
var s = [],
add = function( key, value ) {
// If value is a function, invoke it and return its value
value = jQuery.isFunction( value ) ? value() : value;
s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value );
};
// Set traditional to true for jQuery <= 1.3.2 behavior.
if ( traditional === undefined ) {
traditional = jQuery.ajaxSettings.traditional;
}
// If an array was passed in, assume that it is an array of form elements.
if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
// Serialize the form elements
jQuery.each( a, function() {
add( this.name, this.value );
} );
} else {
// If traditional, encode the "old" way (the way 1.3.2 or older
// did it), otherwise encode params recursively.
for ( var prefix in a ) {
buildParams( prefix, a[ prefix ], traditional, add );
}
}
return s.join("&");
// Return the resulting serialization
//return s.join( "&" ).replace( r20, "+" );
};
})(jQuery);
var obje = {
'test': 'tester 2'
}
console.log($.param(obje));
This fails however.. The $.param isn't overridden.
Any idea what can be wrong?
Thanks!
Edit: my solution (because I'm a new user I appearently may not answer my own question in 8 hours (Why is that?))
With the solution of ThiefMaster I still had the problem that buildParams is undefined.
I solved this by calling the old function and then replacing the + back to %20
// modification of the jQuery.param function: spaces are encoded by jQuery.param with + instead of %20. replace these back to %20
(function($, oldFunction){
$.param = function( a, traditional ) {
var s = oldFunction.apply(oldFunction,[a,traditional]);
// Return the resulting serialization
return s.replace( '+', '%20' );
};
})(jQuery,jQuery.param);
You need to use $.param instead of $.fn.param (which would be a function to call on a jQuery object, e.g. $(...).param()).
Old post I know, but for the sake of recorded knowledge. To replace the '+' left behind when using $.param(), consider doing the following:
(Using the code you provided)
var obje = {
'test': 'tester 2'
}
console.log($.param(obje).replace(/\+/g, "%20"));
That will result in:
test = tester 2
Hope this helps someone.
The "re-replace" fix may also be implemented by using "beforeSend" in the ajax settings object:
{ beforeSend: function (request, settings) { settings.data = settings.data.replace(/\+/g, "%20"); } }
This approach is suitable for cases where you don't actually want to alter $.param()'s original behavior (for example, if you want "+" in URLs but "%20" for POST data).
[Edited because I remembered that string.replace() will only match once unless it's a regex object with the g flag.]