Updating variables inside js template literals - javascript

I have 1 string named a which stores large html element with js template literals by calling objects property . I am keeping those element in my div id hello. Now I want to update data inside js template literal when I change objects property without keeping elements of a in div again.
my code:-
var object = {name:'alisha', age:18 , count:1000};
var a = `<div class="nav">
<span>name:</span><span>${object.name}<span><br>
<span>age:</span><span>${object.age}<span><br>
<span>count:</span><span>${object.count}<span><br>
<input type="text">
</div>`;
var el = document.getElementById('hello');
el.innerHTML = a;
var replace_count = 0;
setInterval(function(){
replace_count = replace_count + 1;
var object.count = replace_count;
},2000)
Yes I have alternative idea of doing this but I can't follow those idea
My Ideas:-
I can give id or class to each span containing js template literals.
I can keep all the elements of variable a by updating objects property in div of id hello.
Please give me idea how can I do this.

You need to place the object and the string within a function to get this to work.
function doReplace(count) {
var object = {name:'alisha', age:18 , count};
var a = `<div class="nav">
<span>name:</span><span>${object.name}<span><br>
<span>age:</span><span>${object.age}<span><br>
<span>count:</span><span>${object.count}<span><br>
<input type="text">
</div>`;
var el = document.getElementById('hello');
el.innerHTML = a;
}
var replace_count = 0;
doReplace(replace_count);
setInterval(function(){
replace_count = replace_count + 1;
doReplace(replace_count);
},200)
<div id="hello"></div>
I change the time to 200ms instead of 2s so it is easier to see.
But the string literals must be recreated each time. It is a literal, not a template.
If you don't want to replace the entire innerHTML every time, then you need to set the innerHTML once and then change the textContent of the correct span during each step of the interval.
But, as I said above, these are not really templates and the ES6 name of template literal is misleading. There is no binding in vanilla JavaScript and Template Literals are a one time generation. So you need to either regenerate the literal after the data changes or just change the innerHTML or textContent of a specific element.
UPDATE
You must understand that vanilla JavaScript does not have built in data binding. Template Literals are really just a simplification of string concatenation. But they are not bindable templates.
You can always look into any of the multiple of template tools out there. If you don't want something as heavy as React, Vue or Angular then you can look into things like Rivets, Simulacra, knockout or any other of the many small data binding libraries out there.
var object = {name:'alisha', age:18 , count:0};
function render(obj) {
var binders = document.querySelectorAll('[bind]');
binders.forEach(
(el) => {
var field = el.getAttribute('bind');
if (field && (obj[field] !== undefined)) {
el.innerHTML = obj[field];
}
}
);
}
var replace_count = 0;
render(object);
setInterval(function(){
object.count++;
render(object);
},200)
<div class="nav">
<div>name: <span bind="name"></div>
<div>age: <span bind="age"></div>
<div>count: <span bind="count"></div>
<input type="text">
</div>
This is a simple binding library I just wrote. It is under 300 bytes. It probably needs some enhancements, but it shows that you can create a very simple binding library.
If these answers don't help then please describe why they don't help. I can help if you don't describe why.

Here this simple function do everything.
const getTemplateByInterpolatingParams = (templateHTML, placeholders) => {
try {
const names = Object.keys(placeholders);
const values = Object.values(placeholders);
return new Function(...names, `return \`${templateHTML}\`;`)(...values);
} catch (error) {
console.log('Error in template interpolation', error);
return error;
}
};
Pass your template with template literals and object with same keys

If you will use jQuery, it is easy:
HTML
<span>count:</span><span class="count"></span>
JS
var replace_count = 0;
setInterval(function(){
$('.nav span.count','#hello').text(replace_count++);
}, 2000);
This will change the data in all span.count in div#hello every 2 sec.
If you render ${object.count}, than it is a normal Text, or you use view.js or angular with special template-engine.
test it on fiddle:
https://jsfiddle.net/chrobaks/r2a1anw6/

Related

Onclick event in literal string in Preload for electron

Well this is a thing i need help of some one who know how to get the element from a foreach that innerHTML into a table data that comes from a data base, i did it by this way but it is not the most optimize way, so i need help to do it by other way, i add an onclick event with javascript and the only thing i make by doing this is that the only button that works is the one on top of the table or the bottom button of the table.
This is the way i make it work:
//Preload
const { contextBridge, ipcRenderer } = require("electron");
contextBridge.exposeInMainWorld(
"electron", {
printmovieonpreload: (results) => ipcRenderer.on("send-movie", (event, results) => {
mylist.innerHTML = " "
results.forEach(elements => {
mylist.innerHTML += `<tr><td> ${elements.movie-name} </td>
<td> ${elements.movie-duration} min </td><td><button id="btn" value="${elements.id-movie}" "/*this get all the onclick events of the page*/${onclick = deletefromtable}" type="button" class="fas cli fa-trash-alt"></button></td>
</tr>`;
});
})
});
async function deletefromtable(e) {
/* this is were i filter from id of the onclick event and how i reduce the value of the button to delete it*/
if (e.srcElement.id == "btn") {
const obj = {
id: e.srcElement.value
}
await ipcRenderer.invoke('delete_movie', obj);
}
}
It's better to not do this by string interpolation, but by letting the browser (in Electron's terms, Chromium) create elements of the wanted type and then setting their contents and appending them to the parent. This can (and should) be done like so:
// inside your forEach function
var tr = document.createElement ("tr");
var td1 = document.createElement ("td");
var td2 = document.createElement ("td");
var td3 = document.createElement ("td");
var btn = document.createElement ("button");
td1.textContent = elements ["movie-name"];
td2.textContent = elements ["movie-duration"] + " min";
btn.id = "btn";
btn.classList.add ("fas", "cli", "fa-trash-alt");
btn.type = "button";
btn.value = elements ["id-movie"];
btn.onclick = delete_from_table;
td3.appendChild (btn);
tr.appendChild (td1);
tr.appendChild (td2);
tr.appendChild (td3);
myList.appendChild (tr);
For this to work, however, you must meet the following conditions:
The index strings for the elements array must be correct. I'm pretty sure that your code using string interpolation would throw errors, because - is an operator and cannot be used when referencing indices using the dot operator.
delete_from_table must be a function, not a string. Since it is not included in the code snippet you have posted, I cannot verify if my solution would work based off of that. However, it seems that you would rather like to bind updatestatuscero to the onclick event of the button. If so, simply replace delete_from_table with this function's name.
Just some more tips regarding general JavaScript:
Use braces wherever possible to make the code more intelligible. For example, you could have written your code like so:
contextBridge.exposeInMainWorld(
"electron", {
printmovieonpreload: (results) => {
ipcRenderer.on("send-movie", (event, results) => {
mylist.innerHTML = " "
results.forEach(elements => {
mylist.innerHTML += `<tr><td> ${elements.movie-name} </td>
<td> ${elements.movie-duration} min </td><td><button id="btn" value="${elements.id-movie}" "/*this get all the onclick events of the page*/${onclick = delete_from_table}" type="button" class="fas cli fa-trash-alt"></button></td>
</tr>`;
});
});
}
}
});
This way, the scope of the functions can be understood at first glance. Both you and any other person having to work with this code in the future will be very pleased if you keep it organised.
When using string interpolation, you can execute operations. This is why I stated that - is not valid in an index above. The reason for this is that any expression inside ${} in an interpolated string will be evaluated and its return value will be included in the string. Thus,
var testObject = {
"test-prop": "def"
};
var stringA = `abc ${testObject.test-prop} ghi`; // Throws: ReferenceError, prop is not defined
var stringB = `abc ${testObject["test-prop"]} ghi`; // yields "abc def ghi"
What the expression inside stringA tries to do is to actually subtract prop from testObject.test, neither of which are defined.
Also, with string interpolation, you have executed an assignment (${ onclick = delete_from_table}) which caused the new variable onclick to be defined, the method to be (correctly) written to the HTML, but not bound to the buttons' onclick event! Instead, you could have done onclick="${delete_from_table}" just like you did with the value property.
Don't ever insert HTML by using element.innerHTML! Take a look at MDN -- this method has some pretty unexpected implications. Better sanitise your HTML first, use element.insertAdjacentHTML() or simply create elements and add them to a parent element by using element.appendChild() as I showed you above. This way is also, in my opinion, but tastes differ, the cleanest way possible.

HTML not rendering in browser using jQuery append with for loop

Trying to dynamically update the choices in the trivia game using a for loop with jQuery append, but the browser is not rendering the choiceText variable.
for (let i = 0; i < allChoices[0].length; i++) {
let allChoicesFin = allChoices[0];
const choiceContainer = $('.choice-container');
let choiceText =
'<span class="px-2 text-start">' + allChoicesFin[i] + '</span>';
choiceContainer[i].append(`${choiceText}`);
}
picture of the trivia game where the HTML is not rendering
The issue is because you're accessing the jQuery object by index. This will return an Element object, not a jQuery object, so you're using the native append() method, not the jQuery one. As such the string is injected as text content, not a DOM string.
To fix this change choiceContainer[i] to choiceContainer.eq(i):
let allChoices = [['foo', 'bar']];
let allChoicesFin = ['lorem', 'ipsum'];
const $choiceContainer = $('.choice-container');
for (let i = 0; i < allChoices[0].length; i++) {
$choiceContainer.eq(i).append(`<span class="px-2 text-start">${allChoices[0][i]}</span>`);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="choice-container"></div>
<div class="choice-container"></div>
Note that I made a couple of tweaks to the logic to tidy it up a little, such as defining $choiceContainer outside of the loop, removing one unnecessary template literal, and adding one which was necessary.

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);

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.

output translations without document.write

I understand that document.write is evil and should not be used to output text on a website.
However, I did not find a better solution yet for my usage, where I output translated text via javascript on a page. Consider this:
My template code looks like this:
<h1><script>_e("title");</script></h1>
In a javascript file I have similar code like this to translate the strigs:
// Initialize the dictionaries and set current language.
window.lang = {};
window.lang.en = { "title": "Sample Page" };
window.lang.de = { "title": "Beispiel Seite" };
window.curLang = "en";
// Translation function that should output translated text.
function _e(text) {
var dictionary = window.lang[window.curLang];
var translation = dictionary[text];
// BELOW LINE IS EVIL.
// But what's the alternative without having to change the template code above?
document.write( translation );
}
Question:
Can I change the javascript function to not use document.write to work without changing the template code?
If not: What would be the best solution for translation here?
This is my first time using document.currentScript but i've tried this code and it should work correctly:
<!DOCTYPE html>
<html>
<head>
<title></title>
<script type="text/javascript" charset="utf-8">
// Initialize the dictionaries and set current language.
window.lang = {};
window.lang.en = { "title": "Sample Page" };
window.lang.de = { "title": "Beispiel Seite" };
window.curLang = "en";
// Translation function that should output translated text.
function _e(text) {
var dictionary = window.lang[window.curLang];
var translation = dictionary[text];
var container = document.currentScript;
container.parentNode.innerHTML = translation;
}
</script>
</head>
<body>
<h1><script>_e("title");</script></h1>
</body>
</html>
Another why is to use special tags or attribute to replace with something like jQuery, but this change your template. Something like
<span class='i18n-text'>title</span>
var dictionary = window.lang[window.curLang];
jQuery('.i18n-text').each(function(){
var text = $(this).text();
var translation = dictionary[text];
$(this).html(translation);
});
(i haven't tried the second solution but it should work :D)
One way of doing the translations without using document.write - and with the added consequence (benefit?) of having text when you turn off Javascript or don't have a translation - would be to annotate elements that require translation and perform the translation after the document is loaded by changing the innerHTML or textContent (if you want to escape entities).
Your translate functions could be something like this:
function translateAll() {
var dictionary = window.lang[window.curLang];
var elements = document.querySelectorAll("[data-i18n-id]");
for (var i = 0; i < elements.length; i++) {
var el = elements[i];
var translation = dictionary[el.dataset.i18nId]
if (translation) {
el.innerHTML = translation;
}
}
}
And you could execute it, for example, onload:
<body onload="translateAll();">
<h1 data-i18n-id="title">My Title</h1>
</body>
Using knockout.js, the way to go would be to store your translation keys in computed variables, then write a translation service function that looks the translations up in a hash table. The thing that would make this trigger would then be an observable which you would do an attribute binding on the HTML tag with, setting the lang attribute. Changing this observable would trigger the depending computed variables to automatically re-evaluate and update your document. If using knockout is an option here, let me know and I will setup an example.

Categories