How to render links generated dynamically from JSON? - javascript

To display links in my rendered JSON, I want them as value in the name/value pair.
The function below takes a valid JSON as a parameter and should return JSON with their values converted to anchor tags.
The input JSON is
{"#context":"/api/contexts/EntryPoint.jsonld","#id":"/api","#type":"EntryPoint","AnomalyCollection":"/api/AnomalyCollection","CommandCollection":"/api/CommandCollection","ControllerLogCollection":"/api/ControllerLogCollection","DroneCollection":"/api/DroneCollection","DroneLogCollection":"/api/DroneLogCollection","HttpApiLogCollection":"/api/HttpApiLogCollection","Location":"/api/Location","MessageCollection":"/api/MessageCollection","dsCollection":"/api/dsCollection"}
{"k": "v"} should get changed to {"k": v}
I understand that the changed value would not be a valid JSON, then what is the way out?
function makeEditable(data) {
for (var property in data) {
if (data.hasOwnProperty(property)) {
var tag = $('<a href=#>' + data[property] + '</a>');
data[property] = tag;
}
}
return data;
}
The function above instead of giving links, gives the below render.
{
#context : {
0 : "http://localhost:5000/#"
},
#id : {
0 : "http://localhost:5000/#"
},
#type : {
0 : "http://localhost:5000/#"
}
}
What I want is a render similar to this

I would highly suggest to not manipulate your data (JSON) and do the DOM implementation on your HTML or template that you're using.
In case you insist on following your approach: don't wrapper your anchor tag by jQuery just write it as a string. something like <a href=#>${data[property]}</a>

You're meant to make it a string, not a anchor, otherwise (because jQuery) it'll make data[property] equal to the URL you get directed to when you click on the anchor, because this:
<a href=#>v</a>
Was turned into this:
http://localhost:5000/#
Which is where you'd get directed to if you'd click on the link. Try using a simple template literal instead (it's also smaller and simpler than concatenating strings):
function makeEditable(data) {
for (var property in data) {
if (data.hasOwnProperty(property)) {
var tag = `${data]property]}`;
data[property] = tag;
}
}
return data;
}

I realised that modifying JSON would not work and instead decided to append to the div directly.
This was the function after modification:
function makeEditable(data) {
var fdata={};
for (var property in data) {
if (data.hasOwnProperty(property)) {
$("#vocab-json").append('<b>'+property+'</b>'+''+data[property]+'<br>');
}
}
return fdata;
}

Related

Using JSON Response Data to apply a background-image style to a JS loop item

I'm using an ajax request to display a loop of drupal posts.
$( document ).ready(function() {
$.ajax({
type:"GET",
url:"https://www.nba.com/api/1.1/json/?type=photo_gallery&tags=community&size=4",
success: function(data) {
let galleriesWrapper = document.getElementById("wrapper--community-galleries");
function create(tagName, props) {
return Object.assign(document.createElement(tagName), (props || {}));
}
function ac(p, c) {
if (c) p.appendChild(c);
return p;
}
for (var i=0; i< data.content.length; i++) {
var link = create("a", {
className: "wrapper--single-gallery",
href: data.content[i].url,
style: "background-image:url(" + data.content[i].images.large + ")"
});
var videoTitle = create("div", {
className: "single-gallery--title",
src: data.content[i].title
});
videoTitle.textContent = data.content[i].title;
ac(galleriesWrapper, ac(link, videoTitle));
}
},
dataType: "jsonp",
});
});
I'm pulling the post's image from the .json file and injecting it as the css background-image for that particular item in the loop.
This all works perfectly fine in Chrome and Firefox but fails in IE.
I'm using the helper function "create" which uses obect.assign to easily create and append DOM elements.
My first failure it IE was because I needed an object.asign polyfill. No items were showing up.
The Polyfill:
if (typeof Object.assign != 'function') {
Object.assign = function(target) {
'use strict';
if (target == null) {
throw new TypeError('Cannot convert undefined or null to object');
}
target = Object(target);
for (var index = 1; index < arguments.length; index++) {
var source = arguments[index];
if (source != null) {
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
}
return target;
};
}
Once I added this to the top of my JS, the loop items get displayed in IE, but the code I am using to inject the background-image style inline fails (I see an item with the proper post title from the ajax request, but the background is blank).
EDIT: Seems the issue is with trying to append the style property.
When I use this:
var link = create("a", {
className: "wrapper--single-gallery",
href: data.content[i].url,
style: "background-image:url(" + data.content[i].images.large + ")"
});
The link is created with the class name and the href, but the style property is not appended.
Thinking it had something to do with how I spliced in the url from the json request data, I tried just using the style "color:red". This created the same result of not appending the style tag to the link.
JS FIDDLE EXAMPLE: https://jsfiddle.net/nolaandy/w3gfawcg/
The problem is not Object.assign itselt. The problem is how you're setting the styles.
You have two options:
Set every style to the style property of the element
link.style.backgroundImage = "url(" + data.content[i].images.large + ")"
Replace Object.assign by _.merge and set the styles as an object (https://lodash.com/)
function create(tagName, props) {
// Replace Object.assign by _.merge (https://lodash.com/)
return _.merge(document.createElement(tagName), (props || {}));
}
// ....
var link = create("a", {
className: "wrapper--single-gallery",
href: data.content[i].url,
// Update the styles to be an object
style: {
backgroundImage: "url(" + data.content[i].images.large + ")"
}
});
I would go for the second one since it's cleaner: https://jsfiddle.net/w3gfawcg/7/
Blake's responses led me to the solution. Thank you Blake.
This is the easy solution:
for (var i=0; i< data.content.length; i++) {
var link = create("a", {
className: "wrapper--single-gallery",
href: data.content[i].url,
});
$(link).css("background-image", "url(" + data.content[i].images.large + ")");
}
From Blake/Mozilla:
"Styles should not be set by assigning a string directly to the style property (as in elt.style = "color: blue;"), since it is considered read-only, as the style attribute returns a CSSStyleDeclaration object which is also read-only. Instead, styles can be set by assigning values to the properties of style."
This is weird seeing as Firefox ran the original code properly even though I was doing it wrong.

TypeError on split()

I am trying to hide elements based on whether the user has added the class numbers to the database which I am retrieving through json data. If all the class numbers are present on the component I want to hide it.
At the moment I keep getting this error:
TypeError: $(...).data(...).split is not a function
export function VisaToggleComponent() {
let json = {
visa_subclass:[462,500,801]
};
let elements = document.querySelectorAll('[data-visa-hide]');
console.log(elements);
$(elements).each(function() {
let data = json.visa_subclass,
target = $(this).data('visa-hide').split(',').map(Number);
console.log(target);
for (let i in data) {
let val = data[i];
let index = target.indexOf(val);
if(index > -1) {
$(this).hide();
}
}
});
}
split is a method of the String object. Since you're getting
TypeError: $(...).data(...).split is not a function
It means $(this).data('visa-hide') is not a string.
To be honest, I didnt try to understand your code, however if you think $(this).data('visa-hide') is string-like data type you have to change $(this).data('visa-hide').split(',') to String.prototype.split.call($(this).data('visa-hide'), ',')

Create or update script of given HTML

The code is getting an HTML file and configObject and according to this config object need to modify this htmlFile content(the code is working)
This is the input:
please notice that that in the firstobj of the array (there is scenario which the next attribute is not provided which say to put the new script at the bottom) the next said put the script tag after script that have ID "ui-boot"
var extendedHtmlObject = [{
type: 'script',
action: 'new',
content: 'console.log(‘hello world’);',
next: "ui-boot"
}, {
type: 'script',
id: "ui-boot",
action: 'upd',
innerElem: [{
type: 'attr',
id: 'data--ui-comersion',
content: '1.17'
}, {
type: 'attr',
id: 'src',
content: '/test/test2/-co.js'
}]
}
This is the main function:
getExtend: function(htmlContent, extendedHtmlObject) {
var self = this;
if (extendedHtmlObject) {
extendedHtmlObject.forEach(function(configs) {
switch (configs.type) {
case 'script':
htmlContent = self._handleScriptElement(htmlContent, configs);
break;
case 'head':
break;
}
});
}
return htmlContent;
},
This method determines if I need to create a new script or update existing script attributes according to the input object
_handleScriptElement: function(htmlFilecontent, configEntry) {
var oExtendedHTML = htmlFilecontent;
switch (configEntry.action) {
case 'new':
oExtendedHTML = this._createNewScript(htmlFilecontent, configEntry);
break;
case 'upd':
var sParsedHtml = this._htmlParser(oExtendedHTML);
oExtendedHTML = this._updateScript(oExtendedHTML, configEntry, sParsedHtml);
oExtendedHTML = this._convertHtmlBack(oExtendedHTML);
break;
}
return oExtendedHTML;
},
This is the method for creating new script with two option
1. the first fork need to parse the html
2. the second for doesn't.
_createNewScript: function(htmlFilecontent, configEn) {
var sScriptContent = this._createScript(configEntry.content);
if (configEn.next != null) {
var sParsedHtml = this._htmlParser(htmlFilecon);
$(sScriptContent).insertAfter(sParsedHtml.find('#' + configEn.next));
htmlFilecontent = this._convertHtmlBack(sParsedHtml);
} else {
//when the script is at the end of file
var iHeadEndTagPos = htmlFilecon.search("(| )* )*head(|*>");
htmlFilecon = htmlFilecon.substr(0, iHeadEndTagPos) + sNewScript + htmlFilecon.substr(iHeadEndTagPos);
}
return htmlFilecon;
},
This code is redundant and not efficient(I'm fairly new to JS), could I maybe improve it with JS prototype?
I want to do the parse just once in the start and the parseBack at the end(of looping the input object) but the problem is that in the createNewScript the second fork doesn't need to use the parser...
The code inside module of requireJS
update
To make it more clear, The external API have two input and one output
HTML file content
config object which determine how to update the HTML, for example to create new script (as the first object in the array
extendedHtmlObject ) or update existing script content such as
attributes values)
the output should be the extended HTML with all the modification
**update 2 **
If I can provide additional data to make it more clear please let me know what.
Maybe have a look at some of the multitude of templating systems out there.
Alternatively, simplify your html creation. If you use innerHTML to create DOM nodes, have the HTML file content just be a long string containing the full markup with placeholders for all values/classnames/etc.
eg: <script src="{{src}" id="{{id}}"></script> and then just replace all the placeholders with the values. Then you can just update the innerHTML of the target with this string.
If you use DOM node creation, document.createElement(), create all those template nodes in advance (documentFragments are really handy here). Then have one function that will check the 'extendedHtmlObject' settings and select either the node already on the page in case of update, or select your premade nodeList. Once you have the html structure as nodes, hand it off to a different function that will update all the nodes inside the structure with the new values in the 'extendedHtmlObject'. You can then append the nodes to their target.
In both cases you can juist throw the back and forth html parsing and branching away.
pseudocode:
(function myModule() {
var templates = {},
createTemplates = function() {
// Create all templates you'll use and save them.
// If you create nodes, also include a mapping from each node to its respective value
// eg: children[0] = 'label', children[1] = 'value'
},
updateTemplate = function( template, values ) {
// INNERHTML: for each placeholder 'valueName' in string template, replace placeholder by values[valueName]
// NODE CREATION: use the node mapping in the template to select each node in the template and replace its current value with values[valueName]
return template;
},
getTemplate = function( type, reference ) {
return (type === 'new') ? templates[reference] : $('#' + reference);
},
update = function( config ) {
var html = getTemplate(config.action, (config.next || config.id)),
updated = updateTemplate(html, config);
if (config.next) $('#' + config.next).insertAfter(html);
else $('#' + config.id).innerHTML = html /* OR */ $('#' + config.id).appendChild(html);
},
init = function() {
createTemplates();
};
}());
TLDR: Create new or grab existing HTML first, then update either the string or the nodes, then determine where it has to go. Make everything as general as possible.
The html creation function should be able to create any html element/update any template, not just script nodes.
The update function should not care if the html its updating existed before or is newly generated.
The function that puts the html back into the website should not be inside the creation functions.
This way you'll probably at first end up with just one big switch statement that will call the same functions with different parameters, which can easily be replaced by adding another config setting to the 'extendedHtmlObject'.
UPDATE:
I've created an example of a usable structure. It uses the DOM methods (createElement, etc) to create a new document containing the updated input string. You can change it to better match your specific input/output as much as you want. It's more of an example of how you can create anything you want with just a few general functions and alot of config settings.
This doesn't use the templetes I spoke before of though, since that would take longer to make. Just have a look at handlebars.js or mustache.js if you want templating.
But it better matches the structure you had yourself, so I hope it's easier for you then to pick the parts you like.
var originalHTML = '<script id="ui-boot" src="path/to/script/scriptname.js"></script>',
imports = {},
// IMPORTANT NOTE:
// Using a string as the body of a script element might use eval(), which should be avoided at all costs.
// Better would be to replace the 'content' config setting by a 'src' attribute and ahve the content in a seperate file you can link to.
configAry = [
{ // script-new
type: 'script',
action: 'new',
content: 'console.log(‘hello world’);',
next: "ui-boot"
},
{ // script-upd
type: 'script',
id: "ui-boot",
action: 'upd',
innerElem: [
{
type: 'attr',
id: 'data--ui-comersion',
content: '1.17'
},
{
type: 'attr',
id: 'src',
content: '/test/test2/-co.js'
}
]
}
],
// Extra helpers to make the code smaller. Replace by JQuery functions if needed.
DOM = {
'create' : function create( name ) {
return document.createElement(name);
},
// Create a new document and use the string as the body of this new document.
// This can be replaced by other functions of document.implementation if the string contains more than just the content of the document body.
'createDoc' : function createDoc( str ) {
var doc = document.implementation.createHTMLDocument('myTitle');
doc.body.innerHTML = str;
return doc;
},
'insertAfter' : function insertAfter(node, target, content ) {
target = content.querySelector(target);
if (target.nextSibling) target.nextSibling.insertBefore(node);
else target.parentNode.appendChild(node);
return content;
},
'replace' : function replace( node, target, content ) {
target = content.querySelector(target);
target.parentNode.replaceChild(node, target);
return content;
},
'update' : function update( node, textContent, attributes ) {
if (textContent) node.textContent = textContent;
return (attributes) ?
Object.keys(attributes).reduce(function( node, attr ) {
node.setAttribute(attr, attributes[attr]);
return node;
}, node) :
node;
}
},
// The actual module
moduleHTMLExtender = (function( imports ) {
var createExtension = function createExtension( extension, content ) {
switch (extension.action) {
case 'new' :
return {
'method' : 'insertAfter',
'node' : DOM.update(DOM.create(extension.type), extension.content),
'target' : '#' + extension.next
};
break;
case 'upd' :
return {
'method' : 'replace',
'node' : DOM.update(content.querySelector('#' + extension.id).cloneNode(), null, extension.innerElem.reduce(function( map, config ) {
if (config.type === 'attr') map[config.id] = config.content;
return map;
}, {})),
'target' : '#' + extension.id
}
break;
default:
return false;
break;
}
},
addExtensions = function addExtensions( content, extensions ) {
return extensions.reduce(function( content, extension ) {
return DOM[extension.method](extension.node, extension.target, content);
}, content);
},
// Returns new document as an [object Document]
extendContent = function extendContent( content, extensions ) {
var doc = DOM.createDoc(content),
toExtend = (extensions) ?
extensions.map(function( extension ) {
return createExtension(extension, doc);
}) :
null;
var res = null;
if (toExtend) return addExtensions(doc, toExtend);
else return doc;
};
// Export public interface. Replace this by your require.js code or similar.
return {
'extendContent' : extendContent
};
}( imports )),
extendedHTMLDoc = moduleHTMLExtender.extendContent(originalHTML, configAry);
console.log(extendedHTMLDoc.documentElement.outerHTML);
// output = '<html><head><title>myTitle</title></head><body><script id="ui-boot" src="/test/test2/-co.js" data--ui-comersion="1.17"></script><script>console.log(hello world);</script></body></html>';

How to extract data from array in javascript

I have an object (array type) ,its console representation looks like following image . please see the image
This array is created by restangulr using following code ,
restangularProvider.addResponseInterceptor(function (data, operation, what, url, response, deferred) {
if (operation == "getList") {
var extractedData;
extractedData = data.result;
extractedData.paginginfo = data.paginginfo;
return extractedData;
}
if (operation != "get") {
var item = { status: response.status };
feedBackFactory.showFeedBack(item);
}
return response.data;
});
How can I read the elements from this array, I want to extract properties like paginginfo ,also object collection
// The EDIT :1 js libraries I used here angularjsu 1.3.4, and restangular 1.4
My app.js : here I configured rest angular provider
restangularProvider.addResponseInterceptor(function(data, operation, what, url, response, deferred) {
if (operation == "getList") {
var extractedData;
extractedData = data.result;
extractedData.paginginfo = data.paginginfo;
return extractedData;
}
if (operation != "get") {
var item = {
status: response.status
};
feedBackFactory.showFeedBack(item);
}
return response.data;
});
// according to my knowledge this function will intercept every ajax call (api calls) and modify the response , unfortunately I need to apply custom modification because the getlist method must return collection but my api returning object, so according to restangular ,the above code is the possible solution, and here its fine its fetching the data.
userservice.js : this is angular service which using restangular
function(restangular) {
var resourceBase = restangular.all("account");
this.getUsers = function(pagenumber, recordsize) {
var resultArray = resourceBase.getList({
page: pagenumber,
size: recordsize
}).$object;
};
};
according to my knowledge .$object in restangulr resolve the promise and bring back the data, also I am getting the resultArray its looks like in the image in the console, here I can log this array so I think I got all the data from server and filled in this object. I applied some array accessing techniques available jquery and JavaScript like index base accessing , associate accessing but I am getting undefined ie.
resultArray[1] //undifiend;
In angular you can use angular.forEach(items, function(item){ //your code here});
Where items is the array you want to traverse.
If you want to access to one specific position use [], for example var item= items[5].
Then you can do item.property.
UPDATE
Your problem is that you are setting properties in an Array JS Object:
extractedData.paginginfo = data.paginginfo;
You should return the object data like it is and in your controller do something like:
var results= data.result;
var pagInfo= data.paginationInfo;
angular.forEach(results,function(result){});
It looks like the array is numerically indexed (0..1..5); you should be able to simply iterate through it using ForEach (in Angular) or .each (in Jquery).
Something like (JQuery):
$.each(array, function(key, value)
{
// key would be the numerical index; value is the key:value pair of the array index's element.
console.log(value.firstname); // should print the firstname of the first element.
});
First of all, as I said in the comments, you shouldn't be attaching named properties to arrays. Return an object thact contains what you need:
if (operation == "getList") {
return { values: data.result, paging: data.pagingInfo };
}
The getList() method returns a promise, so you need to use that:
this.getUsers = function(pagenumber, recordsize) {
resourceBase.getList({
page: pagenumber,
size: recordsize
}).then(function (data) {
console.log(data.values[0]);
console.log(data.paging.totalRecords);
});
};

use javascript to find parameter in url and then apply if then logic

I am trying to make my page perform an action only if it sees that a particular parameter is present in the url.
I essentially want the javascript code to do this:
consider an example page such as: http://www.example.com?track=yes
If a page loads that contains the parameter 'track' within the url, print 'track exists', else if the 'track' parameter doesn't exist print 'track does not exist'
This should work:
if (window.location.search.indexOf('track=yes') > -1) {
alert('track present');
} else {
alert('track not here');
}
Use something like the function from Artem's answer in this SO post:
if (getParameterByName('track') != '') {
alert ('run track');
}
It's not hard to split up the query string to find the relevant bits:
var path = location.substr(1), // remove ?
queryBits = path.split('&'),
parameters = {},
i;
for (i = 0 ; i < queryBits.length ; i++) {
(function() { // restrict keyval to a small scope, don't need it elsewhere
var keyval = queryBits[i].split('=');
parameters[decodeURIComponent(keyval[0])] = decodeURIComponent(keyval[1]);
}());
}
// parameters now holds all the parts of the URL as key-value pairs
if (parameters.track == 'yes') {
alert ('track exists');
} else {
alert ("it doesn't");
}
What you're looking for is called the Query String or Query Parameter. See this function to get it w/o the use of plugins like jQuery: How can I get query string values in JavaScript?
You can use the window.location.search property:
if(/(^|&)track(&|$)/.test(window.location.search.substring(1))) {
alert('track exists!');
} else {
alert('it doesn\'t...');
}

Categories

Resources