How do I map calls to a Grails controller from a Javascript method? I see a method using PHP, but not with grails:
function getSelected(checkList)
{
var idList = new Array();
var loopCounter = 0;
//find all the checked checkboxes
jQuery("input[name=" + checkList + "]:checked").each
(
function()
{
//fill the array with the values
idList[loopCounter] = jQuery(this).val();
loopCounter += 1;
}
);
//call here
}
Edit:
${remoteFunction(controller:"person", action:"runThroughAll", params:"[ids:idList]")}
So, I feel like there are sort of two things you're asking here. I'm going to tackle them both. First, how do you get the URL right for a call to a grails controller from JavaScript? In my GSP page (I do it in my main layout but whatever), I like to do this little trick:
<script>
myapp.url.root = "<g:resource dir='' file='' />" + "/";
</script>
That will give you the base root of your app wherever it's deployed. Then, you can build your URLs in JavaScript:
myurl = myapp.url.root + "path/to/controller"
Then make a jQuery ajax call using that url.
Then make sure that your controller is set up to respond to whatever URL pattern you've just expressed.
The second question appears to be, "how can I send back an HTML fragment"?
Inside the controller itself, take the parameters from the request, use it to figure out whatever you need, then render the gsp, passing in the model you've created. It will look something like this:
def show() {
def data = [hypothesis : metadataService.getHypothesis(params.id) as JSON]
render(view:"create", model:data)
}
Then in jQuery, your success handler will get as an argument the returned response, which you can then inspect/manipulate/add to the dom.
Hopefully all that made sense. If I glossed over something or didn't answer the question you were asking, let me know.
EDIT: For future reference, here is the javascript method rewritten which we arrived at in chat:
function getSelected(checkList){
var idList = $("input[name='" + checkList + "']:checked").map(function(){ return $(this).val(); });
$.ajax({
url: "/path/to/controller",
type:"POST",
data:{ids:JSON.stringify(idList)}
success:mySuccessFunction
});
}
Related
If I am here asking it is because we are stuck on something that we do not know how to solve. I must admit, we already searched in StackOverflow and search engines about a solution.. but we didn't manage to implement it / solve the problem.
I am trying to create a JavaScript function that:
detects in my html page all the occurrences of an html tag: <alias>
replaces its content with the result of an Ajax call (sending the
content of the tag to the Ajax.php page) + localStorage management
at the end unwraps it from <alias> tag and leaves the content returned from ajax call
the only problem is that in both cases it skips some iterations.
We have made some researches and it seems that the "problem" is that Ajax is asynchronous, so it does not wait for the response before going on with the process. We even saw that "async: false" is not a good solution.
I leave the part of my script that is interested with some brief descriptions
// includes an icon in the page to display the correct change
function multilingual(msg,i) {
// code
}
// function to make an ajax call or a "cache call" if value is in localStorage for a variable
function sendRequest(o) {
console.log(o.variab+': running sendRequest function');
// check if value for that variable is stored and if stored for more than 1 hour
if(window.localStorage && window.localStorage.getItem(o.variab) && window.localStorage.getItem(o.variab+'_exp') > +new Date - 60*60*1000) {
console.log(o.variab+': value from localStorage');
// replace <alias> content with cached value
var cached = window.localStorage.getItem(o.variab);
elements[o.counter].innerHTML = cached;
// including icon for multilingual post
console.log(o.variab+': calling multilingual function');
multilingual(window.localStorage.getItem(o.variab),o.counter);
} else {
console.log(o.variab+': starting ajax call');
// not stored yet or older than a month
console.log('variable='+o.variab+'&api_key='+o.api_key+'&lang='+o.language);
$.ajax({
type: 'POST',
url: my_ajax_url,
data: 'variable='+o.variab+'&api_key='+o.api_key+'&lang='+o.language,
success: function(msg){
// ajax call, storing new value and expiration + replace <alias> inner html with new value
window.localStorage.setItem(o.variab, msg);
var content = window.localStorage.getItem(o.variab);
window.localStorage.setItem(o.variab+'_exp', +new Date);
console.log(o.variab+': replacement from ajax call');
elements[o.counter].innerHTML = content;
// including icon for multilingual post
console.log(o.variab+': calling multilingual function');
multilingual(msg,o.counter);
},
error: function(msg){
console.warn('an error occured during ajax call');
}
});
}
};
// loop for each <alias> element found
//initial settings
var elements = document.body.getElementsByTagName('alias'),
elem_n = elements.length,
counter = 0;
var i = 0;
for(; i < elem_n;i++) {
var flag = 0;
console.info('var i='+i+' - Now working on '+elements[i].innerHTML);
sendRequest({
variab : elements[i].innerHTML,
api_key : settings.api_key,
language : default_lang,
counter : i
});
$(elements[i]).contents().unwrap().parent();
console.log(elements[i].innerHTML+': wrap removed');
}
I hope that some of you may provide me some valid solutions and/or examples, because we are stuck on this problem :(
From our test, when the value is from cache, the 1st/3rd/5th ... values are replaced correctly
when the value is from ajax the 2nd/4th .. values are replaced
Thanks in advance for your help :)
Your elements array is a live NodeList. When you unwrap things in those <alias> tags, the element disappears from the list. So, you're looking at element 0, and you do the ajax call, and then you get rid of the <alias> tag around the contents. At that instant, element[0] becomes what used to be element[1]. However, your loop increments i, so you skip the new element[0].
There's no reason to use .getElementsByTagName() anyway; you're using jQuery, so use it consistently:
var elements = $("alias");
That'll give you a jQuery object that will (mostly) work like an array, so the rest of your code won't have to change much, if at all.
To solve issues like this in the past, I've done something like the code below, you actually send the target along with the function running the AJAX call, and don't use any global variables because those may change as the for loop runs. Try passing in everything you'll use in the parameters of the function, including the target like I've done:
function loadContent(target, info) {
//ajax call
//on success replace target with new data;
}
$('alias').each(function(){
loadContent($(this), info)
});
On my web app, the user is asked a question and can choose only one of two answers. Yes or no. A query string is created based on their answer.
The following code carries the query string through the URL of every page:
var navlinks = document.getElementsByClassName("qString");
$(navlinks).prop("href", function() { return this.href + location.search; })
There are only 2 query strings, ?choice=yes and ?choice=no.
Once the user is taken through the app, if they navigate to either park01.html, park02.html, or park03.html from any other page, data will be pulled accordingly via a called function().
Here's my concept in pseudocode:
// I assume I should store specific html pages to a variable
var parkPages = ["park01.html", "park02.html", "park03.html”];
if (user clicks on specified html pages stored in variable) {
and the url contains = ?choice=yes;
Then call these functions: funcA(), funcB(), funcC();
}
else {
the url contains = ?choice=no;
Then call these functions: funcD(), funcE(), funcF();
}
Does the concept make sense? And what does the syntax look like?
If you're simply looking for a concrete translation of your pseudocode into JavaScript, based on your last comment, this should be what you need:
if (location.search === "?choice=yes") {
funcA();
funcB();
funcC();
}
else {
funcD();
funcE();
funcF();
}
Though at this stage, I'd recommend spending less time here and more on instructional/tutorial based websites.
I'm just learning JavaScript, and it seems there is a lot of information for folks like me about the way it processes functions asynchronously.
While I am still trying to get my head around this, I find myself struggling with some sharepoint csom because of what I am trying to do. Perhaps I am just going about this completely wrong, but as I said, just learning.
Trying to use SP CSOM to get list data like this:
getGridData() {
var gridURL = "https://mySite/ListData.svc/Projects";
var request = new Sys.Net.WebRequest();
request.set_httpVerb("GET");
request.set_url(gridURL);
request.get_headers()["Accept"] = "application/json";
request.add_completed(onCompletedProjectCallback);
request.invoke();
}
onCompletedProjectCallback(response, eventArgs) {
var getProject = eval("(" + response.get_responseData() + ")");
var buildMarkUp = '';
for (var i = 0; i < getProject.d.results.length; i++) {
buildMarkUp += "<div>" + getProject.d.results[i].ProjectName + "</div>";
}
}
This works great.
I do know about other methods such as spservices, but I like this as it seems to be faster for me and returns JSON which is preferable.
What happens when I want to use the ProjectID in the above to call another function and pass the id in order to get related values from a list.
However, I want to build the buildMarkUp string in order before it gets appended to the DOM(oh yeah jQuery btw). Something like this might be totally wrong, but it is what I was trying to do:
onCompletedProjectCallback(response, eventArgs) {
var getProject = eval("(" + response.get_responseData() + ")");
var buildMarkUp = '';
for (var i = 0; i < getProject.d.results.length; i++) {
buildMarkUp += "<div>" + getProject.d.results[i].ProjectName + "</div>";
//call nested function here so that I can go retrieve values for each ProjectID from another list
var getOtherData = getRelatedData(getProject.d.results[i].ProjectID);
}
}
getRelatedData(passedProjectID) {
// have to use the same method as the original to get more sharepoint list data
var relatedURL = "https://mySite/ListData.svc/Related$filter=ProjectID eq " + passedProjectID;
var request = new Sys.Net.WebRequest();
request.set_httpVerb("GET");
request.set_url(relatedURL);
request.get_headers()["Accept"] = "application/json";
request.add_completed(onCompletedRelatedCallback);
request.invoke();
}
This is where I am really struggling with this though.
A separate callback means it is not going back to the original function with data if I return right?
Does the original function keep processing and just fire the nested functions?
How can I control when/how/what values are returned to the original function then? Can I?
Basically what if I was trying to build a table, where each referenced project row contained data from other sharepoint lists, and I would need to control the order in which the string that I was going to append to the DOM got built?
You are correct that a function that executes a web request continues immediately and cannot return the data from that call. You need to code the callbacks to add the data they collected to a public data structure -- perhaps create an object that accumulates the data as new attributes. When you have collected all of the data, the last callback can build the HTML elements. If you are doing multiple simultaneous AJAX requests, then each callback can call a common function to see if all requests have finished. For example:
function checkLoadingComplete() {
if (loadedData.project && loadedData.relatedData && loadedData.summaryData) {
//now build the HTML elements
}
}
A separate callback means it is not going back to the original function with data if I return right?
Yes. return returns from the function executing, but does not wait for the ajax load event and its handler to get the data.
Does the original function keep processing and just fire the nested functions?
Yes. It starts a bunch of ajax requests, and gets back undefined for each of them.
How can I control when/how/what values are returned to the original function then? Can I?
You will need to use callbacks, you never will "return" the values.
Backstory: To specify the correct route for a jqGrid that I show on my ASP.NET MVC 3 page, I do something like so:
$('#jqgFlavors').jqGrid({
url: '#Url.Action("FlavorData", "IceCream")',
etc...
and that will produce the correct route either when running locally out of Visual Studio (where things live at something like "http://localhost:90125/IceCream" or on the deployed site where things live at something like "http://thehostsite/mydeployedsitename/IceCream".
Great. Now the issue I'm having is that I use the onSelectRow in the grid to do a master/details thing based on the selected row's flavor id value. First, I tried doing this to just get the route correct:
onSelectRow: function(theRow){
$('#flavorDetails').load('#Url.Action("Details","IceCream", new {id = 42)})');
}
So that I can pass the value 42 in as the 'id' parameter in the Details action of the IceCream controller. And that works fine, but of course I don't want to hard code the value 42, rather pull the flavor id from the grid itself. So I have tried to reference the flavorID but can't seem to get the syntax correct:
onSelectRow: function(theRow){
var grid = jQuery('#jqgFlavors');
var flavorID = grid.jqGrid('getCell', theRow, 'FlavorID');
$('#flavorDetails').load('#Url.Action("Details","IceCream", new {id = flavorID)})');
}
I'm sure you get what I'm going for here - referencing the flavorID value I extract from the grid. But what I get is a compilation error:
The name 'flavorID' does not exist in the current context.
I suspect this is really simple. How do I reference correctly that variable?
You could use the second argument of the .load() method which allows you to pass additional parameters:
var flavorID = grid.jqGrid('getCell', theRow, 'FlavorID');
$('#flavorDetails').load('#Url.Action("Details", "IceCream")', { id: flavorID });
This might probably use the following url: /IceCream/Details?id=123 instead of what you might want /IceCream/Details/123 because javascript doesn't know anything about your routes but why care? It will still map correctly to the controller action:
public ActionResult Details(int id)
{
...
}
But if you are really anal about urls and insist on having the first type of url I've seen people doing the following:
var flavorID = grid.jqGrid('getCell', theRow, 'FlavorID');
var url = '#Url.Action("Details", "IceCream", new { id = "_TOREPLACE_" })';
url = url.replace('_TOREPLACE_', flavorID);
$('#flavorDetails').load(url);
Personally I wouldn't do it but providing it just for the record.
I'm working using scriptaculous library. However I'm facing some issues with inclusion of the JSON library for the prototype library. It adds a toJSONSTring and parseJSONSTRING method to all objects automatically and frankly this is causing havoc in places. Like I can't seem to use the Ajax Updater function and I suspect its because of this toJSONSTring method that has been attached to my options object which I pass to it.
Is there anyway to unset or atleast somehow remove a function which has been added to the Object.
EDIT:::
Actually I'm trying to make an ajax request and I'm facing an issue in the
Ajax.Updater = Class.create(Ajax.Request,....
part of the prototype library. At the part where its supposed to execute and post an AJAX request it doesn't - especially at:
$super(url, options);
To be precise I'm using this sortable and editable table grid here at this url:
http://cloud.millstream.com.au/millstream.com.au/upload/code/tablekit/index.html
Basically you clcik on a table cell to edit it and push the OK button to confirm. Upon clicking the button an ajax request is made.
The editable feature of the table calls the Ajax updater as follows in a submit function:
submit : function(cell, form) {
var op = this.options;
form = form ? form : cell.down('form');
var head = $(TableKit.getHeaderCells(null, cell)[TableKit.getCellIndex(cell)]);
var row = cell.up('tr');
var table = cell.up('table');
var ss = '&row=' + (TableKit.getRowIndex(row)+1) + '&cell=' + (TableKit.getCellIndex(cell)+1) + '&id=' + row.id + '&field=' + head.id + '&' + Form.serialize(form);
this.ajax = new Ajax.Updater(cell, op.ajaxURI || TableKit.option('editAjaxURI', table.id)[0], Object.extend(op.ajaxOptions || TableKit.option('editAjaxOptions', table.id)[0], {
postBody : ss,
onComplete : function() {
var data = TableKit.getCellData(cell);
data.active = false;
data.refresh = true; // mark cell cache for refreshing, in case cell contents has changed and sorting is applied
}
}));
},
The problem is that the request is never made and I keep pushing the OK button to no avail.
EDIT::::::::::::::::
I'm still stumped here - I've even tried calling the Ajax.Updater function on my own and it won't work at all. Its like this function has officially been rendered as useless all of a sudden. I've made the changes you said but all to no avail :( frankly I'm running out of options here - another idea would be to ditch this tablekit and look for something else which has similar functionality in the hopes that THAT MIGHT work!
It sounds like those methods are being added to the prototype of Object. By adding to Object's prototype, the library is automatically giving everything that derives from Object (in other words, everything) those methods as well. You might want to take do some reading on Prototypal inheritance in Javascript to get a better handle on this.
Anyway, you can remove those methods by doing this:
delete Object.prototype.toJSONString;
delete Object.prototype.parseJSONString;
You can delete anything from an object using "delete":
a.toJSON = function () {};
delete a.toJSON;
a.toJSON() => error: toJSON is not a function
a.toJSON => undefined
However I don't think that what is happening happens because of what you think is happening :) Maybe give more details on the problem you have with Ajax.Updater?
Seen the edit. OK, can you also post the actual line of code that calls Ajax.Updater and, also important, explain in detail how the options object you feed to it is made?
Also, please make sure you're doing something like this:
new Ajax.Updater(stuff)
and NOT just:
Ajax.Updater(stuff)
You NEED to create a new Updater object and use "new" (most probably you're already doing that, just making sure).
OK I'm still not sure what is getting passed to ajax.Updater since you extend stuff that I can't see, but try this: remove the "&" from the beginning of the variable "ss"; in the options object use parameters: ss instead of postBody: ss.
delete obj.property
In this case:
delete obj.toJSONSTring;
delete obj.parseJSONSTRING;