Passing javascript function in JSON - javascript

I am trying to implement jQCloud word cloud with click event handler. It requires me to pass a javascript function in JSON.
In C#, I have made the dynamic JSON text
foreach (var r in result)
{
sbChart.Append("{\"text\": \"" + r.Key + "\", \"weight\": " + r.Count().ToString() + ", ");
sbChart.Append("\"handlers\": { \"click\": \"function() { alert('You clicked " + r.Key + "');}\"}}, ");
}
if (sbChart.Length != 0)
{
returnString = "[" + sbChart.ToString().Substring(0, sbChart.Length - 2) + "]";
}
I return this through web method to javascript where my code is
var words = JSON.parse(strJSON);
$('#div').jQCloud(words);
The JSON generated is
[
{"text": "the", "weight": 111, "handlers": { "click": "function() { alert('You clicked the');}"}},
{"text": "in", "weight": 66, "handlers": { "click": "function() { alert('You clicked in');}"}}
]
However, since my function is a string, it does not gets execute as a object. And if I remove the double quotes before and after the function statement, it gives me Invalid Character error during parse.
Please can anyone help me as to how can I make this alert work?

If you absolutely have to pass a function to your page that way (see the "but" below), you could use eval for it, since you're the one providing the text you'll be evaling it doesn't have the security issue (and the speed issue is a non-issue and has been for years). I'm not sure what you want to do with the function, but for instance:
$(".foo").on("click", eval(words[0].handlers.click));
...would eval the function and assign the result as a click handler on the elements matching the .foo selector.
But, there's almost certainly a better way to structure things. Instead of passing back functions in the JSON, you might have those functions in your JavaScript already in some kind of map:
var clickHandlers = {
"the": function() { alert("You clicked the"); },
"in": function() { alert("You clicked the"); }
};
...and then just given the key ("the", "in") in your JSON.
Or, given that they're the same thing other than what was clicked, figure out what was clicked ("the" or "in") from the element that was clicked.
Or any of a dozen other things, but what they have in common is that the functions are defined in your JavaScript code, not sent back via JSON, and then you link up those functions via information in the JSON rather than actual functions in the JSON.

Functions are not meant to be passed as JSON, and it's a pretty big security issue to execute arbitrary logic passed by a data call. That being said, use eval to solve this.
Something like
var action = eval(response[0].handlers.click); // evaluates the string as javascript, and returns the value, in this case a function.
action();

Thanks a lot all for your suggestions above. But I ended up using the answer from how to provide a click handler in JQCloud
I modified it a bit as per my requirement and it worked
var tag_list = new Array();
var obj = GetCustomJsonObj(strJSON);
for (var i = 0; i < obj.length; i++) {
tag_list.push({
text: obj[i].text,
weight: obj[i].weight,
//link: obj[i].link,
handlers: {
click: function () {
var zz = obj[i];
return function () {
alert("you have clicked " + zz.text);
}
}()
}
});
}

Related

JavaScript "if" statement wont run

(Using Javascript)
If I enter text in the textbox or not, the alert will not come up.
I know this is a simple fix but I can not figure it out!
(This is for learning purposes.)
Workout Log Test
<script type="text/javascript">
function myResults() {
myExercise();
myWeight();
mySets();
myReps();
myFunction();
}
function myExercise() {
var txtExercise = document.getElementById("txtExercise");
var txtOutput = document.getElementById("txtOutput1");
var name = txtExercise.value;
txtOutput1.value = "You destroyed, " + name + "!"
if (txtExercise.length === 0) {
alert ('Do you even lift?');
return;
First off, you're checking the "length" property of the element rather than the value of the input.
Second of all, you're checking against an integer value. If you were to simply read the value of the element, you're going to get text.
I'm guessing, what you want is something like:
var exercise = parseInt(txtExercise.value, 10);
if(exercise === 0) {
alert('Do you even lift?');
return;
}
But that's assuming txtExercise is an input element. Without seeing your markup, it's hard to be sure that any given answer will work.
Here you go, all fixed. You need an event handler, and this is a better if/else use case.
http://codepen.io/davidwickman/pen/vOKjqV
// Check the .value.length instead of just the .length
if (txtExercise.value.length === 0) {
alert('Bro, do you even lift?');
} else {
txtOutput1.value = "You destroyed, " + name + "!";
}
Assuming you have closed the function and if statement properly, you are missing a semi colon just before the if statement.
txtOutput1.value = "You destroyed, " + name + "!"; <---- here

How to send parameters to the JavaScript method that aren't constants?

As shown in the image below, I can provide my little method with a lot of stuff. The thing is that I want to send in e.g. first name of the contact I'm currently on. The field doesn't seem to be able to read the contents of the boxes on the same form that the calling control is on.
The approach now is to execute the method without parameters and let it fetch it's own data as it pleases. However, I'd like it better if I could provide it with some junk. How can I send in dynamical parameters into it?
Should I go for dependencies? I've always thought of them as "stuff the method will see if you query for them" and not "parameters that will be sent in"...
You can pass there any valid javascript statement - call to function etc.. For example Xrm.Page.getAttribute('name').getValue() will pass current record name value as argument.
CRM takes whatever you type in the parameters field and puts as parameter to function call.
(From my comment above) As an alternate/architectural suggestion, maybe consider creating a Javascript resource for each form (e.g. account.js, contact.js, etc.) and each of those files would define all the handlers for the respective form.
For example, your account.js file might look like this:
function form_onLoad() {
// Do stuff on load.
}
function form_onSave() {
// Do stuff on save.
}
function field_onChange() {
executeSigning(param1, param2);
}
Now in the CRM handlers, you bind each event to its respective method. I have found this works well because it makes it easier to view/debug Javascript as everything is in one place and you don't have to view each handler in CRM.
You can send objects to JavaScript functions the same way as constants. To reference objects that are not in a form, you can give them and ID.
Element:
<div id="myelement">Hello</div>
Code:
// function
function jsfunc(variabelname)
{
// dosomething with variabelname
}
// call function with reference to object
jsfunc( document.getElementById("myelement") );
You can also have a function without predefined parameters and access whatever is sent to the function via arguments.
Example:
function jsfunc()
{
var output = "";
var args = arguments;
for(var i = 0, len = args.length; i < len; i++) // loop through all arguments
{
var arg = args[i]; // reference the argument directly
output += i + ": "; // add index number
if (arg == null)
{
output += "null"; // arg is null
} else {
output += typeof(arg) + ", "; // adds type of argument
if (typeof(arg) == "string" || typeof(arg) == "number")
{
// argument is a string og number, which can be easily output
output += arg;
} else if (typeof(arg) == "object") {
// it is an object
if (arg.tagName)
{
// the object has a tagname which means it's an HTML element
output += arg.tagName;
} else {
// object but not HTML element, our array
output += "array, " + arg.firstname;
}
}
}
output += "\n"; // add new line
}
alert(output);
}
jsfunc(
"a" /* type: string */
, 5 /* type: number */
, document.getElementById("txtFirstName") /* type object, has tagName INPUT */
, document.getElementById("ElementThatDoesNotExists") /* type NULL */
, { "firstname" : "john", "lastname" : "smith" } /* type object, no tagname */
);
Output:
0: string, a
1: number, 5
2: object, INPUT
3: null
4: object, array, john

how do javascript objects constructed with both dot and literal notation behave when called?

While running the code below, without any function calls, I would immediately get this output
["1122","3rd St","Seattle","WA","92838"]
The closest thread that addressed this code was Need Explanation: Organization with Objects in a Contact List (Javascript, Codecademy) but it didn't quite address my concern.
I'm sure that the way I had added key,value pairs to the objects is somehow yielding this output, but I can't seem to explain why, especially when running the code, there is no function call included.
When actually trying to call search (e.g. search("steve")), it would fail but it would work on search("bill"). I thought it might be related to the javascript console but I checked using Chrome's console with the same results. Any help would be much appreciated.
var friends={};
friends.bill = {};
friends.steve={};
friends.bill["firstName"]="Bill";
friends.bill["lastName"]="Gates";
friends.bill.number="333.222.3937";
friends.steve["firstName"]="Steve";
friends.steve.lastName="Ballmer";
friends.steve["number"]="829.383.3939";
friends.bill["number"]="232.8392.2382"
friends.bill.address=['5353','Cook Ave','Bellevue','CA','94838']
friends.steve.address=['1122','3rd St','Seattle','WA','92838']
var search=function(name)
{
for(var i in friends){
if (name==i["firstName"])
{
console.log(friends[i])
return friends[i]
}
else{
return "no match"
}
}
}
try changing:
for(var i in friends){
if (name==i["firstName"])
...
to
for(var i in friends){
if (name == friends[i]["firstName"])
...
You meant something like:
for(var i in friends){
if( name == i ) { //i would be either "bill" or "steve" and check against name variable
console.log("Data for:" + i + " firstname is:" + friends[i]["firstName"]);
}
}
You are seeing this output:
["1122","3rd St","Seattle","WA","92838"]
Because it is what is currently stored in $_ (Most browser based JS interpreters will spit out what is in $_ on the prompt).
$_ is the value of the last evaluated expression, which in your case is:
friends.steve.address=['1122','3rd St','Seattle','WA','92838']
The "var search ..." is actually a declaration and will therefore not be stored in $_.
Also, your search function should probably look like this:
var search = function(name) {
for(var i in friends) {
if (name.toLowerCase() == friends[i]["firstName"].toLowerCase()) {
console.log(friends[i])
return friends[i]
}
}
return "no match";
};
#cherniv might have beat me to the punch on moving the return out.

JQuery - Using Selector :contains - Weird Results

Story so far.....
I want to learn JQuery, and im also building an MVC ASP.NET Apps which requires a "search as you type" search function - perfect to learn JQuery as well! So far (with the help of stackoverflowers ) I have managed to get the AJAX/JSON bit. Now I want to evaluate each key press and validate it against the JSON Array which was created as an unordered list. What im trying to achieve is to only show the account numbers in the list that contain what is inputted. So, my reasoning was to check against keydown event and validate it. For the time being im just changing the color of the account numbers to red of hiding them, just to prove my logic works.
My JQuery Code so far....
http://jsfiddle.net/garfbradaz/JYdTU/28/
...and for convenience....
$(document).ready(function() {
var $input = $("#livesearchinput"),
filled = false;
$.ajaxSetup({
cache: false
});
$input.keydown(function(key) {
if (!filled) {
filled = true;
$.getJSON("/gh/get/response.json//garfbradaz/MvcLiveSearch/tree/master/JSFiddleAjaxReponses/", function(JSONData) {
var $ul =
$('<ul>').attr({
id: "live-list"
}).appendTo('div#livesearchesults');
$.each(JSONData, function(i, item) {
$.each(item, function(j, val) {
$('<li>').attr({
id: "live-" + val
}).append(val).appendTo($ul);
});
});
});
}
var n = $("li:contains('" + this.value + "')").length;
if (n === 0) {
$("li").removeClass("color-change");
console.log("1. value of n equals " + n + " : " + this.value);
}
else {
$("li:contains('" + this.value + "')").addClass("color-change");
console.log("2. value of n equals " + n + " : " + this.value);
}
});
});​
My Issue.....
My issue is that when i evaluate the key press using the following this.value is empty on the first keydown event and then out of step throughout
var n = $("li:contains('" + this.value + "')").length
Example:
If i input 100004, let me show you my console.log results from Chromefor inputting that result:
The results seem to always be one step behind. Is the keydown event the best one to use or am i missing something.
As always - thanks guys and happy coding.
because this.value on the keydown event does not contain the keystoke which triggered the event. Use keyup instead.
You can access the keystoke via the event object which is available in the function... but the value of the input will not contain it until the key is released.
http://jsfiddle.net/rlemon/TGBUe/1/ in this example you can open your console... and type a character in the input... you will notice the keydown this.value is blank for the first char whereas the keyup contains it.
Maybe the check fires before the AJAX part has completed.
Mind that AJAX is asynchronous and the code continues its flow regardless of the AJAX request, that is completed asynchronously.
Try to move the logic inside function(JSONData).

How to optimize jquery grep method on 30k records array

Is it possible to optimize this code? I have very low performance on this keyup event.
$('#opis').keyup(function () {
if ($('#opis').val() != "") {
var search = $.grep(
svgs, function (value) {
reg = new RegExp('^' + $('#opis').val(), 'i');
return value.match(reg) == null;
}, true);
$('#file_list').html("");
var tohtml = "";
$cnt = 0;
for (var i = 0; i < search.length; i++) {
if ($cnt <= 30) {
tohtml += "<li class='file_item'><a href='' class='preview'>" + search[i] + "</a> <a href='" + search[i] + "' class='print_file'><img src='img/add16px.png' alt='dodaj'/></li></a>";
$cnt++;
} else {
break;
}
}
$('#file_list').html(tohtml);
$(".preview").click(function () {
$('#file_preview').html('<embed src="opisy/' + $(this).html() + '" type="image/svg+xml" pluginspage="http://www.adobe.com/svg/viewer/install/" /> ');
$(".preview").parent().removeClass("selected");
$(this).parent().addClass("selected");
return false;
});
$(".print_file").click(function () {
if (jQuery.inArray($(this).attr('href'), prints) == -1) {
$('#print_list').append('<li>' + $(this).attr('href') + '</li>');
prints.push($(this).attr('href'));
} else {
alert("Plik znajduje się już na liście do wydruku!");
}
return false;
});
} else {
$('#file_list').html(" ");
}
});
var opis = $('#opis')[0]; // this line can go outside of keyup
var search = [];
var re = new RegExp('^' + opis.value, 'i');
for (var i = 0, len = svgs.length; i < len; i++) {
if (re.test(svgs[i])) {
search.push(svgs[i]);
}
}
It's up to 100x faster in Google Chrome, 60x in IE 6.
first thing you have to learn:
$('#opis').keyup(function() {
$this = $(this);
if($this.val()!=""){
// so *$this* instead of *$('#opis')*
// because you are reperforming a *getElementById("opis")* and you've already called it when you used the keyup method.
// and use $this instead of $(this) | pretty much the same problem
so about the grep function, maybe if you cache the results it would help in further searchs I guess, but I don't know if can help you with that
Well the thing with javascript is that it executes under the users environment and not the servers environment so optimization always varies, with large large arrays that need extensive work done on them to I would prefer to handle this server side.
Have you thought about serializing the data and passing them over to your server side, which would handle all the data calculations / modifications and return the prepared result back as the response.
You may also want to take alook at SE:Code Review for more optimization advise.
Some optimization, tips:
if($('#opis').val()!=""){ should be using '!=='.
return value.match(reg)==null; should be ===.
for(var i=0;i<search.length;i++){
reg = new RegExp(...); should be var reg ... as its not defined outside the function as a global.
Move all your variable declarations to the top of the function such as
var i,cnt,search,tohtml etc
i would advise you to start using Google Chrome, it has a built in system for memeory tracking on perticular tabs, you can go to the url about:memory in chrome, which would produce a result like so:
Image taken from: http://malektips.com/google-chrome-memory-usage.html
Each time you perform the grep, you are calling the 'matching' function once per array entry.
The matching function creates a RegExp object and then uses it to perform the match.
There are two ways you could improve this:
Create the RegExp once, outside of the function, and then use a closure to capture it inside the function, so that you don't have to keep recreating the object over and over.
It looks like all you're trying to do is to perform a case-insensitive tests to see whether the sought string is the start of a member of your array. It may be faster to do this more explicitly, using .toUpper and substring. However, that's a guess and you should test to find out.

Categories

Resources