Passing a currying function from Rails to JavaScript - javascript

I need to pass an URL to a .js file. This URL is generated by Rails and accepts one argument.
#routes
get "my_super/:some_id" => "controller1#my_super",
#index.html.haml
:javascript
var myUrlFunc = "#{my_super_url}"; //my_super_url(...) expects one argument
And a .js file:
$.ajax({
url: myUrlFunc($("#active_user_id"));
})
//................
The point is I don't know some_id initially as it's dynamically chosen, it's chosen from a drop down list. So I have to a function myUrlFunc which takes one argument and returns the URL instead of the URL itself. I thought this would work that it didn't due to an error:
No route matches {:action=>"my_super", :controller=>"controller1"} missing required keys: [:some_id]
What do I do about this?

As you have found out, the routing helper won't let you call it with missing parameters. Further more ruby doesn't know how to serialize a ruby method into a javascript function.
One simple, if not particularly elegant would be to pass a dummy value to my_super_url. Your function would then be along the lines of
var myUrlFunc = function(id){
var base = #{my_super_url("--DUMMY--")};
return base.replace("--DUMMY--", id);
}

Related

How to pass the action and controller to the #Url.action call

I would like to pass the action name and controller name to the #Url.action call within a Javascript function as variables. Is there a method to achieve this?
function list_onSelectionChanged(e) {
var strActionName = "Map";
var strControllerName = "PMO";
$("#content").load("#Url.Action(strActionName,strControllerName)");
}
You can't pass them from client-side code in this way because the server-side code has already run at that point. Specify them directly in the server-side operation:
function list_onSelectionChanged(e) {
$("#content").load("#Url.Action("Map", "PMO")");
}
Note that it looks like the syntax will fail because of nested double-quotes. However, keep in mind that the Razor parser identifies the server-side code separately from the client-side code. So this is the entire server-side operation:
Url.Action("Map", "PMO")
And the result of that operation is emitted to the client-side code:
$("#content").load("result_of_server_side_operation");
I was able to modify the script as follows:
function list_onSelectionChanged(e) {
var url = e.addedItems[0].path;
$.get(url, function (data) {
$("#content").html(data);
});
}
Even simpler:
$("#content").load(url);
This allows a variable to be passed for the url.

Loading Custom Component ViewModel from JavaScript file

I'm trying to create a custom component loader within knockout but I'm struggling with the view model. Essentially I want to remotely go grab both the HTML template and the JavaScript view model, but in this instance I don't want to use a traditional AMD module loader.
I've managed to get some of this working, specifically loading the HTML template but I can't figure out how to load the view model. Before I start here's my directory structure:
-- index.html
-- customerLoader.js
-- comps
   -- myCustom.html
   -- myCustom.js
So I've created my component loader like so. getConfig basically takes the name of the component and turns that into a path for the viewModel and the html template.
var customLoader = {
getConfig: function(name, callback) {
callback({ template: "comps/" + name + ".html", viewModel: "comps/" + name + ".js" });
},
loadTemplate: function(name, templateConfig, callback) {
console.log("loadTemplate", name, templateConfig);
$.get(templateConfig, function(data) {
callback(data);
});
},
loadViewModel: function(name, templateConfig, callback) {
console.log("loadViewModel", name, templateConfig);
$.getScript(templateConfig, function(data) {
callback(data);
});
}
};
ko.components.loaders.unshift(customLoader);
This successfully makes a request to load the template, which brings back some basic content. What I'm struggling with is the view model. I'm not sure what should be in the target of my JavaScript file?
I assumed that I'd want to return a function that would take some parameters, most likely a params object. However if I try and do this I get an error, telling me the JavaScript is invalid:
Uncaught SyntaxError: Illegal return statement
This is the current content I've got that is producing this error:
return function(params) {
console.log("myCustom.js", name, viewModelConfig);
// Add a computed value on
params.bookNum = ko.computed(function() {
switch(this.title()) {
case "A": return 1;
case "B": return 2;
case "C": return 3;
default: return -1;
}
});
//ko.components.defaultLoader.loadViewModel(name, viewModelConstructor, callback);
};
So ultimately I'm not sure how to achieve this, but I guess there are 3 basic questions that explain the gaps in my understanding:
What should my "view model" JavaScript file contain exactly? A function? An object? etc...
Do I need to call the ko.components.defaultLoader.loadViewModel at all?
Within my customLoader what should loadViewModel() be doing with the result of the jQuery callback? I'm not sure if I get back a JavaScript object, or just a string?
I'm open to achieve this in a different way if need be (e.g. not using jQuery but getting files a different way), but I don't want to use a module loader (e.g. require.js/curl.js in this instance).
First lets figure out what is happening...
From the docs:
This ($.getScript()) is a shorthand Ajax function, which is equivalent to:
$.ajax({
url: url,
dataType: "script",
success: success
});
And from jQuery.ajax():
...
dataType: ...
"script": Evaluates the response as JavaScript and returns it as plain text.
So your code is fetched, evaluated and then would have been returned as text, but evaluation first fails because you can't return if you're not within a function.
So what can be done? There are several options:
Use a module loader.
jQuery isn't a module loader, and as such it doesn't have the ability to parse fetched code and create a value / object from that code. A module loader is designed specifically for this task. It will take a script written in a specific pattern and "evaluate" it into a value (typically an object with 1 or more properties).
Change your script to a legal script
Because it's illegal to have a return statement in global code, your current code fails. You could however create a named function (or a variable with a function expression) and then use that name to reference that function. It could look like this:
function myCreateViewModel(param) {
// whatever
}
And the usage would be:
$.getScript(templateConfig, function() {
callback(myCreateViewModel);
});
The downside here is that if you ever go through that code path twice in the same page, your script will overwrite the old declaration. That might not ever be a problem, but it feels dirty.
Not use $.getScript(), use $.ajax() (or $.get()) with dataType: 'text' and evaluate yourself.
Remove the return from your code, and wrap it with an eval(). It will be evaluated as a function expression, the return value of the eval will be your function, and you could pass that directly to the callback:
$.get({
url: templateConfig,
dataType: 'text',
success: function(text) {
callback(eval(text));
}
});
This will work, but it will use the frowned upon eval(), which is exposing you to various risks.

Razor Syntax in External Javascript

So as you might know, Razor Syntax in ASP.NET MVC does not work in external JavaScript files.
My current solution is to put the Razor Syntax in a a global variable and set the value of that variable from the mvc view that is making use of that .js file.
JavaScript file:
function myFunc() {
alert(myValue);
}
MVC View file:
<script language="text/javascript">
myValue = #myValueFromModel;
</script>
I want to know how I can pass myValue directly as a parameter to the function ? I prefer to have explicit calling with param than relying on globals, however I'm not so keen on javascript.
How would I implement this with javascript parameters? Thanks!
Just have your function accept an argument and use that in the alert (or wherever).
external.js
function myFunc(value) {
alert(value);
}
someview.cshtml
<script>
myFunc(#myValueFromModel);
</script>
One thing to keep in mind though, is that if myValueFromModel is a string then it is going to come through as myFunc(hello) so you need to wrap that in quotes so it becomes myFunc('hello') like this
myFunc('#(myValueFromModel)');
Note the extra () used with razor. This helps the engine distinguish where the break between the razor code is so nothing odd happens. It can be useful when there are nested ( or " around.
edit
If this is going to be done multiple times, then some changes may need to take place in the JavaScript end of things. Mainly that the shown example doesn't properly depict the scenario. It will need to be modified. You may want to use a simple structure like this.
jsFiddle Demo
external.js
var myFunc= new function(){
var func = this,
myFunc = function(){
alert(func.value);
};
myFunc.set = function(value){
func.value = value;
}
return myFunc;
};
someview.cshtml
<script>
myFunc.set('#(myValueFromModel)');
myFunc();//can be called repeatedly now
</script>
I often find that JavaScript in the browser is typically conceptually tied to a specific element. If that's the case for you, you may want to associate the value with that element in your Razor code, and then use JavaScript to extract that value and use it in some way.
For example:
<div class="my-class" data-func-arg="#myValueFromModel"></div>
Static JavaScript:
$(function() {
$('.my-class').click(function() {
var arg = $(this).data('func-arg');
myFunc(arg);
});
});
Do you want to execute your function immediately? Or want to call the funcion with the parameter?
You could add a wrapper function with no parameter and inside call your function with the global var as a parameter. And when you need to call myFunc() you call it trough myFuncWrapper();
function myFuncWrapper(){
myFunc(myValue);
}
function myFunc(myParam){
//function code here;
}

Passing function object references as serialized json

I have written some widgets using the jquery widget factory. Normally, I pass options and callback references using the widget constructor in a JavaScript code block.
Now I want to use the widgets in auto generated html pages and pass options to them without using JavaScript blocks. I am specifying the options in embedded json which a common JavaScript code block parses and passes it on to the widget.
<div id="search_widget">
<script type="application/json">
{
"search": {
"search_string": "text to be searched",
"callback": "a function object in an outer scope"
}
}
</script>
</div>
How do I pass the function objects preserving their scope to the widgets in json format ?
The widget will then call the function specified when a condition is met.
Thanks for your help.
You can pass the function body as text, but your receiving context will have to know it's a function and "reconstitute" it with the Function constructor (or something). You can't preserve the "scope", though that doesn't really mean much in a block of JSON.
You could also pass the function name, or some identifying pattern, or anything else you like, but all on the condition that the receiving code knows how to act on that.
JSON provides no mechanism for representing a "function". The concept is completely alien to the format, and rightly so, as it's intended to be language-neutral.
edit — Note that if you encode your function by name:
<script type="application/json">
{
"search": {
"search_string": "text to be searched",
"callback": "search_callback_23"
}
}
</script>
Then the code that grabs the object and interprets the contents can always invoke the function with the outer JSON object as the context:
function handleJSON( the_JSON_from_the_example ) {
var json = JSON.parse(the_JSON_from_the_example);
if ("search" in json) {
var searchTerm = json.search.search_string;
var callback = findCallbackFunction(json.search.callback); // whatever
callback.call(json, searchTerm); // or json.search maybe?
}
}
In other words, if the code that's interpreting the JSON-encoded information knows enough to find callback function references, then it can probably figure out an appropriate object to use as this when it calls the function.

Passing a local variable to a function that I can only make limited changes to

I am just starting to get into JavaScript and couldn't find an exact scenario like this yet on SO, so I'm going to try my luck. I have two functions in an external JS file which create video feeds on our website:
function getVideos() {
//gets a list of videos
}
//callback function automatically called by getVideos()
function response(jsonData) { //can't change this line
var resp = document.getElementById("resp"); //can change this line and any subsequent lines
//parses data and populates resp
}
Then, from the HTML side, we just call getVideos() and the video feed will be created and populated.
However, I want to be able to pass any element ID I want into response() so that we can create multiple video feeds in different places on the same page. The thing is I can't change the function declaration of response() to include another parameter. Or at least I'm not led to believe I can by the company hosting our videos.
I've tried wrapping response() with getVideos() and passing an element ID from there, but then response() doesn't get called, and the only solution I can think of is resorting to storing an element ID in a global variable, which I know is a no-no in general in JavaScript.
My question is: Do I just bite the bullet and use a global variable, or is there another way?
For more info, here is our JS code as it stands now (with the closure): http://www.thebearrocks.com/Other/js/videoFeed/createVideoFeed.js
And here is the tutorial on response() we're following from the host of our videos: http://support.brightcove.com/en/video-cloud/docs/making-media-api-calls-dynamic-script-tags
may be you can use arguments? like so:
function response(jsonData) { //callback function automatically called by getVideos()
var elemId = arguments.length<2 ? "resp" : arguments[1]+"";
var resp = document.getElementById(elemId);
//parses data and populates resp
}
or, declare second argument what has default value like this:
function response(jsonData, elemId) {
elemId = elemId || "resp";
var resp = document.getElementById(elemId);
//parses data and populates resp
}
in this case function can be called as with one or two arguments
I've tried wrapping response() with getVideos() and passing an element ID from there, but then response() doesn't get called, and the only solution I can think of is resorting to storing an element ID in a global variable, which I know is a no-no in general in JavaScript.
My question is: Do I just bite the bullet and use a global variable, or is there another way?
No. Not the id variable needs to become global, but your local response function needs to for getting called back from the JSONP script - you're going to create a closure.
You can "export" it by calling
window.response = mylocalResponseFunction; // you did name that local var "response"

Categories