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

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

Related

why doesn't this return an l-value?

Maybe ReSharper or visual studio is wrong, but I don't think that this returns an r-value. I also don't think it actually sets the property in the $parent controller:
function getParentItem(path) {
var obj = $scope.$parent;
var param = null;
var items = path.split(".");
for (var i = 0; i < items.length; i++) {
var item = items[i];
var split = item.split("(");
if (split.length === 2) {
param = split[1].replace(/[\)\']/g, "");
}
obj = obj[split[0]];
}
if (param == null) {
var thisObj = obj;
return thisObj;
} else {
return { obj: obj, param: param };
}
}
If I do this:
getParentItem($scope.someProperty) = "yadda"
I get error marked by probably ReSharper and I think it doesn't actually set the new value
As Amy/Volkan said your code is not valid but I think I get what you want to do. There are lots of ifs but here it goes:
if your $scope.someProperty is string property that you want to reassign on result of the function getParentItem, and your function returns object that can have that param ($scope.someProperty), first you need to figure out which path you pass in but it looks like it's some string separated by dots.
// so then assign result of the function to some variable
// you need to pass somePath to function
let parentItem = getParentItem(somePath);
// then change that property
parentItem[$scope.someProperty] = "yadda";
or another possibility what you might need would be:
parentItem.param[$scope.someProperty] = "yadda";
then do whatever you want with parentItem like put it on $scope or whatever.
If you want better help please do some jsfiddle or something.
The problem is (and I slap my head on how stupid I was) that the leaf branches of this $scope object aren't objects themselves, and in some cases in our code they don't even exist yet. You get so used to $scope being an object you fail to realize that the final elements can't possibly be objects at least in Javascript.
So the solution was to pass the value that I wanted to set as a parameter:
function getParentItem(path, optionalValue)
On the final loop of the parent search, if optionalValue is passed, I can then set the value onto the object:
obj[--last parameter name--] = optionalValue;

Always call a function with +1 higher than last call?

Each time the input_function() is called the argument needs to be one integer higher than the last time it was called. It can't be modified.
In the below example the first iteration works, but on the second it will be called with the argument of 4 which should have been 5.
input_function(1)
input_function(2)
for i in 1..4
input_function(2+i)
# do something here with i
input_function(3+i)
# do something here with i
end
Question
How can I make sure that the argument for input_function() is always one higher than the last time it was called?
The solution can be in any langauge.
You can use a closure to keep the state of the function
var input_function = (function() {
var last_argument = null;
return function(input) {
if (last_argument !== null && input !== last_argument + 1) {
throw new Error('invalid input argument: ' + input)
}
last_argument = input;
console.log('input OK', input);
// do something here?
}
})();
input_function(4);
input_function(5);
input_function(3);
Well, usually I would use static variables for this kind of problem. Unfortunately, Javascript doesn't support it. If you want to do something similar in Javascript, you can recurr to this alternative:
function input_function(n) {
alert(n + count.num);
count.num++;
}
// initialize count number
count.num = 0;
input_function(10); // alert 10
input_function(4); // alert 5
input_function(1); // alert 3
Source: DeluxeBlogTips
Javascript may not support static variables per se but functions in JS are first class objects and therefor they can have properties that can be used like statics. Reworking the above example:
function input_function(n) {
alert(n + input_function.count++);
}
input_function.count = 0;
input_function(10); // alert 10
input_function(4); // alert 5
input_function(1); // alert 3

Can I access a variable's name as a string after passing it as an argument to another function?

Perhaps a slightly dim question but I'm trying to design something where I'm using javascript / jquery to change the layout of a website and I'd like to see the values of both a variable name and it's current value in another div.
I was just doing $test.append('example string' + exampleVar) a lot so I thought I would make a function called test().
So far I have:
function test (test) {
$test = $('.test');
$test.append(test+"<br>");
}
and then would pass it a variable name as an argument but I can't find any way of making it display the name as a string. I know about making it as an object to access the key and value but that doesn't really seem to work here?
Bit of a long-winded way to do it, but here's an example using an object:
function tester(options) {
var keys = Object.keys(options);
console.log(keys[0] + ': ' + options[keys[0]]); // test: My value
}
tester({ test: 'My value' });
DEMO
You could use a feature of javascript where obj["prop"] is the same as obj.prop
So instead of passing the variable as a variable and hoping to get its name, you use the name as a string to get the variable's value.
If you aren't using namespaces/variables and want to a global/root variable, pass window, eg:
function test(obj, val) {
console.log(val + ": " + obj[val]);
}
var val1 = 1;
var val2 = 2;
test(window, "val1");
test(window, "val2");
(obviously you don't get the name of 'obj' - but maybe it's a start)
if you only have root/global variables (as in the example provided in the question) then you could remove obj:
function test(val) {
console.log(val + ": " + window[val]);
}
var val1 = 1;
var val2 = 2;
test("val1");
test("val2");
It seems what you want to do something like this:
var argumentName = /([^\s,]+)/g;
// fu is a function
// fu.toString look like: function myFunction (param[, param]*) { code }
function getParamNames(fu) {
var f = fu.toString();
return f.slice(f.indexOf('(')+1,f.indexOf(')')).match(argumentName);
}
Then you might want to create an helper which will take every functions:
// I choosed the console as an output, but you can set anything
function displayParameters(fu) {
var _params = getParamNames(fu);
if(_params!==null) {
for(var i=0;i<_params.length; i++) {
console.log(_params[i]);
}
} else { console.log('no parameters'); }
}
And, you will need to call: displayParameters(test);
In a function you will be using a parameter. So in that case the "variable name" will always be the name of the parameter. In this case getting the string value will always be test. I would assume this is not what you want. You were correct that the best way to do this is to use an object and iterate over the key, values. You would do this like:
var test = {
"test" : "value"
};
function test (test) {
var k, $test = $('.test');
for(k in test){
$test.append(k + "<br>");
}
}
Also, I do not think there is a way to get the variable string name. So the above would be the way to get the name of a variable.

synchronicity problems with Internet Explorer

I'm developing a javascript widget using the UWA widget format. Unfortunately this makes it impossible to jsFiddle my code but I've commented it in detail so that, hopefully, you can follow its fairly straightforward sequence.
HighestClass = {};
HighestClass.array = [];
HighestClass.url = "http://our.url.local/frog/pointsByWeek.php?cmd=highestClass&students=";
HighestClass.init = function(groupPrefix) {
var count = 0;
/* Using the group prefix, i.e. "CLS 9", from the drop-down box,
get a list of all of the classes in that year group */
/* First time round, count the number of groups that match this
syntax because there are no parameters available to filter
this API */
Frog.API.get('groups.getAll',{
'onSuccess': function(data){
for (var i = 0; i < data.length; i++) {
if (data[i].name.indexOf(groupPrefix) != -1)
count++;
}
});
/* Now that these classes have been counted, run through the API
call again to push each class ID through to another function */
var run_through = 0;
Frog.API.get('groups.getAll',{
'onSuccess': function(data){
for (var i = 0; i < data.length; i++) {
if (data[i].name.indexOf(groupPrefix) != -1) {
var end = false;
run_through++;
/* When it gets to the last class group, i.e. the run_through
variable becomes equal to count, let the getClassPoints
function know */
if( run_through == count )
end = true;
HighestClass.getClassPoints( data[i].name, data[i].id, end );
}
}
}
});
}
HighestClass.getClassPoints = function(name, id, end) {
var list = '';
/* Using the ID of the class group, create a comma-separated list
of students for use in our MySQL query */
Frog.API.get("users.search", {
"params": {
"group": id
},
"onSuccess": function (data){
for (var i = 0; i < data.length; i++)
list += data[i].id + ",";
}
});
/* If the list exists... */
if( typeof list === "string" && list.length > 0 ) {
list = list.slice(0,-1);
/* Run an AJAX call to our PHP script which simply returns an integer
value of the SUM of reward points earned by that list of students */
UWA.Data.getJson(HighestClass.url + list, function(res){
if (res === false || res === "") res = 0;
/* Push this data into an array of objects alongside the class name */
var obj = { "name": name, "points": res };
HighestClass.array.push(obj);
});
}
/* As this function is being called asynchronously multiple times we need to
determine when the last call is run so that we can deal with our array
of data. We do this thanks to the count/run_through variables in earlier
function which will trigger end=true in this function */
if( end === true )
HighestClass.display();
}
HighestClass.display = function() {
/* Once we've put our array of objects together, we need to sort it so that
the class with the highest number of points are at array entry 0 */
function compare(a,b) {
if (a.points < b.points)
return 1;
if (a.points > b.points)
return -1;
return 0;
}
/* IF I PUT AN ALERT HERE, INTERNET EXPLORER WORKS, LOL? */
HighestClass.array.sort(compare);
/* We can then display the data of array entry 0 which should be our highest
point-scoring class */
$('#display').html( '<h1>' + HighestClass.array[0].name + '</h1><h3>' + HighestClass.array[0].points + '</h3>' );
}
/* equivalent of document ready */
widget.onLoad = function(){
/* Choose the year group from the drop down box */
$("select").change(function(){
var val = $('select option:selected').val();
$("#display").html('<h1><img width="60" height="60" src="http://logd.tw.rpi.edu/files/loading.gif" />Loading...</h1>');
HighestClass.init(val);
});
}
In essence the script does the following:
Retrieve a list of students for each class group
Run an AJAX call to our PHP script/MySQL database to return the SUM of points for those students
Add the name and points info to an array of objects
Sort the array of objects so that the highest point-scoring class is our first array entry
Display the name of the class and their points from array entry 0
The problem is, the only way I can think about doing it (because of limitations of the APIs) is to run asynchronous API calls and chain AJAX calls off these. I then use a counting variable to determine when the last asynchronous call is made.
Now, importantly, this script works perfectly well in FireFox. However, in Internet Explorer - which is where I need it to work - the script displays our "loading" DIV/image and goes no further.
The strange thing is, if I put an alert in the code (where I've commented it in capital letters), Internet Explorer works correctly.
This must be an issue with synchronicity and timing but I have no experience or knowledge of it.
Can anyone suggest a solution? Hacky is fine, if necessary.
Cheers,
First thing is: /!\ When use the callback pattern, your "flow" need to re-begin in the callback function.
I can see that you have problems with the Asynchronous and callback approach. When you $.getJSON but also every time you make a Frog.API call, example:
Frog.API.get("users.search", {
"params": {
"group": id
},
"onSuccess": function (data){
for (var i = 0; i < data.length; i++)
list += data[i].id + ",";
}
});
Here you retrieve data, and put them in a list with the onSuccess callback function. My guess is that this call is also asynchronous. If this call takes too long:
if( typeof list === "string" && list.length > 0 ) {
won't pass. So nothing will happening and your display will try to get values of an undefined object => error, the JavaScript stops, no update of your view.
You need to getJSON after your list is retrieve, in the onSuccess callback. And this will help because you make the same mistake after:
In what follow you ask to have a display, but you absolutely don't know if your calls are finished. The fact it asked for the last call does not mean any of the calls are finished.
if( end === true )
HighestClass.display();
So you just need to add that after:
HighestClass.array.push(obj);
which is in your $.getJSON call.
Ajax call are usually Asynchronous and your problem is that you try to update the display synchronously with the current flow, without waiting for your server to answer.
/!\ When use the callback pattern, your "flow" need to re-begin in the callback function. Using that, you will always be sure that the code you are running has all the data it needs to achieve it's duty.
PS: Here is all the code modified. I also modified your function init. You do not need to call your API again to redo the same thing. just loop twice on the data or put the only relevant data aside in an array then loop on it.
HighestClass = {};
HighestClass.array = [];
HighestClass.url = "http://our.url.local/frog/pointsByWeek.php?cmd=highestClass&students=";
HighestClass.init = function(groupPrefix) {
/* Using the group prefix, i.e. "CLS 9", from the drop-down box,
get a list of all of the classes in that year group */
Frog.API.get('groups.getAll',{
'onSuccess': function(data){
var i = 0,
l = 0,
count = 0,
group = [];
/* First time round, count the number of groups that match this
syntax because there are no parameters available to filter
this API */
for (i = 0, l = data.length; i < l; i++) {
if (data[i].name.indexOf(groupPrefix) != -1)
group.push(data[i]);
}
/* Now that these classes have been counted, run through the API
call again to push each class ID through to another function */
l = group.length;
count = l - 1;
for (i = 0; i < l; i++) {
// i == count will be true when it is the last one
HighestClass.getClassPoints( group[i].name, group[i].id, i == count);
}
});
}
HighestClass.getClassPoints = function(name, id, end) {
/* Using the ID of the class group, create a comma-separated list
of students for use in our MySQL query */
Frog.API.get("users.search", {
"params": {
"group": id
},
"onSuccess": function (data){
var list = '';
// We have data and build our string
for (var i = 0; i < data.length; i++)
list += data[i].id + ",";
/* If the list exists... */
if( typeof list === "string" && list.length > 0 ) {
list = list.slice(0,-1);
/* Run an AJAX call to our PHP script which simply returns an integer
value of the SUM of reward points earned by that list of students */
UWA.Data.getJson(HighestClass.url + list, function(res){
if (res === false || res === "") res = 0;
/* Push this data into an array of objects alongside the class name */
var obj = { "name": name, "points": res };
HighestClass.array.push(obj);
/* As this function is being called asynchronously multiple times we need to
determine when the last call is run so that we can deal with our array
of data. We do this thanks to the count/run_through variables in earlier
function which will trigger end=true in this function */
if( end === true )
HighestClass.display();
});
}
}
});
}
HighestClass.display = function() {
/* Once we've put our array of objects together, we need to sort it so that
the class with the highest number of points are at array entry 0 */
function compare(a,b) {
if (a.points < b.points)
return 1;
if (a.points > b.points)
return -1;
return 0;
}
/* IF I PUT AN ALERT HERE, INTERNET EXPLORER WORKS, LOL? */
HighestClass.array.sort(compare);
/* We can then display the data of array entry 0 which should be our highest
point-scoring class */
if (HighestClass.array.length > 0)
$('#display').html( '<h1>' + HighestClass.array[0].name + '</h1><h3>' + HighestClass.array[0].points + '</h3>' );
else
$('#display').html( '<h1>No data available</h1>' );
}
/* equivalent of document ready */
widget.onLoad = function(){
/* Choose the year group from the drop down box */
$("select").change(function(){
var val = $('select option:selected').val();
$("#display").html('<h1><img width="60" height="60" src="http://logd.tw.rpi.edu/files/loading.gif" />Loading...</h1>');
try {
HighestClass.init(val);
} catch (e) {
$("#display").html('<h1>Sorry, an error occured while retrieving data</h1>');
}
});
}
The fact that an alert "fixes" the problem does indicate that it's something to do with a timing issue. It looks like one of your functions isn't returning in time and not populating the array variable correctly.
Try making the count and end variables global and seeing if that helps. I think it's something to do with scope.
It's most likely because your Ajax call is async here:
UWA.Data.getJson(HighestClass.url + list, function(res){
if (res === false || res === "") res = 0;
/* Push this data into an array of objects alongside the class name */
var obj = { "name": name, "points": res };
HighestClass.array.push(obj);
});
and HighestClass.array is empty at when HighestClass.display(); is called unless you wait for your ajax call to complete. You can make your ajax call synchronous or put this HighestClass.display(); in the Ajax callback.

Why does this function work for literals but not for more complex expressions?

I am having some insidious JavaScript problem that I need help with. I am generating HTML from a JSON structure. The idea is that I should be able to pass a list like:
['b',{'class':'${class_name}'}, ['i', {}, 'Some text goes here']]
...and get (if class_name = 'foo')...
<b class='foo'><i>Some text goes here.</i></b>
I use the following functions:
function replaceVariableSequences(str, vars) {
/* #TODO Compiling two regexes is probably suboptimal. */
var patIdent = /(\$\{\w+\})/; // For identification.
var patExtr = /\$\{(\w+)\}/; // For extraction.
var pieces = str.split(patIdent);
for(var i = 0; i < pieces.length; i++) {
if (matches = pieces[i].match(patExtr)) {
pieces[i] = vars[matches[1]];
}
}
return pieces.join('');
}
function renderLogicalElement(vars, doc) {
if (typeof(doc[0]) == 'string') {
/* Arg represents an element. */
/* First, perform variable substitution on the attribute values. */
if (doc[1] != {}) {
for(var i in doc[1]) {
doc[1][i] = replaceVariableSequences(doc[1][i], vars);
}
}
/* Create element and store in a placeholder variable so you can
append text or nodes later. */
var elementToReturn = createDOM(doc[0], doc[1]);
} else if (isArrayLike(doc[0])) {
/* Arg is a list of elements. */
return map(partial(renderLogicalElement, vars), doc);
}
if (typeof(doc[2]) == 'string') {
/* Arg is literal text used as innerHTML. */
elementToReturn.innerHTML = doc[2];
} else if (isArrayLike(doc[2])) {
/* Arg either (a) represents an element
or (b) represents a list of elements. */
appendChildNodes(elementToReturn, renderLogicalElement(vars, doc[2]));
}
return elementToReturn;
}
This works beautifully sometimes, but not others. Example from the calling code:
/* Correct; Works as expected. */
var siblings = findChildElements($('kv_body'), ['tr']);
var new_id = 4;
appendChildNodes($('kv_body'),
renderLogicalElement({'id': new_id},
templates['kveKeyValue']));
/* Incorrect; Substitutes "0" for the expression instead of the value of
`siblings.length` . */
var siblings = findChildElements($('kv_body'), ['tr']);
var new_id = siblings.length; // Notice change here!
appendChildNodes($('kv_body'),
renderLogicalElement({'id': new_id},
templates['kveKeyValue']));
When I trap out the first argument of renderLogicalElement() using alert(), I see a zero. Why is this?? I feel like it's some JavaScript type thing, possibly having to do with object literals, that I'm not aware of.
Edit: I have this code hooked up to the click event for a button on my page. Each click adds a new row to the <tbody> element whose ID is kv_body. The first time this function is called, siblings is indeed zero. However, once we add a <tr> to the mix, siblings.length evaluates to the proper count, increasing each time we add a <tr>. Sorry for not being clearer!! :)
Thanks in advance for any advice you can give.
If new_id is 0, doesn't it mean that siblings.length is 0? Maybe there is really no sibling.
Perhaps siblings.length is actually 0? Try debugging further (e.g. with Firebug)
OK, I fixed it. As it turns out, I was modifying my source JSON object with the first function call (because in JS you are basically just passing pointers around). I needed to write a copy function that would make a new copy of the relevant data.
http://my.opera.com/GreyWyvern/blog/show.dml/1725165
I ended up removing this as a prototype function and just making a regular old function.

Categories

Resources