Related
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.
I have the following singleton object declared in DOJO. I would like to know if there is any way to track it in Chrome console, for example with something like watch on window.mySingleTone.
// App (singletone)
define(['dojo/topic', 'ntv/state', ], function (_topic, _state) {
return {
init: function () {
console.log('app:init');
}
};
});
If you're looking for a way to get a reference to the object this module returns, calling require(mid) any time after the module has already been loaded will return it. You should be able to add that as a watch expression, too.
I currently have a Web Application that runs off a global Javascript-based API, and it is initialized like this:
var Api = {
someVar: "test",
someFunction: function() {
return "foo";
}
}
This API is shared across many "Widgets" that live in the Web Application, and they should all run off this single Api instance so they can pass data to each other.
AJAX is currently used to load these Widgets, for example in widgets/mywidget.html, and it's placed in, say, <div id='widget_<random number>'>...</div>
Certain other parts of the code may choose to add more functionality to Api, and it's currently done like this:
Api.myExtension = {
myNewFunction: function() {
return "bar";
}
}
However, some issues arise from this kind of usage:
Problem One: What if one Widget (these may be provided by third-parties) decides to hide some code within, and does something similar to Api = {}, destroying the global Api var everything lives on, and breaking the whole Application? Is it possible to protect this Api variable from being overwritten from outside? Only "extending" is allowed (adding new things), but "removing/changing" is not allowed. i.e.:
Api.foo = { test: "bar" } // allowed
Api.someVar = "changing the existing someVar"; // not allowed
The following code is located "inside" Api, for example:
var Api = {
Debug: {
Messages = new Array,
Write: function() {
Api.Debug.Messages.push("test"); // allowed
}
}
}
Api.Debug.Messages.push("test 2"); // not allowed
Probable Solutions I've Thought Of:
Suppose we simply use frames to resolve this issue. The Apis provided are now separate from each other. However, there's additional overhead when loading Api again and again if I have many Widgets running, and they can no longer communicate with the "Host" of the widgets (the page where frames reside in), for example, I may want to tell the host to show a notification: Api.Notify.Show("Test"), but it cannot do so because this Api is completely independent from other instances, and it cannot communicate with the "Host"
Using something like a "getter" and "setter" function for the Api to be read and written. I'm unsure on how to implement this, so any help on directions on how to implement this is welcome!
A mixture of 1/2?
There's no good way to prevent having a "third party" widget overwrite the a global variable. Generally it is the responsibility of whoever is putting together the final application to ensure that whatever JavaScripts they are using aren't littering the global namespace and conflicting. The best thing you can do in that direction is give your "Api" a nice, unique name.
What I think can help you a lot is something like the "revealing pattern", which would be a way of doing the "getters and setters" you mentioned, plus more if you needed it.
A simple, useless example would be like the following:
var Api = (function () {
// private variable
var myArray = [];
return {
addItem: function (newItem) {
myArray.push(newItem);
},
printItems: function () {
console.log("lots if items");
}
};
})();
Api.addItem("Hello, world");
Api.extensionValue = 5;
I think you should make a clear delineation of what is shared, or "singleton" data, and keep those items private, as with myArray in my example.
Make it a constant:
const Api = "hi";
Api = 0;
alert(Api); //"hi"
Take a look at
Object.freeze
More info here
Here is a code example from Mozilla's page:
var obj = {
prop: function (){},
foo: "bar"
};
// New properties may be added, existing properties may be changed or removed
obj.foo = "baz";
obj.lumpy = "woof";
delete obj.prop;
var o = Object.freeze(obj);
assert(Object.isFrozen(obj) === true);
// Now any changes will fail
obj.foo = "quux"; // silently does nothing
obj.quaxxor = "the friendly duck"; // silently doesn't add the property
// ...and in strict mode such attempts will throw TypeErrors
function fail(){
"use strict";
obj.foo = "sparky"; // throws a TypeError
delete obj.quaxxor; // throws a TypeError
obj.sparky = "arf"; // throws a TypeError
}
fail();
// Attempted changes through Object.defineProperty will also throw
Object.defineProperty(obj, "ohai", { value: 17 }); // throws a TypeError
Object.defineProperty(obj, "foo", { value: "eit" }); // throws a TypeError
However browser support is still partial
EDIT: see Kernel James's answer, it's more relevant to your question (freeze will protect the object, but not protect reassigning it. however const will) same issue with limited browser support though.
The only way (at least that I can think of) to protect your global variable is to prevent the Widgets from having a direct access to it. This can be achieved by using frames functions, as you suggested. You should create an object that contains all the functions that the Widgets should be able to use, and pass such to each Widget. For example:
var Api = {
widgetApi = {
someFunction: function(){
// ...
}
},
addWidget:function(){
var temp = this.widgetApi.constructor();
for(var key in this.widgetApi)
temp[key] = clone(this.widgetApi[key]);
return temp;
}
// Include other variables that Widgets can't use
}
This way, the Widgets could execute functions and communicate with the host or global variable Api. To set variables, the Widget would be editing its private object, rather than the global one. For every frame (that represents a Widget), you must initialize or create a copy of the widgetApi object, and probably store it inside an array, in such a way that an instance of a Widget is stored in the main Api object.
For example, given <iframe id="widget"></iframe>
You would do the following:
var widget = document.getElementById("widget");
widget.contentWindow.Api = Api.addWidget();
widget.contentWindow.parent = null;
widget.contentWindow.top = null;
Additionally, in every frame you would need to set the parent and top variables to null so that the Widgets wouldn't be able to access the data of the main frame. I haven't tested this method in a while, so there might be ways to get around setting those variables to null.
Sorry I couldn't be anymore specific with the title.
I'm building a web-site (personal), which displays different content to the user depending on the query string that is used in the url.
e.g. page=home.html would display home.html
The websites Javascript is wrapped inside an object, with each value containing different data, some pseudo code:
(function(){
var wrapper = {
init: function(){
//Runs on document ready
this.foo();
this.nav.render();
},
foo: function(){
//Some functionality goes here for the website, e.g. Display something from an API
},
nav: {
//Functionality to handle the navigation, has different properties
config: {
//Contains the config for nav, e.g. page names + locations
dir: '/directory/to/content/',
pages: {
page_name: wrapper.nav.config.dir + 'page_value'
}
},
render: function(){
//some code
},
routes: function(){
//some code}
}
}
};
$(function(){
wrapper.init();
});
})();
My problem is that I'm trying to prepend the dir value to each of the page values (inside the object where the pages are defined), expecting to get the output of (in this pseudo code case) of directory/to/content/page_value, but instead dir is undefined when I'm trying to access it, I've tried the following to achieve what I want:
wrapper.nav.config.dir + 'page_value'
I've been playing around with the last 30 minutes trying to find out what I'm doing wrong, and even thought about hard-coding the URL in for each page.
The reasoning for wanting to do this is that my local development server and web host have different directory structures, so I don't want to re-write the URL's each time I want to develop + publish. As for why everything is wrapped inside an object, I thought it would be easier to maintain this way.
Hopefully the answer is simple and it's just an amateur mistake / lack of understanding.
The issue is that you can't refer to a variable that is being defined in that very definition.
So, inside the definition of wrapper, you can't refer to wrapper. And, inside the definition of config, you can't refer to config either and so on.
The usual design pattern for solving this is to initialize as much as you can in the declaration of your data structure and then do the rest in .init() when you can freely access all of it.
Change the first two lines to:
var wrapper = null;
(function(){
wrapper = {
Otherwise, the wrapper is a local variable to your anonymous function.
The problem is that you're still busy defining the wrapper when you ask for its value, which is why it's still undefined.
The code below fails too:
var x = {
y:"1",
z:x.y
}
Why not:
//...
init: function(){
//Runs on document ready
this.foo();
var config = this.nav.config;
for (var page in config.pages) {
config.pages[page] = config.dir + config.pages[page];
}
},
//...
I want to make an ajax call that will return a json object. One of this JSON object's properties will be the string of a function to be executed in the client. I realise this can easily be solved by using eval, but seeing the many disadvantages of eval, I'd rather avoid it. My question is:
Can I in some way return from the server some js code and execute it without resorting to eval?
As requested, here's some example code:
Server (Node.js):
var testFunc = function() {
alert('h1');
};
app.get('/testPack', function(req, res) {
var template = jade.render('h1 hi');
res.send({
template : template,
entity : testFunc.toString(),
data : {
id: "OMG I love this"
}
});
});
Client:
$(document).ready(function() {
$.ajax({
url: '/testPack',
success: function(data) {
$('body').append($(data.template))
alert(data.data.id);
var entity = eval(data.entity);
entity();
}
})
})
Of course, the returned function called entity wouldn't do such a silly thing, it would expose an API of the returned widget.
Just to clarify, I'd like to avoid having to make a separate call for the javascript itself. I'd rather bundle it with the template and data to render.
Easiest way to do that, is not to call a server through an ajax, but instead to create a new script tag on the page with the url pointing to a RESTful web-service that would output pure JavaScript (not JSON). That way your output will be evaluated by the browser directly without the use of eval.
To expand a little on my answer:
To get around the problems of running script in the global context you could do some tricks. For example, when you are adding script tag to the head, you can bind onload event (or rather fake onload event, since IE doesn't support onload on the script tag) to it, and if your response from the server will be always wrapped in the the function with a known name, you could apply that function from within your object. Example code below (this is just an example though):
function test ()
{
this.init = function ()
{
var script = document.createElement("script");
script.type = "text/javascript";
script.language = "javascript";
script.src = "test.js";
var me = this;
window.callMe = function () { me.scriptReady(me); };
var head = document.getElementsByTagName("head")[0];
head.appendChild(script);
};
this.scriptReady = function (object)
{
serverResponse.call(object);
};
this.name = "From inside the object";
this.init();
}
var t=new test();
The server response should look something like this:
function serverResponse()
{
alert(this.name);
}
window.callMe();
In this case, everything inside serverResponse() will use your object as "this". Now if you modify your server response in this way:
function serverResponse()
{
this.serverJSONString = { "testVar1": "1", "testVar2": 2 };
function Test()
{
alert("From the server");
}
Test();
}
window.callMe();
You can have multiple things being returned from the server and with just one response. If you don't like just setting variables, then create a function in your main object to handle JSON string that you can supply by calling this function from your response.
As you can see, it's all doable, it really doesn't look pretty, but then again, what you are trying to do is not pretty to begin with.
P.S. Just inserting a string inside tag will not work for IE, it will not allow you to do that. If you don't have to support IE, then you could get away with just inserting server response inside a newly created script tag and be done with it.
P.P.S. Please don't use this code as is, cause I didn't spend too much time writting it. It's ugly as hell, but was just ment as an example:-)
No, you can't do this by definition, because JavaScript functions are not valid JSON. See the spec here:
http://www.json.org/
If you're returning a string, then that's what it is: just a string. You can't evaluate it without eval. You can call whatever else you're returning whatever you want, but please don't call it JSON.
Here's an example of how I think this could work.
The json object represents what is returned from the server. The c and d properties contain function names as strings. If those functions are properties of some other object which exists in your page, then you should be able to call them using the object["property"] accessor.
See it working on jsFiddle: http://jsfiddle.net/WUY4n/1/
// This function is a child of the window object
window.winScopedFunction = function() {
alert("ROCK THE WIN");
}
// This function is a child of another object
var myObject = {
myFunction : function() {
alert("ROCK ON");
}
};
// pretend that this json object was the result of an ajax call.
var jsonResultFromServer= {
a : 1,
b : 2,
c : "myFunction",
d : "winScopedFunction"
};
// you can call the local functions like so
myObject[jsonResultFromServer.c]();
window[jsonResultFromServer.d]();
Yes, there's a way, but it has the exact same disadvantages as eval.
You can use the Function constructor to create a new function, and then call it. For example:
new Function(code)();
http://code.google.com/p/json-sans-eval/ is a fast JSON parser that does not use eval, and JSON.parse is becoming increasing widely available in new browsers. Both are excellent alternatives to eval for parsing JSON.
You can use the trick that Google does with Google Charts.
<html>
<head>
<script>
function onWorkDone(data) {
console.log(data);
}
</script>
<script src="callback.js"></script>
</head>
</html>
Then your callback.js is:
function doWork(callback) {
callback({result: 'foo'});
}
doWork(onWorkDone);
Basically, your script will call onWorkDone when the doWork completed. You can see a working example here:
http://jsfiddle.net/ea9Gc/
Do you have some example cases? Some things I can think of is you that you can just have a regular function inside your js file, and your server will return some parameters for your function to execute. You can even specify what function to use! (Isn't that amazing?)
// your js file
var some_namespace = {
some_function : function(a, b){
// stuff
}
}
// your server output
{
some_other_data: "123",
execute: {
func: "some_namespace.some_function",
params: [1, 2]
}
}
// your ajax callback
function(r){
window[r.execute.func].apply(this, r.execute.params);
}
The reasons of not using eval
Well, you already said it yourself. Don't use eval. But you have a wrong picture regarding why.
It is not that eval is evil. You are getting the reason wrong. Performance considerations aside, using eval this way allows a sloppy programmer to execute code passed from server on the client. Notice the "passed from server" part.
Why never execute code passed from server
Why don't you want to execute code passed from the server (incidentally that's what you're planning to do)?
When a browser executes a script on a web page, as long as the web site is valid -- i.e. really yours, and not a malware site pretending to be yours trying to trick your users -- you can be reasonably sure that every bit of code the browser is running is written by yourself.
Hacker's heaven -- script injection attacks
Now, if you are passing data from the server to your web application, and that data contains executable functions, you're asking for trouble. In the long, twisted journey of that data going from your server to your client's browser, it goes through the wild west called the Internet, perhaps through multiple layers of proxies and filters and converters, most of which you do not control.
Now, if a hacker is hiding somewhere in the middle, takes your data from the server, modify the code to those functions to something really bad, and sends it away to your client, then your client browser takes the data and executes the code. Voila! Bad things happen. The worse is: you (at the server side) will never know that your clients are hacked.
This is called a "script injection attack" and is a serious sercurity risk.
Therefore, the rule is: Never execute functions returned from a server.
Only pass data from server
If you only accept data from a server, the most that can happen whan a hacker tempers with it is that your client will see strange data coming back, and hopefully your scripts will filter them out or handle them as incorrect data. Your client's browser will not be running any arbitrary code written by the hacker with glee.
In your client-side script, of course you're sticking to the Golden Rule: Do not trust ANY data coming through the Internet. Therefore you'd already be type-check and validating the JSON data before using it, and disallowing anything that looks suspicious.
Don't do it -- pass functions from server and execute on client
So, to make a long story short: DON'T DO IT.
Think of another way to specify pluggable functionalities on the browser -- there are multiple methods.
I've had this same question, and I fixed it this way:
File: functions.js.php?f=1,3
$functions=array(
'showMessage' => 'function(msg){ alert(msg); }',
'confirmAction' => 'function(action){
return confirm("Are you sure you want to "+action+"?");
}',
'getName' => 'function getName(){
return prompt("What is your name?");
}'
);
$queried = explode($_REQUEST['f']);
echo 'var FuncUtils = {'; // begin javascript object
$counter=1;
foreach($functions as $name=>$function){
if(in_array($counter, $queried))
echo '"'.$name.'":,'.$function.',';
$counter++;
}
echo '"dummy":null };'; // end javascript object
File: data5.json
{
"action" : ['confirmAction','exit']
}
File: test.js
$(document).ready(function(){
$.getScript('functions.js.php?f=1,3');
});
function onBeforeExit(){
$.getJSON('data5.json', function(data) {
var func = data.action.shift();
FuncUtils[func].apply(null, data.action);
});
}