I am writing a caching system which works with backbone (which I am still new to).
The system is as follows:
First try to get the data from local storage and upon failing to do so then make the call to the server.
Can I overwrite the url() function to first check local storage and then make the call or should this mechanism be outside url() and backbone ? i.e. Maybe this is using backbone incorrectly ?
Thanks !
Edit:
As requested I am adding more details.
I am using backbone.js the specific library I am using via require.js is backbone_amd-min.
Code sample:
Model:
define([ 'jquery', 'underscore', 'backbone', 'cacher' ], function($, _, Backbone, cacher) {
var article = Backbone.Model.extend({
defaults : {
title : '',
content : '',
},
parse : function(response, xhr) {
return response;
},
url : function() {
//What I want to do is (specify where to retrieve the article):
var articleInCache = cacher.inCache(this.flag);
if(articleInCache)
return localStorage
else
return remoteUrl;
},
flag : '2'
});
return article;
});
Can I modify url() or should I be modifying fetch()
To answer this question you first need a quick primer on fetch. When you do:
someModel.fetch();
What happens behind the scenes is:
fetch uses the url (or urlRoot) method/property to figure out where to make the AJAX call
fetch uses the sync method to actually make the AJAX call.
After the AJAX call returns, fetch passes what it gets back through parse, then passes that to set.
Given that, you clearly don't want to override url, as it has nothing to do with the actual AJAX part that you want to replace. You could override fetch, and that would work:
fetch: function() {
var fetched = this.getLocalVersion();
if (fetched) {
return new $.Deferred().resolve();
} else {
return this.prototype.fetch.apply(this, arguments);
}
}
However, that won't help you when you want to save or delete your Model, so really sync (which covers all AJAX operations) is probably the ideal method to override.
... of course, there's no point in re-inventing the wheel, so rather than override anything your best bet is probably to use one of the existing Backbone LocalStorage libraries.
Related
I'm sending a GET request with jQuery
$.get("/index.html", /*Adding '?update' to the request*/ "update",
function (data) {/* Enter code here */}, "html");
where data is my server's response. I'm sending back a simple script like alert() so the 'data' variable equals <script> alert("Hello world!") </script>.
I need a way to automatically execute the script. I could just .append(data) to an element but I'm having multiple appends so that isn't really practical.
What is the easiest and the most practical way of executing the script?
Either .append it, like you said, or use eval(data), but then you'd have to get rid of the <script></script>. You can supply eval() a piece of Javascript code and it will execute that.
Please be aware that using eval should be avoided at all costs.
I did some crazy stuff in a case like this but you may think it is extreme. In my case I had to store some functions in localStorage and execute them by history state ( when user goes back/forth ). I have created a json object similar to
{obj:'myObject', fn: 'myfn', args: myArgs}
then stored this data base64 encoded. then when I need it back, I simply decoded content and
window.[data.fn].[data.obj].apply(null,data.args)`
did the trick without exposing too much data and not using eval. Eval comes from Evil so I would stay away. =)
UPDATE
So in my case all main core functions are json objects at window namespace similar to ( not actual content but an sample)
Member = {
initialize: function (){
//some process
},
render:function(memberId, selector){
//Some process
},
//...etc }
So when I store each item it, I used something similar to
var data = {obj: 'Member', fn: 'render', args: [1,'#member-block']}
then encoded version will be
localStorage.setItem('data', btoa(JSON.stringify(data)));
dmFyIGRhdGEgPSB7b2JqOiAnTWVtYmVyJywgZm46ICdyZW5kZXInLCBhcmdzOiB7bWVtYmVySWQ6MSwgc2VsZWN0b3I6ICcjbWVtYmVyLWJsb2NrJ319
Then when I need to call back
var data = JSON.parse(atob(localStorage.getItem('data'));
would return my original data object. Since the main functions in my case are in window namespace.
if (typeof window[data.obj]!=='undefined') { // same as window.Member
if (typeof window[data.obj][data.fn]!=='undefined' && typeof window[data.obj][data.fn]!=='function' ) { // make sure fn is defined and is a function
window[data.obj][data.fn].apply(null, data.args);
// we pass same arguments to function call with apply.
// `apply` will give us option to add arguments dynamically without knowing its size.
// it can be null any number of arguments that needed for that function.
}
}
I was trying to add an additional url attribute as a function to my page-object while using nightwatchjs.
Like:
module.exports = {
url: function() {
return this.api.launchUrl + '/content/site1.xhtml';
},
cancelUrl: function() {
return this.api.launchUrl + '/content/cancel_site1.xhtml';
}
}
Anyhow nightwatch is not able to get that 2nd attribute cancelUrl, ie undefined.
Why is that so? Shouldn't nightwatch be able to access that attribute as it is nothing more than a function call returning a string or am I misunderstanding a javascript or special page-object concept?
--
I am aware that there should be a page-object for each site so there should not be a 2nd site. Anyhow I would like to understand why this is not working technically.
Not sure I can answer the "why" (other than to say that when nightwatch loads up your page objects as globally available it must be wrapping your js file and filtering on 'known' functions) but I can offer a solution: add a command to your page object with the desired function. For example:
let pageCommands = {
cancelUrl: function() {
return this.api.launchUrl + '/content/cancel_site1.xhtml';
}
};
module.exports = {
commands: [pageCommands],
...
}
It's not the typical use of page commands, but your test would then be able to access the cancelUrl function on the page object instance.
More on page commands here
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.
We are using typehead for autocomplete with bootstrap 2.3, bootstrap-typeahead.js. Though typeahead is a jQuery plugin, jQuery.active is always 0 even when it is trying to get suggestions from server.
Does jQuery.active working only for jQuery ajax requests? Is there any alternative to jQuery.active if I use any plugins rather than jQuery.ajax to get information from server.
This is untested, but from the code on github it appears to me that this is the function responsible for retrieving and returning the data. It is found in the dropdown.js file of the plugin.
getDatumForSuggestion: function getDatumForSuggestion($el) {
var datum = null;
if ($el.length) {
datum = {
raw: Dataset.extractDatum($el),
value: Dataset.extractValue($el),
datasetName: Dataset.extractDatasetName($el)
};
}
return datum;
}
give that is the case you could probably utilize jquery's .active method to get you when the data is being extracted and when its been returned by modifying this function like so,
getDatumForSuggestion: function getDatumForSuggestion($el) {
var datum = null;
$.active++
if ($el.length) {
datum = {
raw: Dataset.extractDatum($el),
value: Dataset.extractValue($el),
datasetName: Dataset.extractDatasetName($el)
};
}
$.active--
return datum;
}
Personally, I wouldn't suggest using $.active but creating your own global variable for this specific purpose. The reason why is you can't always be sure, especially if you're using jquery plugins, when you might actually be calling a jQuery.ajax method and active will update at that time, perhaps not giving you what you're looking for, unless you are wanting to see other connections to the server along with this plugin, in which case use $.active
I need to handle globally ajax responses. Everything works ok, out of the box, when I only want to call normal javascript action without any arguments. Than I can use p:ajaxStatus controll, and application behaves in a correct way.
What I actually now need to do, is to handle situation, when during ajax request there was externalContext.redirect() call. It happens only in one place in application, but it is called from many places.
I was thinking, that I can for instance add callback param in RequestContext. But how can I access this param in javascript?
While watching in firebug, I can see that callbackParam is returned in json response, but how can I access this value in javascript?
It's been added as a property of default args object which is available in oncomplete context.
So, for example
RequestContext.getCurrentInstance().addCallbackParam("foo", "bar");
is available as
oncomplete="console.log(args.foo)"
See also:
PrimeFaces RequestContext showcase
Update: as per the comments, that turns out to fail in <p:ajaxStatus>. I sugges to report it as a bug to PF guys, that the arguments are not available in <p:ajaxStatus oncomplete>. In the meanwhile, you can workaround it with the following script which is loaded by <h:outputScript target="head"> inside the <h:body> (to guarantee that it's loaded after PrimeFaces script):
var originalPrimeFacesAjaxUtilsSend = PrimeFaces.ajax.AjaxUtils.send;
PrimeFaces.ajax.AjaxUtils.send = function(cfg) {
var originalOncomplete = cfg.oncomplete;
cfg.oncomplete = function() {
ajaxStatusOncomplete.apply(this, arguments);
if (originalOncomplete) {
originalOncomplete.apply(this, arguments);
}
};
originalPrimeFacesAjaxUtilsSend.apply(this, arguments);
};
function ajaxStatusOncomplete(xhr, status, args) {
// Do your thing here.
}
In p:ajaxStatus params available via PrimeFaces.ajax.Queue.xhrs
For example:
oncomplete="console.log(PrimeFaces.ajax.Queue.xhrs[0].pArgs.foo)"