Prettify json data in textarea input - javascript

I've searched for this particular topic and couldn't find anything similar to it. If there is please close this and give a link.
I'm creating a json data api simulator. I want users to be able to copy and paste a json object request into a textarea where they can also modify it before sending it to the server.
Problem is json obj copy and patses often results in extra spaces and is never aligned properly, even with the pre tag. I also want a good color scheme applied to keys and values.
I've seen plugins, other questions and snippets of code, but they don't apply to textareas where the text is editable. Is there to keep it styled while in edit mode without it showing all the html tags that styled it? I want to be able to write it from scratch with javascript or jquery.

The syntax highlighting is tough but check out this fiddle for pretty printing a json object entered in a text area. Do note that the JSON has to be valid for this to work. (Use the dev console to catch errors.) Check jsLint for valid json.
The HTML:
<textarea id="myTextArea" cols=50 rows=10></textarea>
<button onclick="prettyPrint()">Pretty Print</button>
The js:
function prettyPrint() {
var ugly = document.getElementById('myTextArea').value;
var obj = JSON.parse(ugly);
var pretty = JSON.stringify(obj, undefined, 4);
document.getElementById('myTextArea').value = pretty;
}
First try simple input like: {"a":"hello","b":123}
Simple pretty printing of JSON can be done rather easily. Try this js code: (jsFiddle here)
// arbitrary js object:
var myJsObj = {a:'foo', 'b':'bar', c:[false,2,null, 'null']};
// using JSON.stringify pretty print capability:
var str = JSON.stringify(myJsObj, undefined, 4);
// display pretty printed object in text area:
document.getElementById('myTextArea').innerHTML = str;
For this HTML:
<textarea id="myTextArea" cols=50 rows=25></textarea>
And check out JSON.stringify documentation.

Late answer but modern one, use the secret intendation parameter.
I usually go for:
JSON.stringify(myData, null, 4);
Here's the code definition, it explains it well.
stringify(value: any, replacer?: (this: any, key: string, value: any) => any, space?: string | number): string;
/**
* Converts a JavaScript value to a JavaScript Object Notation (JSON) string.
* #param value A JavaScript value, usually an object or array, to be converted.
* #param replacer An array of strings and numbers that acts as a approved list for selecting the object properties that will be stringified.
* #param space Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read.
*/

For the parsing step you're just going to want to JSON.parse the contents of the textarea and handle any errors from bad input.
For the formatting part of your question, Use JSON.stringify(blob, undefined, 2). Alternatively, if you need colors here is a simple JSON format/color component written in React:
const HighlightedJSON = ({ json }: Object) => {
const highlightedJSON = jsonObj =>
Object.keys(jsonObj).map(key => {
const value = jsonObj[key];
let valueType = typeof value;
const isSimpleValue =
["string", "number", "boolean"].includes(valueType) || !value;
if (isSimpleValue && valueType === "object") {
valueType = "null";
}
return (
<div key={key} className="line">
<span className="key">{key}:</span>
{isSimpleValue ? (
<span className={valueType}>{`${value}`}</span>
) : (
highlightedJSON(value)
)}
</div>
);
});
return <div className="json">{highlightedJSON(json)}</div>;
};
See it working in this CodePen:
https://codepen.io/benshope/pen/BxVpjo
Hope that helps!

If you are a jquery fan, you can also use this small plugin I wrote:
// The plugin
$.fn.json_beautify= function() {
this.each(function(){
var el = $(this),
obj = JSON.parse(el.val()),
pretty = JSON.stringify(obj, undefined, 4);
el.val(pretty);
});
};
// Then use it like this on any textarea
$('textarea').json_beautify();
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<textarea id="myTextArea" cols=50 rows=5>{"name":"John","age":30}</textarea>
<textarea id="myTextArea2" cols=50 rows=5>{"name":"Bob","age":55}</textarea>
UPD
Changed code to multiselected elements.

I don't think that can be done with regular textareas. What you can do (and how most online code editors do it) is to create a transparent textarea that overlays on top of a div that contains the styled code. The user would still be able to type and interact with the input (and it fires the associated form events), and you can show syntax highlighting in the div that the user will visually see
(see Textarea that can do syntax highlighting on the fly?)
Now as for JSON formatting, I would add custom events to the textarea so that when a user types or paste something, run it through a Javascript JSON prettifier (see How can I pretty-print JSON using JavaScript?) and then re-populate the div and textarea accordingly

Here's a recursive function to return an object if it has been stringified multiple times:
const jsonPrettify = (json) => {
if (typeof json === 'object' && json !== null) {
const pretty = JSON.stringify(json, undefined, 4);
return pretty;
}
try {
const obj = JSON.parse(json);
return jsonPrettify(obj);
} catch (e) {
return json;
}
};

Related

How to split Json response and get specific part using angular

I am developing angular blockchain application using hyperledger composer tool.When i query the historian i got a response like in the below.
{
transactionType:"org.hyperledger.composer.system.AddParticipant"
}
I display the transaction type using follwing code snippet.
<div class="col-md-6">
{{participant.transactionType}}
</div>
The displayed part like this.
org.hyperledger.composer.system.AddParticipant
but I only want to display the 'AddParticipant' part in the response without 'org.hyperledger.composer.system.' part. How can I fix it?
For that just do little string manipulation. Make use of JS .split() method which splits string by argument character/string.
let arr = this.participant.transactionType.split(".");
then arr[arr.length-1] is your required string part which you can bind to view. Like use below {{txTyepe}} in template binding
this.txType = arr[arr.length-1];
you can use "substr" to pick a word from string but you need position of your word in your string first so :
const str = 'org.hyperledger.composer.system.AddParticipant'
let getPosition = str.indexOf('AddParticipant'); // get the position of AddParticipant
let getWord = str.substr(getPosition,13);
the length of AddParticipant is 13 also you can change the code above for better and cleaner and multi use code
const splitWord = (index)=>{
const str = 'org.hyperledger.composer.system.AddParticipant'
let strArray = str.split('.')
let getPosition = str.indexOf('AddParticipant'); // get the position of AddParticipant
let getWord = str.substr(getPosition,strArray[index].lenght); //you just need to change the number
return getWord;
}
console.log(splitWord(4));
You can also get the last "word" with regular expression :
<div class="col-md-6">
{{participant.transactionType.match(/\w+$/i)}}
</div>
When you see your historian data it'll look something like this
'$namespace': 'org.hyperledger.composer.system',
'$type': 'HistorianRecord',
'$identifier': '6e43b959c39bdd0c15fe45587a8dc866f1fa854d9fea8498536e84b45e281b31',
'$validator': ResourceValidator { options: {} },
transactionId: '6e43b959c39bdd0c15fe45587a8dc866f1fa854d9fea8498536e84b45e281b31',
transactionType: 'org.hyperledger.composer.system.IssueIdentity',
transactionInvoked:
Relationship {
'$modelManager': [Object],
'$classDeclaration': [Object],
'$namespace': 'org.hyperledger.composer.system',
'$type': 'IssueIdentity',
'$identifier': '6e43b959c39bdd0c15fe45587a8dc866f1fa854d9fea8498536e84b45e281b31',
'$class': 'Relationship' },
So, instead of taking transactionType you can use the transactionInvoked object. And then you can get whatever information you want from that object.
Finally your code should be like this
<div class="col-md-6">
{{participant.transactionInvoked.$type}}
</div>
In my case it will give me transaction type as just 'IssueIdentity'.

Es6 how to check if an item exists in an array built from a nodeList using includes()

I cant seem to figure this one out I am trying to check if an input is inlcuded in an array of nodes. No matter what I try it seems to return false. I'm thinking it must be something simple that I'm missing.
Here is what I have
const error = document.getElementsByClassName('has-form-error')
let focusedInputNodeList = error[0].childNodes
let focusedInput = Array.from(focusedInputNodeList)
This is to check for the most immediate form error. I then want to check the return if it is an input, textarea, or select.
the above code returns the array to console from my html
(6) [text, label, text, div.form-error, input#first_name, text]
Then I try
console.log(focusedInput.includes('input')) //returns false
I figured maybe it was due to the #first_name so I tried using
console.log(focusedInput.includes('text')) //returns false
and
console.log(focusedInput.includes('#text')) //returns false
None of these attempts seemed to work. My goal is to eventually have something that looks like
if (focusedInput.includes('input')) {
focusedInput[4].focus() //even better if I could find a way to select focusedInput.input.focus()
}
if (focusedInput.includes('textarea')) {
focusedInput[5].focus() // even better if I could find a way to select focusedInput.textarea.focus()
}
The problem is that your array focusedInput contains elements and not strings. In the code below I get an additional array of the element types, as strings, and from that you can find what you want.
The other thing I did was to use .children to get all of the non-text elements to reduce what is in the array.
const error = document.querySelector('.has-form-error');
let focusedInputNodeList = error.children;
let focusedInput = Array.from(focusedInputNodeList);
let nodeTypes = focusedInput.map(el=>el.localName);
console.log(focusedInput);
console.log(nodeTypes.indexOf('label'));
console.log(nodeTypes.indexOf('input'));
<div class="has-form-error">
<label>Click me</label>
<div class="form-error">Error</div><input id="first_name" />
</div>
UPDATE
In a comment you said: "even better if I could find a way to select focusedInput.input.focus()"
So why not do this:
const input = document.querySelector('.has-form-error input');
if (input) {
input.focus();
}
<div class="has-form-error">
<label>Click me</label>
<div class="form-error">Error</div><input id="first_name" />
</div>
You cannot just check for includes like that, since it's not an array of strings ['text', 'input', 'etc']. It's an array of node objects, and the name you're looking for is a property of that node, which you can find like focusedInput[5].localName (or .tagName or .nodeName).
So, like:
focusedInput.forEach(item => {
if (item.localName === 'input') {
item.focus()
}
})

how to print a javascript object's elements

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.

Can I post JSON without using AJAX?

I have some data, lets say:
var dat = JSON.stringify(frm.serializeArray())
I want to submit this to the server using a roundtrip (aka, non ajax).
I know this is possible, but I can't find any literature on it. Ideas?
(I am using jQuery, if that makes it easier)
EDIT: while all of these answers so far answer the question, I should have included that I want an "content type" of "application/json"
Create an HTML form with unique "id" attribute. You can hide it using CSS "display:none". Also fill the action and method attributes.
Add a text or hidden input field to the form. make sure you give it a meaningful "name" attribute. That's the name that the server would get the data within.
Using JQuery (or plain old javascript) copy the variable "dat" into the input field
Submit the form using script
There is a working draft to support the so called HTML-JSON-FORMS, see:
http://www.w3.org/TR/2014/WD-html-json-forms-20140529/
So far use ajax or send the json into an input text field.
<form action="xxx.aspx" method="POST">
<input type='hidden' id='dat' />
<!-- Other elements -->
</form>
<script type='text/javascript'>
$('#dat').val(JSON.stringify(frm.serializeArray()));
</script>
You would need to assign the json string to an input's value inside a form tag in order for it to get POSTed to the server (either by the user submitting the form or by clicking the submit button programmatically).
Alternatively from javascript you could use window.location to send the variable as part of a GET request.
In another answer someone mentioned a W3 working draft which is outdated now and the newer version of the document says we can use enctype="application/json" attribute for the form and it will send the whole form fields as properties of an object.
It is still unclear to me how to send an array though, but refering to the above document you can send an object simply as:
<form enctype='application/json'>
<input name='name' value='Bender'>
<select name='hind'>
<option selected>Bitable</option>
<option>Kickable</option>
</select>
<input type='checkbox' name='shiny' checked>
</form>
// produces {"name": "Bender", "hind": "Bitable", "shiny": true}
I can't copy the whole doc here, so check out the document to see how to create more complex objects using array notation and sparsing arrays in input field names.
To create the form out of your object, you have to make a series of input elements, that produces the same JSON object you have in hand. You can either do it manually, or if your object is large enough, you can use a code snippet to convert your object to the desired input elements.
I ended up with something like the code below as the base.
You can change it to your need (e.g. make the form hidden or even produce more diverse input field types with styles for different property types for a real proper form)
(function () {
const json = {
bool: false,
num: 1.5,
str: 'ABC',
obj: {b:true, n: .1, s: '2', a: [1, '1']},
arr: [
true, 500.33, 'x', [1, 2],
{b:true, n: .1, s: '2', a: [1, '1']}
]
};
const getFieldHTML = (value, name) => {
if (name||name===0) switch (typeof value) {
case 'boolean': return `<input type="checkbox" name="${name}" ${value?'checked':''}>\n`;
case 'number': return `<input type="number" name="${name}" value="${value}">\n`;
case 'string': return `<input type="text" name="${name}" value="${value}">\n`;
}
return '';
};
const getFieldsHTML = (value, name) => {
const fields = [];
if (value instanceof Array)
fields.push(...value.map((itemValue, i) =>
getFieldsHTML(itemValue, name+'['+i+']')
));
else if (typeof value === "object")
fields.push(...Object.keys(value).map(prop =>
getFieldsHTML(
value[prop], //value is an object
name?(name+'['+prop+']'):prop
)
));
else
fields.push(getFieldHTML(value, name));
return fields.join('');
};
const fieldsHTML = getFieldsHTML(json);
const frm = document.createElement('form');
frm.enctype = 'application/json';
frm.method = 'POST';
frm.action = 'URL GOES HERE';
frm.innerHTML = fieldsHTML;
console.log(fieldsHTML);
console.log(frm)
})();
Check your browser's console to inspect the created form DOM and its children.

Get list of data-* attributes using javascript / jQuery

Given an arbitrary HTML element with zero or more data-* attributes, how can one retrieve a list of key-value pairs for the data.
E.g. given this:
<div id='prod' data-id='10' data-cat='toy' data-cid='42'>blah</div>
I would like to be able to programmatically retrieve this:
{ "id":10, "cat":"toy", "cid":42 }
Using jQuery (v1.4.3), accessing the individual bits of data using $.data() is simple if the keys are known in advance, but it is not obvious how one can do so with arbitrary sets of data.
I'm looking for a 'simple' jQuery solution if one exists, but would not mind a lower level approach otherwise. I had a go at trying to to parse $('#prod').attributes but my lack of javascript-fu is letting me down.
update
customdata does what I need. However, including a jQuery plugin just for a fraction of its functionality seemed like an overkill.
Eyeballing the source helped me fix my own code (and improved my javascript-fu).
Here's the solution I came up with:
function getDataAttributes(node) {
var d = {},
re_dataAttr = /^data\-(.+)$/;
$.each(node.get(0).attributes, function(index, attr) {
if (re_dataAttr.test(attr.nodeName)) {
var key = attr.nodeName.match(re_dataAttr)[1];
d[key] = attr.nodeValue;
}
});
return d;
}
update 2
As demonstrated in the accepted answer, the solution is trivial with jQuery (>=1.4.4). $('#prod').data() would return the required data dict.
Actually, if you're working with jQuery, as of version 1.4.3 1.4.4 (because of the bug as mentioned in the comments below), data-* attributes are supported through .data():
As of jQuery 1.4.3 HTML 5 data-
attributes will be automatically
pulled in to jQuery's data object.
Note that strings are left intact
while JavaScript values are converted
to their associated value (this
includes booleans, numbers, objects,
arrays, and null). The data-
attributes are pulled in the first
time the data property is accessed and
then are no longer accessed or mutated
(all data values are then stored
internally in jQuery).
The jQuery.fn.data function will return all of the data- attribute inside an object as key-value pairs, with the key being the part of the attribute name after data- and the value being the value of that attribute after being converted following the rules stated above.
I've also created a simple demo if that doesn't convince you: http://jsfiddle.net/yijiang/WVfSg/
A pure JavaScript solution ought to be offered as well, as the solution is not difficult:
var a = [].filter.call(el.attributes, function(at) { return /^data-/.test(at.name); });
This gives an array of attribute objects, which have name and value properties:
if (a.length) {
var firstAttributeName = a[0].name;
var firstAttributeValue = a[0].value;
}
Edit: To take it a step further, you can get a dictionary by iterating the attributes and populating a data object:
var data = {};
[].forEach.call(el.attributes, function(attr) {
if (/^data-/.test(attr.name)) {
var camelCaseName = attr.name.substr(5).replace(/-(.)/g, function ($0, $1) {
return $1.toUpperCase();
});
data[camelCaseName] = attr.value;
}
});
You could then access the value of, for example, data-my-value="2" as data.myValue;
jsfiddle.net/3KFYf/33
Edit: If you wanted to set data attributes on your element programmatically from an object, you could:
Object.keys(data).forEach(function(key) {
var attrName = "data-" + key.replace(/[A-Z]/g, function($0) {
return "-" + $0.toLowerCase();
});
el.setAttribute(attrName, data[key]);
});
jsfiddle.net/3KFYf/34
EDIT: If you are using babel or TypeScript, or coding only for es6 browsers, this is a nice place to use es6 arrow functions, and shorten the code a bit:
var a = [].filter.call(el.attributes, at => /^data-/.test(at.name));
Have a look here:
If the browser also supports the HTML5 JavaScript API, you should be able to get the data with:
var attributes = element.dataset
or
var cat = element.dataset.cat
Oh, but I also read:
Unfortunately, the new dataset property has not yet been implemented in any browser, so in the meantime it’s best to use getAttribute and setAttribute as demonstrated earlier.
It is from May 2010.
If you use jQuery anyway, you might want to have a look at the customdata plugin. I have no experience with it though.
As mentioned above modern browsers have the The HTMLElement.dataset API.
That API gives you a DOMStringMap, and you can retrieve the list of data-* attributes simply doing:
var dataset = el.dataset; // as you asked in the question
you can also retrieve a array with the data- property's key names like
var data = Object.keys(el.dataset);
or map its values by
Object.keys(el.dataset).map(function(key){ return el.dataset[key];});
// or the ES6 way: Object.keys(el.dataset).map(key=>{ return el.dataset[key];});
and like this you can iterate those and use them without the need of filtering between all attributes of the element like we needed to do before.
You should be get the data through the dataset attributes
var data = element.dataset;
dataset is useful tool for get data-attribute
or convert gilly3's excellent answer to a jQuery method:
$.fn.info = function () {
var data = {};
[].forEach.call(this.get(0).attributes, function (attr) {
if (/^data-/.test(attr.name)) {
var camelCaseName = attr.name.substr(5).replace(/-(.)/g, function ($0, $1) {
return $1.toUpperCase();
});
data[camelCaseName] = attr.value;
}
});
return data;
}
Using: $('.foo').info();
You can just iterate over the data attributes like any other object to get keys and values, here's how to do it with $.each:
$.each($('#myEl').data(), function(key, value) {
console.log(key);
console.log(value);
});
I use nested each - for me this is the easiest solution (Easy to control/change "what you do with the values - in my example output data-attributes as ul-list) (Jquery Code)
var model = $(".model");
var ul = $("<ul>").appendTo("body");
$(model).each(function(index, item) {
ul.append($(document.createElement("li")).text($(this).text()));
$.each($(this).data(), function(key, value) {
ul.append($(document.createElement("strong")).text(key + ": " + value));
ul.append($(document.createElement("br")));
}); //inner each
ul.append($(document.createElement("hr")));
}); // outer each
/*print html*/
var htmlString = $("ul").html();
$("code").text(htmlString);
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.17.1/prism.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.17.1/themes/prism-okaidia.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<h1 id="demo"></h1>
<ul>
<li class="model" data-price="45$" data-location="Italy" data-id="1234">Model 1</li>
<li class="model" data-price="75$" data-location="Israel" data-id="4321">Model 2</li>
<li class="model" data-price="99$" data-location="France" data-id="1212">Model 3</li>
</ul>
<pre>
<code class="language-html">
</code>
</pre>
<h2>Generate list by code</h2>
<br>
Codepen: https://codepen.io/ezra_siton/pen/GRgRwNw?editors=1111
One way of finding all data attributes is using element.attributes. Using .attributes, you can loop through all of the element attributes, filtering out the items which include the string "data-".
let element = document.getElementById("element");
function getDataAttributes(element){
let elementAttributes = {},
i = 0;
while(i < element.attributes.length){
if(element.attributes[i].name.includes("data-")){
elementAttributes[element.attributes[i].name] = element.attributes[i].value
}
i++;
}
return elementAttributes;
}
If you know the name of keys you can also use object destructuring to get values like this
const {id, cat, cid } = document.getElementById('prod').dataset;
You can also skip keys you don't need and get the ones you need like this
const { cid, id } = document.getElementById('prod').dataset;
100% Javascript no jQuery ;)
DOMStringMap :
console.log(document.getElementById('target-element-id').dataset);
or custom variable :
var data = {};
Object.entries(document.getElementById('target-element-id').dataset).forEach(([key, val]) => {
data[key] = val;
});
console.log(data);

Categories

Resources