I'm using the Blogger Protocol API and I'm having trouble deleting posts. I'm working on a webOS device and so I can't send DELETE directly; instead I use Google's workaround to use POST:
deletePostList: function(event)
{
var deletePostID = event.item.id.split('.').pop().split('-').pop();
var deleteRequest = new Ajax.Request("http://www.blogger.com/feeds/" + activeBlogID + "/posts/default/" + deletePostID,
{
method: 'post',
requestHeaders:
{
Authorization: 'GoogleLogin auth=' + authCode,
"X-HTTP-Method-Override": "DELETE",
"If-Match": "*"
},
onSuccess: this.deletePostRequestSuccess.bind(this),
onFailure: this.deletePostRequestFailure.bind(this)
});
},
This seems to work, i.e. deletePostRequestSuccess is called after this processes and all the headers and response text look like I think they should when deleting a post, but the reality is that the post remains in the feed. I tried adding the "If-Match" header to make sure it wasn't the GData conditional delete holding me up (even though I haven't changed anything in the post at this time), but that doesn't seem to help.
Any ideas on how to make this work? I'd like to stick with Protocol since it's native on webOS, whereas jQuery, etc. is not.
From what I can tell, your issue with the HTTP methods is not webOS, but in Prototype according to the source.
I would suggest creating a subclass:
<script type="text/javascript">
var MyAjaxRequest = Class.create(Ajax.Request, {
request: function(url) {
this.url = url;
this.method = this.options.method;
var params = Object.isString(this.options.parameters) ?
this.options.parameters :
Object.toQueryString(this.options.parameters);
/* comment out this stuff that prevents you from using the DELETE method
if (!['get', 'post'].include(this.method)) {
// simulate other verbs over post
params += (params ? '&' : '') + "_method=" + this.method;
this.method = 'post';
}
*/
if (params && this.method === 'get') {
// when GET, append parameters to URL
this.url += (this.url.include('?') ? '&' : '?') + params;
}
this.parameters = params.toQueryParams();
try {
var response = new Ajax.Response(this);
if (this.options.onCreate) this.options.onCreate(response);
Ajax.Responders.dispatch('onCreate', this, response);
this.transport.open(this.method.toUpperCase(), this.url,
this.options.asynchronous);
if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1);
this.transport.onreadystatechange = this.onStateChange.bind(this);
this.setRequestHeaders();
this.body = this.method == 'post' ? (this.options.postBody || params) : null;
this.transport.send(this.body);
/* Force Firefox to handle ready state 4 for synchronous requests */
if (!this.options.asynchronous && this.transport.overrideMimeType)
this.onStateChange();
}
catch (e) {
this.dispatchException(e);
}
});
</script>
That way you can use method: 'DELETE'
Related
I need to send some data to the server when the page is closed or refreshed. Therefore, I created a simple script that can handle my needs. The problem is that this script is not working in Mozilla firefox.
The script is working in many other browsers like chrome, chromium, brave, opera, falkon, epiphany, qutebroser, Midori, safari, edge. The problem is only with firefox.
var timeLog = {
start: null,
end: null,
init: function () {
this.start = new Date().getTime();
},
sendResults: function () {
this.end = new Date().getTime();
var url = "tracker";
url += "?" + "start=" + this.start;
url += "&" + "end=" + this.end;
url += "&" + "User-Agent-JS=" + navigator.userAgent;
url += "&" + "url=" + window.location.toString();
fetch(url, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
keepalive: true
});
}
};
window.onbeforeunload = function () {
timeLog.sendResults();
};
timeLog.init();
The error message is:
Uncaught (in promise) TypeError: NetworkError when attempting to fetch resource.
EDIT:
if the event for onbeforeunload is registred as here:
window.onbeforeunload = async function(event){
event.preventDefault();
timeLog.sendResults();
};
it is working, but I need to confirm that I want to leave the page.
As I found on the internet, the problem arises because firefox uses its own implementation of fetch.
-----------------------SOLUTION [still not working in firefox correctly]-------------------------
window.onbeforeunload = function (event) {
event.preventDefault();
timeLog.sendResults();
delete event['returnValue'];
};
-----------------------SOLUTION-------------------------
I used sendBeacon instead of fetch
so the final code is following:
/* ----REPLACED----
fetch(url, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
keepalive: true
});
*/
navigator.sendBeacon(url);
Let's add a bit more code to see what is going on, allow the fetch to complete then process any error (pause to see it) then proceed to the unload if no error happened - our desired case.
var timeLog = {
start: null,
end: null,
init: function() {
this.start = new Date().getTime();
},
sendResults: function() {
this.end = new Date().getTime();
var url = "tracker";
url += "?" + "start=" + this.start;
url += "&" + "end=" + this.end;
url += "&" + "User-Agent-JS=" + navigator.userAgent;
url += "&" + "url=" + window.location.toString();
return fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
keepalive: true
});
}
};
window.addEventListener('beforeunload', function(e) {
// Cancel the event
// e.preventDefault(); // If you prevent default behavior in Mozilla Firefox prompt will always be shown
// Chrome requires returnValue to be set
// e.returnValue = '';
let myfetch = timeLog.sendResults();
myfetch
// borrowed code https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
.then(response => {
//do something with response
const contentType = response.headers.get('content-type');
if (!contentType || !contentType.includes('application/json')) {
throw new TypeError("Oops, we haven't got JSON!");
}
return response.json();
})
.then(data => {
/* process your data further */
})
.catch(error => {
console.error(error);
e.preventDefault(); // pause to see the error in console
});
// the absence of a returnValue property on the event will guarantee the browser unload happens
delete e['returnValue'];
});
timeLog.init();
How I write java code from javascript that will complete in server. Please help me.
I have long been trying to remake it
It is query to server to will be logged.
this.login = function(options) {
//It is query to server to will be logged.
if (typeof (options.success) == "function" && typeof (options.error) == "function" && options.params != null) {
var successCallback = options.success;
var errorCallback = options.error;
} else {
AV.console.error(LP + 'Invalid number of arguments (min req = 3), Please read API Documentation.');
return;
}
$.ajax({
type: 'POST',
url: _sURL + '/csportal/v1/login',
contentType: 'application/json',
dataType: 'json',
data: JSON.stringify(options.params),
success: function(response) {
AV.console.debug(LP + "login::Success: " + JSON.stringify(response));
if (response && response.success == true) {
_userLoggedIn = 'true';
_userReturned = 'false';
_userInfo = response.data;
successCallback({"message": response.message,"data": response.data});
} else {
_userLoggedIn = 'false';
errorCallback({message: response.message});
}
},
error: function(e) {
AV.console.warn(LP + "login:: error: " + e.message);
errorCallback({message: e.responseText});
}
});
};
You have two options here.
Option 1 - Corresponding to your _sURL + '/csportal/v1/login, you need to create a class extending HttpServlet class, override the post method and return required response. Or if you are using any frameworks (like Spring MVC or Struts), you just need to override corresponding Action classes.
Option 2 - Corresponding to your _sURL + '/csportal/v1/login, you create a REST api (using Jersey), and write a POST method handling JSON request and return required RESPONSE.
If you don't have server side experience, consider catching a server side engineer from your team for help.
I'm trying to figure out the "correct" way of accomplishing custom update
functions in Backbone.js Models. An example of what I'm trying to do is:
var Cat = Backbone.Model.extend({
defaults: {
name : 'Mr. Bigglesworth',
location : 'Living Room',
action : 'sleeping'
},
sleep: function () {
// POST /cats/{{ cat_id }}/action
// { action: "sleep" }
},
meow: function () {
// POST /cats/{{ cat_id }}/action
// { action: "meow" }
}
})
From what I can tell, the Backbone.Collection.save() method only performs the
following:
POST /cats/{{ cat_id }}
{ name: 'Mr. Bigglesworth', location: 'Living Room', action: '{{ value }} '}
But the API I'm working with won't let me change action that way, only by:
POST /cats/{{ cat_id }}/action
{ action: "{{ value }}" }
Hopefully that makes sense?
Any help would be appreciated.
You can pass the URL as a parameter when you call save. Maybe you can do something like this:
var Cat = Backbone.Model.extend({
urlRoot: '/cats/',
defaults: {
name : 'Mr. Bigglesworth',
location : 'Living Room',
action : 'sleeping'
},
sleep: function () {
var custom_url = this.urlRoot + this.id + "/action";
this.save({}, { url: custom_url});
// POST /cats/{{ cat_id }}/action
// { action: "sleep" }
},
});
See here: Posting form data using .save() to pass url parameters.
You can also implement the sync method to use another URL if you always want to use a custom URL on update. See for example here: backbone.js use different urls for model save and fetch.
There are different approaches you can take to solve this, but IMO the cleanest is to override Backbone.sync to act the way you want it to act if it's universal to the server backend you're connecting to.
For instance, if you want every one of your models/collections to interact with a particular backend implementation, this approach makes a lot of sense.
This way you can leave the rest of the Collection (or Model) code as the Backbone default but it will work the way you want it to work.
For example:
// Store the default Backbone.sync so it can be referenced later
Backbone.vanillaSync = Backbone.sync;
// Most of this is just copy-pasted from the original Backbone.sync
Backbone.sync = function(method, model, options) {
var type = methodMap[method];
// Default options, unless specified.
_.defaults(options || (options = {}), {
emulateHTTP: Backbone.emulateHTTP,
emulateJSON: Backbone.emulateJSON
});
// Default JSON-request options.
var params = {type: type, dataType: 'json'};
// Ensure that we have a URL.
if (!options.url) {
params.url = _.result(model, 'url') || urlError();
}
// START ADD YOUR LOGIC HERE TO ADD THE /action
// Add the action to the url
params.url = params.url + '/' + options.action;
// Remove the action from the options array so it isn't passed on
delete options.action;
// END ADD YOUR LOGIC HERE TO ADD THE /action
// Ensure that we have the appropriate request data.
if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) {
params.contentType = 'application/json';
params.data = JSON.stringify(options.attrs || model.toJSON(options));
}
// For older servers, emulate JSON by encoding the request into an HTML-form.
if (options.emulateJSON) {
params.contentType = 'application/x-www-form-urlencoded';
params.data = params.data ? {model: params.data} : {};
}
// For older servers, emulate HTTP by mimicking the HTTP method with `_method`
// And an `X-HTTP-Method-Override` header.
if (options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) {
params.type = 'POST';
if (options.emulateJSON) params.data._method = type;
var beforeSend = options.beforeSend;
options.beforeSend = function(xhr) {
xhr.setRequestHeader('X-HTTP-Method-Override', type);
if (beforeSend) return beforeSend.apply(this, arguments);
};
}
// Don't process data on a non-GET request.
if (params.type !== 'GET' && !options.emulateJSON) {
params.processData = false;
}
// If we're sending a `PATCH` request, and we're in an old Internet Explorer
// that still has ActiveX enabled by default, override jQuery to use that
// for XHR instead. Remove this line when jQuery supports `PATCH` on IE8.
if (params.type === 'PATCH' && window.ActiveXObject &&
!(window.external && window.external.msActiveXFilteringEnabled)) {
params.xhr = function() {
return new ActiveXObject("Microsoft.XMLHTTP");
};
}
// Make the request, allowing the user to override any Ajax options.
var xhr = options.xhr = Backbone.ajax(_.extend(params, options));
model.trigger('request', model, xhr, options);
return xhr;
};
In the above example I assumed you had sent the action via the options array, if you actually wanted the static word /action you could just replace that block with:
// Add the action to the url
params.url = params.url + '/action';
This should give you the cleanest implementation while still keeping the rest of your code clean.
I have some trouble appending a token to the backbone url query string and hope you guys could help me out here. Three things to know,
There is a rest api that expects a token with each request
An nginx backend that does auth, serves the backbone app + proxy req to the api under /api
i'm a new to javascript + backbone :/
The backbone app actually reads the token from a cookie and I need to append this to the request url everytime backbone makes a call. I see this can be done by overriding backbone sync. but it troubles me in a few different things. like, this is what I do
console.log('overriding backbone sync');
var key ="token";
Backbone.old_sync = Backbone.sync
Backbone.sync = function(method, model, options) {
if (method === 'read') {
if (!(model.url.indexOf('?key=') != -1)) {
model.url = model.url + '?key=' + key;
}
} else {
old_url = model.url();
if (!(old_url.indexOf('?key=') != -1)) {
model.url = function() {
return old_url + '?key=' + key;
}
}
}
Backbone.old_sync(method, model, options);
};
model.url was returning a function when its not a "read" method and didn't know how to handle it well and the other trouble is when a consecutive request is made, the token is added twice. I tried to remove it with that indexOf stuff with no luck.
Is there a better way to do this ?
I don't think you need to override sync at all:
var globalKey = 'key123';
var urlWithKey = function(url, key) {
return function() {
return url + "?key=" + key;
};
};
var MyModel = Backbone.Model.extend({
url: urlWithKey('/my/url/', globalKey)
});
If you now create an object and save it, a POST request to my/url/?key=key123 is sent.
I guess you could also override the url method if this is the behavior you need for all of your Backbone models.
A general note: in Backbone most parameters, such as url can be a function or a value. I don't know why in your example it was a function once and a value in another case, but you always must be able to handle both ways if you override some of the internal functions. If you look at Backbone's sourcecode you will see that they use getValue to access these parameters:
var getValue = function(object, prop) {
if (!(object && object[prop])) return null;
return _.isFunction(object[prop]) ? object[prop]() : object[prop];
};
Update: Overloading the url method for all models could work like this:
var globalKey = 'key123';
(function() {
var baseUrl = Backbone.Model.prototype.url;
Backbone.Model.prototype.url = function() {
return this.baseUrl + "?key=" + globalKey;
};
})()
var MyModel = Backbone.Model.extend({
baseUrl: '/my/url/'
});
You could also leave the regular Backbone.Model as it is, and create your own base class. See http://documentcloud.github.com/backbone/#Model-extend for details.
Just set your URL like so:
url : function() {
return "/my/url" + this.key;
}
In your overridden .sync, you only need to set the key property.
I'm having a really frustrating problem I hope someone can help me with. Here is a piece of my Greasemonkey script, I can't figure out why the asynchronous requests are always sent to the same URL.
function parse(details) {
var element = $(details);
var coll = element.find("#my valid selector");
$.each(coll, function(index, href) {
SendData(href);
});
}
function SendData(url) {
GM_xmlhttpRequest ({
method: 'GET',
url: url,
headers: {
'User-agent': 'Mozilla/4.0 (compatible) Greasemonkey',
'Accept': 'application/atom+xml,application/xml,text/xml',
},
onload: function(responseDetails) {
doSomething(responseDetails.responseText);
}
});
}
When I fire up Fiddler, I can see that it makes the same request no matter how many items are in my collection. Whatever the first link is, all requests are made to the that link. I have verified that the parse method successfully passes a different link to the SendData function every time, but the requests are always made to the first URL in the collection.
I thought what I had was similar to what I found here, but maybe I'm missing something. Any help would be appreciated.
It seems as though url is not getting captured in a closure, so it's undefined for all but the first GM_xmlhttpRequest run.
Modifying SendData(), like so:
function SendData(url)
{
var moreSubstantial = url + " ";
GM_xmlhttpRequest(
{
method: 'GET',
url: moreSubstantial,
should be enough.
Or, you can get the pages sequentially. Change parse() to something like:
function parse (details)
{
var element = $(details);
var coll = element.find("#my valid selector");
var TargetPages = coll.map (function() {return this.href;} );
(function getNextPage (J)
{
var PageURL = TargetPages[J];
GM_xmlhttpRequest
( {
method: "GET",
url: PageURL,
headers: {
'User-agent': 'Mozilla/4.0 (compatible) Greasemonkey',
'Accept': 'application/atom+xml,application/xml,text/xml',
},
onload: function (responseDetails)
{
doSomething (responseDetails.responseText);
if (--J >= 0)
getNextPage (J);
}
} );
} ) (TargetPages.length - 1);
}