call a javascript function based on a regex match - javascript

In the following string, i would like to replace [choice:a3d] with an appropriate drop down menu. I am not sure of how the options need to be formatted just after the colon and before the closing square brace.
string = "operation [number] [choice:a3d] [number]";
I am not really sure where the .replace function comes from but the code I am working with has jquery imported.
string.replace(/(?:\[choice\:)(\w+)(?:\])/g, choice_func);
where:
function choice_func(choice_lists, listname, default_opt)
{
console.log("choice_lists: "+choice_lists); // [choice:a3d]
console.log("listname: "+listname); // a3d
console.log("default_option: "+default_opt); // 67
var list = choice_lists[listname];
return '<span class="string ' + listname + ' autosocket"><select>' +
list.map(function(item)
{
if (item === default_opt){
return '<option selected>' + item + '</option>';
}else{
return '<option>' + item + '</option>';
}
}).join('') +'</select></span>';
}
needless to say the code fails with error "Uncaught TypeError: Cannot call method 'map' of undefined"
also where do parameters to the function come from?
Don't assume that any of this code is correct....

This looks to me like it would be simpler for you to just use whatever code you need to use to compute the replacement string and then just do a replace using the string instead of the regex function. Regex functions are best used when you need to examine the context of the match in order to decide what the replacement is, not when you are just doing a replacement to something that can be computed beforehand. It could be made to work that way - there's just no need for that level of complexity.
When using regex callbacks, the callback gets multiple parameters - the first of which is the match string and there are a number of other parameters which are documented here. Then, you must return a string from that function which is what you want to replace it with. You function is pretending that it has three parameters which it does not and thus it won't work.
I suggest that you compute the replacement string and then just do a normal text replacement on it with no regex callback function.
If you can be clearer about what the initial string is and what you want to replace in it, we could give you some sample code that would do it. As you've shown in your question, your string declaration is not even close to legal javascript and it's unclear to me exactly what you want to replace in that string.
The pseudo code would look like this:
var menuStr = "xxxxxxx";
var replaceStr = choice_func(lists, name, options);
menuStr = menuStr.replace(/regular expression/, replaceStr);

Related

Passing dynamic arguments to a function called by a dynamically added link

I am trying to dynamically insert a link into the DOM. The link hyperlinks to another Javascript function that takes in a single argument.
Depending on the variable type of the argument (integer or string), the function either generates an error or behaves as expected.
Edit: added a CodePen demo here
function appendLink(userInput){
var functionLink = document.createElement("a");
functionLink.innerHTML = "Call Function";
functionLink.href = "javascript:func("+ userInput + ")"; //calling function + concatenating dynamic input
document.body.append(functionLink);
}
function func(arg){
alert(arg);
}
If arg is a string (e.g.: userInput = 83we0 -- this is the exact argument in my code), I get Uncaught SyntaxError: Invalid or unexpected token
However, if arg is numerical (e.g.: userInput = 62121), then the program behaves as expected, alerting "62121" when the dynamically-appended link is pressed.
Considering
func("+ userInput + ")";
Numbers work because the produced syntax will be something like
func(123)
Non-numbers won't, because the produced syntax will be something like
func(83we0)
Strings require delimiters.
While you could fix this by conditionally adding delimiters and escaping them inside the argument, it would be far better to avoid inline handlers entirely, and use addEventListener instead, that way you don't have to worry about silly and tedious escaping issues:
functionLink.addEventListener('click', () => func(userInput));
Do that instead of assigning javascript: to the href.

What is the best way to access an element through a data-attribute whose value is an object (JSON)?

Say I have the following element:
<div class='selector' data-object='{"primary_key":123, "foreign_key":456}'></div>
If I run the following, I can see the object in the console.
console.log($('.selector').data('object'));
I can even access data like any other object.
console.log($('selector').data('object').primary_key); //returns 123
Is there a way to select this element based on data in this attribute? The following does not work.
$('.selector[data-object.foreign_key=456]');
I can loop over all instances of the selector
var foreign_key = 456;
$('.selector').each(function () {
if ($(this).data('object').foreign_key == foreign_key) {
// do something
}
});
but this seems inefficient. Is there a better way to do this? Is this loop actually slower than using a selector?
You can try the contains selector:
var key_var = 456;
$(".selector[data-object*='foreign_key:" + key_var + "']");
I think that you may gain a little speed here over the loop in your example because in your example jQuery is JSON parsing the value of the attribute. In this case it's most likely using the JS native string.indexOf(). The potential downside here would be that formatting will be very important. If you end up with an extra space character between the colon and the key value, the *= will break.
Another potential downside is that the above will also match the following:
<div class='selector' data-object="{primary_key:123, foreign_key:4562}"></div>
The key is clearly different but will still match the pattern. You can include the closing bracket } in the matching string:
$(".selector[data-object*='foreign_key:" + key_var + "}']");
But then again, formatting becomes a key issue. A hybrid approach could be taken:
var results = $(".selector[data-object*='" + foreign_key + "']").filter(function () {
return ($(this).data('object').foreign_key == foreign_key)
});
This will narrow the result to only elements that have the number sequence then make sure it is the exact value with the filter.
With a "contains" attribute selector.
$('selector[data-object*="foreign_key:456"]')

how to embed value with an apostrophe in querystring

I have the following javascript:
tr.append("<a href='add_widget.html?id=" + data[i].id + "&pg=" + data[i].page_number + "&dest=" + data[i].dest + "&name=" + data[i].name.replace("'","\\'") + "'</a><button class='btn btn-xs btn-primary'>Edit</button> </td>");
The code in question has to do with the name field.
If I have a name like "John Doe" when I click on the hyperlink created by the above javascript, the new page's querystring has the full name.
However, if I try to pass a name like "John's stuff", the above logic creates a query string variable that looks like this:
&name=John\
How can I change the above code so that the entire string "John's stuff" is passed to the add_widget.html page?
Thanks.
replace("'","%27")
try http://meyerweb.com/eric/tools/dencoder/ it's an online URL encoder/decoder.
When you're trying to "protect" characters, you have to keep in mind what you're protecting them from. In this case, there are two interpreters you have to worry about:
You're building HTML, so you have to worry about the HTML parser;
You're building a URL, so you have to worry about how the browser and the server will parse the URL.
To deal with the first problem, you can replace the quotes with the HTML entity equivalent ('). To deal with the second, you can use encodeURIComponent().
I think you'd want to do the encodeURIComponent() call first, to avoid having the HTML entity notation get messed up. The entity notation will be gone after the HTML parser is finished with the string anyway:
function qEncode(str) {
return encodeURIComponent(str).replace(/'/g, "'");
}
To use that:
tr.append("<a href='add_widget.html?id=" +
qEncode(data[i].id) + "&pg=" +
qEncode(data[i].page_number) + "&dest=" +
qEncode(data[i].dest) + "&name=" +
qEncode(data[i].name) +
"'</a><button class='btn btn-xs btn-primary'>Edit</button> </td>"
);
Note that you could also encode double-quote characters too.
A totally different way of working around this problem would be to build the DOM content with DOM APIs. By doing that, you'd completely avoid the HTML parser, and you'd just need encodeURIComponent().
You need to think, what will be interpreting my code, so what do I need to escape for?
Your code will be interpreted by the HTML Interpreter in the browser
Your code will be interpreted as a URI
This means you need to escape/encode them in reverse order. Luckily JavaScript provides a URI encoder as encodeURIComponent, but it doesn't provide a HTML one (probably as we have DOM Methods) but it isn't too hard to implement for important characters, e.g.
function html_encode(str) {
var re_chars = /[<>'"]/g;
function replacer($0) {
return '&#' + $0.charCodeAt(0) + ';'
}
return str.replace(re_chars, replacer);
}
// example follows
html_encode('<foo bar="baz">'); // "<foo bar="baz">"
So for you,
attrib_value = html_encode(/* ... + */ encodeURIComponent(data[i].name) /* + ... */ );
For completeness,
function html_decode(str) {
var re = /&(?:#\d{1,3}|amp|quot|lt|gt|nbsp);/g, // notice extra entities
d = document.createElement('div');
function replacer($0) {
d.innerHTML = $0;
return d.textContent;
}
return str.replace(re, replacer);
}
// and an example
html_decode('<foo bar="baz">'); // "<foo bar="baz">"
Using escape(data[i].name) instead of data[i].name.replace("'","\\'"), will solve your problem.

Is there an equivalent of elem.innerHTML that gets the code in the tag body?

If I have the following:
<p class="demo" id="first_p">
This is the first paragraph in the page and it says stuff in it.
</p>
I could use
document.getElementById("first_p").innerHTML
to get
This is the first paragraph in the page and it says stuff in it.
But is there something simple you can run which would return as a string
class="demo" id="first_p"
I know I can iterate through all of the element's attributes to get each one individually but is there a function which returns tagHTML or something like that?
The following code is something of a mouthful: I wrote it as a one-liner, but I've broken it out into several lines here. But this will get you a plain object where the keys are attribute names and the values are the values of the corresponding attributes:
Array.prototype.reduce.call(
document.getElementById('first_p').attributes,
function (attributes, currentAttribute) {
attributes[currentAttribute.name] = currentAttribute.value;
return attributes;
},
{}
);
Going through this, document.getElementById('first_p').attributes gets you a NamedNodeMap of the element's attributes. A NamedNodeMap is not an Array, but Array.prototype.reduce.call(...) calls Array.prototype.reduce on the NamedNodeMap as if it were an Array. We can do this because NamedNodeMap is written so that it can be accessed like an array.
But we can't stop here. That NamedNodeMap that I mentioned is an array of Attr objects, rather than an object of name-value pairs. We need to convert it, which is where the other arguments to Array.prototype.reduce come into play.
When it's not being called in a strange way, Array.prototype.reduce takes two arguments. The second argument (which is third for us because of the way we called it) is an object that we want to build up. In our case, that's a brand-new bare object: the {} that you see at the end.
The first argument to Array.prototype.reduce (which, again, is second for us) is another function. That function will get called once for each item in the loop, but it takes two arguments. The second argument is the current loop item, which is easy to understand, but the first argument is a little wild. The first time we call that function, its first argument is the object we want to build up (i.e. the last argument to Array.prototype.reduce. Each time after that, the first argument is whatever that function returned the last time it was called. Array.prototype.reduce returns whatever the last call to its inner function returned.
So we start with an empty object. Then for every Attr in the element's attributes, we add something to the object, and return it. When the last call finishes, the object is finished, so we return that. And this is how we make the attribute list.
If you wanted the exact code in the tag, like a String, then I'm afraid there is no standard function to get that exactly. But we can get a close approximation of that code, with a similar setup:
Array.prototype.map.call(
document.getElementById('first_p').attributes,
function (currentAttribute) {
return currentAttribute.name + '=' + JSON.stringify(currentAttribute.value);
}
).join(' ');
The basic principle is the same: we take that NamedNodeMap and call an Array function on it, but this time we're using map instead of reduce. You can think of map as a special case of reduce: it always builds up an Array, with one element for every element that was in the original. Because of that, you don't even need to mention the object you're building up: the callback function only has one argument, and we just return the thing to put into the new Array. Once we're done, we have an Array of 'name="value"' strings, and then we just join that with ' '.
It isn't a built-in property, but you can use the array-like object attributes to obtain what you're looking for.
Array.prototype.map.call(element.attributes, function (el) {
return el.name + '="' + el.value + '"';
}).join(' ')
This is assuming a browser that supports the map function. The Array.prototype.map.call part is because attributes is not really an array and does not have a join method, but because it's an array-like JavaScript's dynamism allows us to call map on it anyway.
Example from the current page with the footer div:
var element = document.getElementById('footer')
Array.prototype.map.call(element.attributes, function (el) {
return el.name + '="' + el.value + '"';
}).join(' ');
// "id="footer" class="categories""
You can try the following:-
var attributes = '';
for(var i=0; i<document.getElementById("first_p").attributes.length; i++){
var attr = document.getElementById("first_p").attributes[i];
attributes += attr.nodeName+"='"+attr.nodeValue+"' "
}
console.log(attributes);
You can use document.getElementById("first_p").attributes to get an array of all the attributes on that DOM element
If you wanted them all in one string just do: document.getElementById("first_p").attributes.join(' ') to get the desired output
Well, while nothing currently exists to do this directly (though the approaches using the Node's attributes is a more reliable approach, one option is to create this method yourself:
HTMLElement.prototype.tagHTML = function(){
// we create a clone to avoid doing anything to the original:
var clone = this.cloneNode(),
// creating a regex, using new RegExp, in order to create it
// dynamically, and inserting the node's tagName:
re = new RegExp('<' + this.tagName + '\\s+','i'),
// 'empty' variables for later:
closure, str;
// removing all the child-nodes of the clone (we only want the
// contents of the Node's opening HTML tag, so remove everything else):
while (clone.firstChild){
clone.removeChild(clone.firstChild);
}
// we get the outerHTML of the Node as a string,
// remove the opening '<' and the tagName and a following space,
// using the above regular expression:
str = clone.outerHTML.replace(re,'');
// naively determining whether the element is void
// (ends with '/>') or not (ends with '>'):
closure = str.indexOf('/>') > -1 ? '/>' : '>';
// we get the string of HTML from the beginning until the closing
// string we assumed just now, and then trim any leading/trailing
// white-space using trim(). And, of course, we return that string:
return str.substring(0,str.indexOf(closure)).trim();
};
console.log(document.getElementById('test').tagHTML());
console.log(document.getElementById('demo').tagHTML());
JS Fiddle demo.

refactor HTML-generating JavaScript

Unfortunately on my project, we generate a lot of the HTML code in JavaScript like this:
var html = new StringBuffer();
html.append("<td class=\"gr-my-deals\">").append(deal.description).append("</td>");
I have 2 specific complaints about this:
The use of escaped double quotes (\”) within the HTML string. These should be replaced by single quotes (‘) to improve readability.
The use of .append() instead of the JavaScript string concatentation operator “+”
Applying both of these suggestions, produces the following equivalent line of code, which I consider to be much more readable:
var html = "<td class=’gr-my-deals’><a href=’" + deal.url + "’ target=’_blank’>" + deal.description + "</a></td>";
I'm now looking for a way to automatically transform the first line of code into the second. All I've come up with so far is to run the following find and replace over all our Javascript code:
Find: ).append(
Replace: +
This will convert the line of code shown above to:
html.append("<td class=\"gr-my-deals\">" + deal.description + "</td>)";
This should safely remove all but the first 'append()' statement. Unfortunately, I can't think of any safe way to automatically convert the escaped double-quotes to single quotes. Bear in mind that I can't simply do a find/replace because in some cases you actually do need to use escaped double-quotes. Typically, this is when you're generating HTML that includes nested JS, and that JS contains string parameters, e.g.
function makeLink(stringParam) {
var sb = new StringBuffer();
sb.append("<a href=\"JavaScript:myFunc('" + stringParam + "');\">");
}
My questions (finally) are:
Is there a better way to safely replace the calls to 'append()' with '+'
Is there any way to safely replace the escaped double quotes with single quotes, regex?
Cheers,
Don
Consider switching to a JavaScript template processor. They're generally fairly light-weight, and can dramatically improve the clarity of your code... as well as the performance, if you have a lot of re-use and choose one that precompiles templates.
Here is a stringFormat function that helps eliminate concatenation and ugly replacment values.
function stringFormat( str ) {
for( i = 0; i < arguments.length; i++ ) {
var r = new RegExp( '\\{' + ( i ) + '\\}','gm' );
str = str.replace( r, arguments[ i + 1 ] );
}
return str;
}
Use it like this:
var htmlMask = "<td class=’gr-my-deals’><a href=’{0}’ target=’_blank’>{1}</a></td>";
var x = stringFormat( htmlMask, deal.Url, deal.description );
As Shog9 implies, there are several good JavaScript templating engines out there. Here's an example of how you would use mine, jQuery Simple Templates:
var tmpl, vals, html;
tmpl = '<td class="gr-my-deals">';
tmpl += '#{text}';
tmpl += '</td>';
vals = {
href : 'http://example.com/example',
text : 'Click me!'
};
html = $.tmpl(tmpl, vals);
There is a good reason why you should be using the StringBuffer() instead of string concatenation in JavaScript. The StringBuffer() and its append() method use Array and Array's join() to bring the string together. If you have a significant number of partial strings you want to join, this is known to be a faster method of doing it.
Templating? Templating sucks! Here's the way I would write your code:
TD({ "class" : "gr-my-deals" },
A({ href : deal.url,
target : "_blank"},
deal.description ))
I use a 20-line library called DOMination, which I will send to anyone who asks, to support such code.
The advantages are manifold but some of the most obvious are:
legible, intuitive code
easy to learn and to write
compact code (no close-tags, just close-parentheses)
well-understood by JavaScript-aware editors, minifiers, and so on
resolves some browser-specific issues (like the difference between rowSpan and rowspan on IE)
integrates well with CSS
(Your example, BTW, highlights the only disadvantage of DOMination: any HTML attributes that are also JavaScript reserved words, class in this case, have to be quoted, lest bad things happen.)

Categories

Resources