Testing function that creates a list of DOM components - javascript

I have a function which creates an Array of components. Each component is an outer div with a few inner divs.
function createDivs(quizQuestions) {
var returnElements = new Array();
$.each(quizQuestions.questions, function(i, val){
// create the div.
quizDiv = $('<div class="questionContainer radius">')
questionDiv = $('<div class="question"><b><span>QuestionText</span></b></div>');
quizDiv.append(questionDiv);
// Now change the question div text.
questionDiv.text = val.question;
answerDiv = $('<div class="answers">');
// ...
// ...
// Now the answers.
questionDiv.append(answerDiv);
returnElements[i] = quizDiv;
});
return returnElements;
I pass JSON such as:
{questions:[{"question":"Name the best Rugby team?",
"answers":["Leinster", "Munster", "Ulster", "Connaught"],
"correct_answer":"Leinster"},
{"question":"Name the best DJ?",
"answers":["Warren K", "Pressure", "Digweed", "Sasha"],
"correct_answer":"Leinster"}]};
I'd like to write a simpe unit test so that I could test the array of div returned made sense
Any tips?
Also, are my better to return a DOM component or just text? The latter would be easier to test.
Thanks.

Not sure exactly what you want to test but it is far more performant to create as much html in strings as you possibly can to reduce function calls. Also append is expensive so ultimately making one string for all the new content represented by the JSON will be the biggest performance gain.
In my opinion it also makes code more readable since fragments are in same order as the would be in html editor
Example(my preferece is creating an array of all the string fragments, concatenation also commonly used):
var newcontent = [];
$.each(quizQuestions.questions, function(i, val) {
newcontent.push('<div class="questionContainer radius">');
newcontent.push('<div class="question"><b><span>' + val.question + '< /span></b > < /div>');
$.each(val.answers, function(idx, answer) {
newcontent.push('<div class="answers">' + answer + '</div > ')
})
newcontent.push(' </div></div > ');
});
Then to add content to DOM:
$('#someDiv').append( newcontent.join(''));
disclaimer: Not fully checked for proper closing/nesting of tags.

Related

How can I create a syntax like vue js in vanilla JavaScript?

<div id="">
<span>{{msg}}</span>
</div>
Let's think msg is variable of JavaScript and now I want to get the parent tag of {{msg}} and push a new value by innerHTML, here {{msg}} working as an identity.
demo JavaScript example:
<script>
var msg = "This is update data";
{{msg}}.parentElement.innerHTML=msg;
</scritp>
This is not actual JavaScript code, only for better understanding.
You can use jquery easily to find that element and then replace the text
var msg = "This is update data";
$(`span:contains(${msg})`).html("Its New");
In javascript:
var spanTags = document.getElementsByTagName("span");
var msg = "This is update data";
var found;
for (var i = 0; i < spanTags.length; i++) {
if (spanTags[i].textContent == msg) {
found = spanTags[i];
break;
}
}
Now, you have found that element in found and you can now change its text
if (found) {
found.innerHTML = "New text";
}
The simplest approach is to treat the entire document as a string and then re-parse it when you're done.
The .innerHTML property is both an HTML decompiler and compiler depending on weather you're reading or writing to it. So for example if you have a list of variables that you want to replace in your document you can do:
let vars = {
msg: msg, // pass value as variable
test_number: 10, // pass value as number
test_str: 'hello' // pass value as string
};
let htmlText = document.body.innerHTML;
// find each var (assuming the syntax is {{var_name}})
// and replace with its value:
for (let var in vars) {
let pattern = '\\{\\{\\s*' + var + '\\s*\\}\\}';
let regexp = new RegExp(pattern, 'g'); // 'g' to replace all
htmlText = htmlText.replace(regexp, vars[var]);
}
// Now re-parse the html text and redraw the entire page
document.body.innerHTML = htmlText;
This is a quick, simple but brutal way to implement the {{var}} syntax. As long as you've correctly specified/designed the syntax to make it impossible to appear in the middle of html tags (for example <span {{ msg > hello </ }} span>) then this should be OK.
There may be performance penalties redrawing the entire page but if you're not doing this all the time (animation) then you would generally not notice it. In any case, if you are worried about performance always benchmark your code.
A more subtle way to do this is to only operate on text nodes so we don't accidentally mess up real html tags. The key to doing this is to write your own recursive descent parser. All nodes have a .childNodes attribute and the DOM is strictly a tree (non-cyclic) so we can scan the entire DOM and search for the syntax.
I'm not going to write complete code for this because it can get quite involved but the basic idea is as follows:
const TEXT_NODE = 3;
let vars = {
msg: msg, // pass value as variable
test_number: 10, // pass value as number
test_str: 'hello' // pass value as string
};
function walkAndReplace (node) {
if (node.nodeType === TEXT_NODE) {
let text = node.nodeValue;
// Do what you need to do with text here.
// You can copy the RegExp logic from the example above
// for simple text replacement. If you need to generate
// new DOM elements such as a <span> or <a> then remove
// this node from its .parentNode, generate the necessary
// objects then add them back to the .parentNode
}
else {
if (node.childNodes.length) {
for (let i=0; i<node.childNodes.length; i++) {
walkAndReplace(node.childNodes[i]); // recurse
}
}
}
}
walkAndReplace(document.body);

translation of vanilla JavaScript to jQuery

Below is a JS function:
I'd like to update this so it uses jQuery but I am having difficulty figuring out how to translate it.
function pathContents(fileList) {
var list = document.createElement('ul');
for (file in fileList) {
var item = document.createElement('li');
item.appendChild(document.createTextNode(fileList[file]));
list.appendChild(item);
}
return list;
}
UPDATE:
Removing the call to pathContents() and replacing it with Malvolio's code - this is the browser output -
Why isn't it displaying the data as list items in an unordered list?
The jQuery equivalent "all-the-way" would be:
function pathContents(fileList) {
var $list = $('<ul/>');
$.each(fileList, function () {
$('<li/>').text(this).appendTo($list);
});
return $list;
}
The use of this inside $.each is often seen in jQuery code, but it is cleaner to use the function arguments instead:
$.each(fileList, function (_, file) {
$('<li/>').text(file).appendTo($list);
});
But the pure JavaScript way really is not that bad. For one, you cannot beat the performance of it.
you can try following :):
function pathContents(fileList) {
var list = $('<ul />');
for (file in fileList) {
list.append($('<li />').text(fileList[file]));
}
return list;
}
or you can return list.html();
And yes, as many users mentioned, you will have some performance loss compared to pure javascript
The modern version of your function would be
let pathContents =
fileList =>
$('<ul/>').append(fileList.map(file => $('<li/>').text(file)));
This version uses jQuery to create and append to DOM objects, the fat-arrow notation for functions, and the .map method on lists. It doesn't rely on the unreliable in operator and avoids the Bobby Tables exploit.
I kept your function and variable names, but I think you should consider more descriptive names.
For better performance only parse the HTML string at the very end:
function pathContents(fileList) {
var ul = '<ul>';
for( var file of fileList )
ul += `<li>${file}</li>`;
ul += '</ul>';
return $.parseHTML(ul);
;
}
console.log( pathContents([1,2,3])[0].outerHTML )
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
or as simple as:
function pathContents(fileList) {
var ul = '<ul><li>' + fileList.join('</li><li>') + '</li></ul>';
return $.parseHTML(ul);
}
By using $.parseHTML the parsed output will be stripped of any <script> tags. If you do want to keep them, pass true as the second argument for that method. See further discussion about this.
Please be careful when creating DOM from javascript: make sure the
input is sanitized (does not contain any malicious code).

What methods or procedures need to be in place to append in jQuery a simple Java 'class' instance that represents a jQuery implementation?

I have this class in Javascript: call it Caption:
function Caption
{
var ...
function get...()
{ }
function set...(...)
{ ... }
return( { get...: get...
, set...: set...
});
}
This is only one component of a larger system and is part of an outer class called Filter. I would love to be able to when creating the jQuery for the object to be able to say:
tblFilter.append(getCaption())
which would get the Caption class instance variable and append the jQuery representation of it. Do I need to inherit from jQuery to make that happen? Like this?
function Caption
{
var ...
function get...()
{ }
function set...(...)
{ ... }
var that = jQuery();
that.get... = get...;
that.set... = set...;
return(that);
}
?
If that's true, what I'm not sure of is what function/method I need to write to produce the jQuery that is produced to be appended to the outer jQuery. Does this make sense and if so, what am I missing?
Edit:
Let me elaborate - Ok I'm creating my own version of a Data Table. Yes I know jQueryUI has but with this, I pass the data and it renders the data. There's a Filter component that really can't be created/constructed publicly but just accessed by a getFilter() method. Every time the Filter instance is changed, thanks to a home grown listener/observer/observable pattern, the filter is erased and re-rendered. As of now much of it is hard coded. When I first wrote it it was more loosely written as it was for a programming assignment to get a job and I did it within 2 days I had. Now that it's done I'm trying to implement it more generically into a library. When I get to appending the Caption and trying to make it render it based on the Caption object, I have no ID for a Caption or a parent object to remove. Ideally I'd love to be able to do:
var objDataTable = new DataTable(/*parameters*/);
$(/*parent selector/*).append(objDataTable);
I'm self taught so my Object Oriented Programming is a bit all over the place, but if I had a class, that I wanted to 'HTMLize' and insert values from into the dom, I would do it like this:
var Caption = {
'amount':10,
'word':'Hello!',
'getCaption': function(appendToObject){
var div = $('<div />');
var ul = $('<ul />');
var li = $('<li />', {
'text' : this.word
});
li.appendTo(ul);
var li2 = $('<li />', {
'text' : this.amount
});
li2.appendTo(ul);
ul.appendTo(div);
div.appendTo(appendToObject);
}
};
var caption_instance = Caption;
caption_instance.getCaption('#wrapper');
var second_caption = Caption;
second_caption.amount = 13;
second_caption.word = 'Goodbye';
caption_instance.getCaption('#wrapper');
You can see it in action here:
http://codepen.io/EightArmsHQ/pen/bVrapW
If you are including jQuery globally you don't need to pass anything to the function, you can just get going.
The important part here is the var xxx = $('<DOM_TAG />'); which lets you create elements to append to the DOM. This can be found here: http://api.jquery.com/jquery/#jQuery2
If I've misunderstood the question just let me know and I'll remove the answer.

jQuery command other than appendTo

I am trying to clear an LI tag's previous data.
Is there another command that will work other than appendTo?
Here is what my code currently looks like:
var obj = JSON.parse(data);
$.each(obj, function(index, item)
{
$('<li>').
text(item.datestamp+' - '+item.comment).
appendTo($('#pCodeComment'));
});
I asked a similar question not too long ago. I just want to know if there is another command other than appendTo that will clear out the previous data.
You should empty the list before you loop to populate it, then just continue doing what you are already doing.
var obj = JSON.parse(data);
$('#pCodeComment').empty();
$.each(obj, function(index, item)
{
$('<li>').
text(item.datestamp+' - '+item.comment).
appendTo($('#pCodeComment'));
});
And after optimizing a little bit:
var obj = JSON.parse(data); // i'm assuming `obj` is an array
var htmlToInsert = obj.map(function (item) {
return '<li>' + item.datestamp + ' - ' + item.comment + '</li>';
}).join('');
$('#pCodeComment').html(htmlToInsert);
Note: the above is vulnerable to XSS. See this so question for ways to fix it, or just use the original.
$.replaceWith()
might be what you are looking for, or as Kevin B pointed out
$.html()
replaceWith would need an element selected to replace. whilst html will populate a parent element to insert a new dom fragment into.

How to use JS object/classes to save repeating html

First I'm brand new to JS but have an idea that object classes are what I should be looking into, yet I can't find a straightforward tutorial that doesn't shoot off into arrays,arguments or silly alert boxes! All I'm looking for is something like this:
<head>
<Script Language="JavaScript">
function Class (color, bodypart, item)
Var1 =newClass ('red', 'hand', 'ball')
Var2 =newClass ('green', 'foot, 'bat')
btw I have hundreds of these Vars to write, this is the reason for looking into this method
then put these Vars into some HTML:
('<div><span style="color:'+color+';>'+bodypart+'</span><br/>'+item+'</div><br/>');</script></head>
now in the body I want to call one of the Vars (e.g. Var1) and put it (with the HTML) into a div so it would automatically generate:
<div><span style="color:red;">hand</span><br/>item</div><br/>
My question:what exactly do I put in the head and body to make this happen??
I'm guessing something to do with GetElementById, Classes, etc but I can't find the right syntax to make a simple example work.
Aaaaany help would be fantastically appreciated for such a noob! (p.s. I have spent the last full 2 days reading tutorials and forum posts but they keep losing me in far more complex things than I can grasp)
You sound like you're in over your head at this point, and if you plan on doing a lot of javascript, you're much better off finishing off those tutorials on arrays and such.
Having said that, to help you jump ahead a little, here's what you need to do:
Go to http://www.jquery.com and follow the instructions on how to include jQuery onto your page
Write a javascript function that uses jQery to transform a javascript array into your html
That function might look something like:
var parts = [
['red', 'hand', 'ball'],
['green', 'foot', 'ball']
];
makePart = function(part){
var div = $('<div />');
var span = $('<span />').css({color:part[0]}).text(part[1]);
div.append( span );
div.append( $('<br />') );
div.append( part[2] );
$('body').append( div ).append( $('<br />') );
}
// this next line makes all your parts
$.each( parts, function(index, part) { makePart(part); } );
// while this would only make the first part
makePart( parts[0] );
There are a number of templating libraries around that will turn arrays of data into DOM structures. However, if your requirements are simple then you can put the data into an array and iterate over it to generate HTML or DOM elements:
var data = [
['red', 'hand', 'ball'],
['green', 'foot', 'bat']
];
function genElements(data) {
var frag = document.createDocumentFragment();
var div = document.createElement('div');
var span = document.createElement('span');
var br = document.createElement('br');
var b, d, s, x;
for (var i=0, iLen=data.length; i<iLen; i++) {
x = data[i];
d = frag.appendChild(div.cloneNode());
s = d.appendChild(span.cloneNode());
s.style.color = x[0];
s.appendChild(document.createTextNode(x[1]));
div.appendChild(br.cloneNode());
frag.appendChild(br.cloneNode());
alert(frag.childNodes.length);
}
}
Alternatively you can generate HTML and insert that:
function genElements(data) {
var html = [];
var x;
for (var i=0, iLen=data.length; i<iLen; i++) {
x = data[i];
html.push('<div><span style="color:' + x[0] + '">' +
x[1] + '</span><br>' + x[2] + '</div><br>');
}
return html.join('');
}
Then insert it wherever is appropriate. But you could do exactly the same logic on the server and just send the HTML, avoiding all the issues with client side scripting. Overall it would be much more efficient and robust.

Categories