I've got a web app which works perfectly in the browser - both on the desktop and mobile.
The problem comes when I try to pretty it up by adding:
<meta name="apple-mobile-web-app-capable" content="yes" />
This works great too - up to the point I need to delete a record in the app.
I'm also using this great gist I found - https://gist.github.com/1042167 to stop the app switching into mobile safari:
<script type="text/javascript">
(function(document,navigator,standalone) {
// prevents links from apps from oppening in mobile safari
// this javascript must be the first script in your <head>
if ((standalone in navigator) && navigator[standalone]) {
var curnode, location=document.location, stop=/^(a|html)$/i;
document.addEventListener('click', function(e) {
curnode=e.target;
while (!(stop).test(curnode.nodeName)) {
curnode=curnode.parentNode;
}
// Conditions to do this only on links to your own app
// if you want all links, use if('href' in curnode) instead.
if(
'href' in curnode && // is a link
(chref=curnode.href).replace(location.href,'').indexOf('#') && // is not an anchor
( !(/^[a-z\+\.\-]+:/i).test(chref) || // either does not have a proper scheme (relative links)
chref.indexOf(location.protocol+'//'+location.host)===0 ) // or is in the same protocol and domain
) {
e.preventDefault();
location.href = curnode.href;
}
},false);
}
})(document,window.navigator,'standalone');
</script>
I'm wondering if this could be altered so data-method="delete" will play nice? at the minute - when I click 'Delete' - the 'are you sure?' confirm box hangs for a second or two, before dumping me back on the same Show page, with no delete having occured...
So I'm going to assume from what you've written that you are using the (bundled) jquery-ujs to handle your delete links for you as that sounds like what you're doing from your mention of data-method
The way the ujs handlers work for delete links is different to what you might expect. Here's the relevant bit of the jquery-ujs source:
// Handles "data-method" on links such as:
// Delete
handleMethod: function(link) {
var href = rails.href(link),
method = link.data('method'),
target = link.attr('target'),
csrf_token = $('meta[name=csrf-token]').attr('content'),
csrf_param = $('meta[name=csrf-param]').attr('content'),
form = $('<form method="post" action="' + href + '"></form>'),
metadata_input = '<input name="_method" value="' + method + '" type="hidden" />';
if (csrf_param !== undefined && csrf_token !== undefined) {
metadata_input += '<input name="' + csrf_param + '" value="' + csrf_token + '" type="hidden" />';
}
if (target) { form.attr('target', target); }
form.hide().append(metadata_input).appendTo('body');
form.submit();
},
So you can see that's what actually happening is a form is being dynamically created (with the required csrf data) and then submitted.
Your delegated click handler method from the gist you link to isn't going to work in this situation, as the rails js does a return: false on click on links with a data-method attribute that prevents your handler from ever firing.
The simplest thing to do is probably to roll your own (ajax based) delete handler based on the ujs code. For brevity I'm using the excellent jQuery form plugin to handle the actual submission:
function handleDeleteLink(endpoint){
var confirmed = confirm("Are you sure you want to delete this record?"),
csrf_token = $('meta[name=csrf-token]').attr('content'),
csrf_param = $('meta[name=csrf-param]').attr('content'),
form = $('<form method="post" action="' + endpoint + '"></form>'),
metadata_input = '<input name="_method" value="delete" type="hidden" />',
deleteOptions = {
beforeSubmit: function(){
//handle ajax start
},
success: function(el){
//hand success
},
error: function(){
}
};
if (csrf_param !== undefined && csrf_token !== undefined) {
metadata_input += '<input name="' + csrf_param + '" value="' + csrf_token + '" type="hidden" />';
}
if (confirmed){
form.hide()
.append(metadata_input)
.appendTo('body')
.submit(function(e){
e.preventDefault();
$(this).ajaxSubmit(options);
})
.submit();
}
}
In your view just replace yourdata-method with a class of say delete-link and bind via event delegation (or use a different data attr if you like):
$('body').on('click', '.delete-link', function(e){
e.preventDefault();
handleDeleteLink(e.target.href);
}
The problem is location.href = curnode.href; changes requests into GET requests, although I've had inconsistent behavior with POST and PUT requests. So when you have some routes such as:
GET /photos index
POST /photos create
GET /photos/new new
GET /photos/:id/edit edit
GET /photos/:id show
PUT /photos/:id update
DELETE /photos/:id destroy
The update and destroy routes end up rerouted to show. My hacky (hopefully temporary) solution is to just make custom routes for those requests that are being rerouted. So you would add:
match "update" => "Photos#update", :as => :update_photo
match "destroy" => "Photos#destroy", :as => :destroy_photo
I know, I know, this is not Rails convention, but it should work.
Related
I'm working on an old piece of code in a software where there's a generic JavaScript (w/ some jQuery) utility function that calls an ASHX web-handler to download any type of file like excel, word, zip, csv, etc. (definition mentioned below). It dynamically creates an iframe & form element with the iframe target, submits the form, and removes the form element.
// existing function
downloadFile: function(url, method, formData) {
// method = by default 'POST' and can be 'GET', 'POST', ...
encUrl = encodeUrl(url);
downloadDiv = $('<div id="download-file" style="display: none;"></div>');
iframeId = getNewId();
src = isPost ? '' : 'src="' + encUrl + '"';
downloadDiv.html('<iframe name="' + iframeId + '" id="' + iframeId + '" ' + src + '></iframe>');
if(isPost) {
form = $('<form target="' + iframeId + '" method="post" action="' + encUrl + '"></form>').appendTo(document.body);
if (formData) {
for (p in formData) {
if (formData.hasOwnProperty(p)) {
$('<input type="hidden" />').attr("name", p).val(formData[p]).appendTo(form);
}
}
}
form.submit().remove();
}
}
At few places in the codebase from where this function is called, I have to delete the file after the user downloads it, so I need a callback to request the server. For this, I have converted this function to use an XHR. I seldom work with JS so this is the best I've been able to do so far. With the code below, the server writes the file in the response but I am not able to write it in the iframe so that the download file popup opens up in the application and the user downloads the file. Basically, I'm trying to imitate the form's target="iframeId" thing in JS.
// modified function
downloadFile: function(url, method, formData, downloadAsync, asyncSuccessCallback, asyncFailureCallback) {
// same as before, just changed the form submit part
if (submitAsync) {
downloadXhr = new XMLHttpRequest();
// is encoded URL required here or the normal one will do?
downloadXhr.open('POST', url);
downloadXhr.onloadend = () => {
var xhr = downloadXhr,
iframeTarget = downloadDiv.children().first().get(0);
form.remove();
iframeTarget.contentDocument.open();
iframeTarget.contentDocument.write(xhr.response);
iframeTarget.contentDocument.close();
}
if (asyncSuccessCallback) {
downloadXhr.onload = asyncSuccessCallback;
}
if (asyncFailureCallback) {
downloadXhr.onerror = asyncFailureCallback;
}
// needed? downloadXhr.responseType = 'blob';
downloadXhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
downloadXhr.send(new FormData(form.get(0)));
}
else {
form.submit().remove();
}
}
How can I write the response to the iframe so that the download file window opens in the browser?
Is this approach correct or something better can be done? Perhaps, AJAX could also be used here instead.
Please help, thanks.
I have this survey that stores to local storage. The user is prompted 'are you sure' once clicking submit. I'm trying to navigate to a confirmation HTML page(Confirmation.html) in my directory after user clicks 'ok' . But I'm not able to achieve both storing values and navigating to work. Can get any one only, it seems. Any help would be appreciated.
function clicked() {
if (confirm('Are you sure you want to submit? You will not be able to go back.')) {
form.submit();
} else {
return false;
}
}
$('form').submit(function () {
var person = $("#FirstName").val() + "." + $('#LastName').val();
$('input, select, textarea').each(function () {
var value = $(this).val(),
name = $(this).attr('name');
localStorage[person + "." + name] = value;
window.location.href = "Confirmation.html";
console.log('stored key: ' + name + ' stored value: ' + value);
});
});
<button type="submit" value="Save" id="Save" onclick="clicked();" >Submit Form</button>
If the above does not show my problem, here is the whole in jsfiddle
try this
<script>
$( document ).ready(function() {
$('#Save').click(function (e) {
if (confirm('Are you sure you want to submit? You will not be able to go back.')) {
var person = $("#FirstName").val() + "." + $('#LastName').val();
$('input, select, textarea').each(function () {
var value = $(this).val(),
name = $(this).attr('name');
localStorage[person + "." + name] = value;
window.location.href = "Confirmation.html";
console.log('stored key: ' + name + ' stored value: ' + value);
});
}
});
});
</script>
and remove onclick="clicked();" from button.
I am not sure why you need the confirmation.html page. Consider my opinions as follows:
1st: you are already asking the user for the confirmation giving him a messagebox. Thereafter you should only submit the form data (after any additional validation) to a server-side page (which I believe you have mentioned in the action value of the form).
2nd: If you still need the confirmation.html page then you should redirect to confirmation.html from that server-side page but not from your form page(the current page). Now it depends on the usage of confirmation.html that you should redirect to confirmation.html before/after feeding the form data into the database(or doing something else).
I've been working on some JavaScript for a little while, and one of the most frustrating things so far is that it seems my JavaScript is refusing to load if Back, Forward or Hard Refresh are used. Considering my website is built on static HTML pages generated repeatedly through a MySQL database connection, I had to manually add a moderator menu, but it seems to sometimes just refuse to appear?
if (getCookie('opt23') !== '1') {
var ajaxtwo = new XMLHttpRequest();
var wrap = '';
if (document.getElementById("firstpage") !== null) {
wrap = document.getElementById("firstpage");
} else {
wrap = document.getElementById("firstpageinside");
}
var main = document.createElement("div");
main.setAttribute("id","modmenu");
wrap.appendChild(main);
ajaxtwo.onreadystatechange = function() {
var reports = (ajaxtwo.responseText === '0' ? 'flagblue.png' : 'flag.png');
reportcount.innerHTML = '<img src=' + ku_cgipath + '/css/images/mods/' + reports + ' style="height:15px;' +
'width:15px;" />' + ajaxtwo.responseText;
};
ajaxtwo.open("GET",ku_cgipath + "/manage_page.php?action=reports&count",true);
ajaxtwo.send();
var threadid = document.getElementsByName("replythread");
// taking the initiative of togglePassword, this makes things less needlessly lengthy.
main.innerHTML =
'<h2>Quick Mod</h2>' +
'<input type="hidden" name="threadid" value="' + threadid[0].value +'" />' +
'<label for="modaction">Action </label><select id="action" name="modaction">' +
'<option value=""><none></option>' +
'<option value="delpost">Delete posts</option>' +
'<option value="rebuildone">Rebuild board</option>' +
'<option value="bans">View Bans</option>' +
'<option value="appeals">View Appeals</option>' +
'<option value="stickypost">Sticky Thread</option>' +
'<option value="unstickypost">Unsticky Thread</option>' +
'<option value=lockpost>Lock Thread</option>' +
'<option value="unlockpost">Unlock Thread</option>' +
'<option value="bump">Instant Bump</option>' +
'<option value="viewthread">Switch to Moderator View</option>' +
'</select>' +
'<input type="submit" name="submit" value="Submit">' +
'<a href="' + ku_cgipath + '/manage_page.php?action=reports" target="_blank">' +
'<span id="reportcount"></span></a> ';
}
My second issue is in another JavaScript that I'm using to try to make these HTML pages function dynamically. I'm using AJAX to interact with a PHP script, however, when it brings back the page (basically taking the next page and straps it next to the first one with CSS, creating a 'duo view'), my JavaScript doesn't get applied, like time settings, or generated links. How does one deal with that? I can't use window.onload as it can only be used once - it's being used in another JavaScript. Do I just somehow modify the results of the PHP file as they come? I'm using a Regular Expression to grab the next page, so I can't actually modify the results inside the PHP... can I?
Willing to concede to most solutions, including rewrites.
Answer to the First Question
This is a little JavaScript "problem" caused by the browser's Back-Forward cache! I've already answered a similar question, but for completeness, here's the solution:
Firefox and the iOS Safari are the only ones (as of this writing) known to have this issue. The solution is to hook into window.unload event, and a specific condition to reload the page inside window.onpageshow!
Firefox fix
jQuery:
$(window).unload(function () { $(window).unbind('unload'); });
JavaScript:
function UnloadHandler() { window.removeEventListener('unload', UnloadHandler, false); }
window.addEventListener('unload', UnloadHandler, false);
iOS Safari fix
jQuery:
$(window).bind('pageshow', function(event) {
if (event.originalEvent.persisted) {
window.location.reload()
}
});
JavaScript:
window.addEventListener('pageshow', function (event) {
if (event.persisted) {
window.location.reload()
}
}, false);
Answer to the Second Question
You can actually hook into the onload event with multiple scripts/multiple functions. Instead of using the overwriting form of hooking into an event, such as ajaxtwo.onreadystatechange - you should "add" an event listener.
Simple example would be:
ajaxtwo.addEventListener('readystatechange', function () {
alert("hi! I'm done!");
}, false);
As for a helper function that will "do this for you" with backwards compatibility with some older versions of Internet Explorer; you can use this simple function:
function AttachEventListener(element, event, handler) {
if (element.addEventListener) {
element.addEventListener(event, handler, false);
} else if (element.attachEvent) {
element.attachEvent('on' + event, handler);
} else {
alert('Invalid element specified for AttachEventListener');
}
}
Usage:
AttachEventListener(ajaxtwo, 'readystatechange', function () {
alert("hi! I'm using an awesome helper function!");
});
I'm trying to make a spotify app that takes in user information and sends it to an SQL database. However, I don't want this to be done using ajax since I want the submission of the information to move the user to a new page while the information is posted to the database in the background.
Here's my code so far:
function complete2() {
var name = document.getElementById("inputname").value;
var form = '<form action="http://site.net/dbconnect.php" method="post" style="display:none">' + '<input type="text" name="name" value="' + name + '">' + '</form>';
$('body').append(form);
$(form).submit();
}
</script>
</head>
<body>
<form id = "submitform" name = "submitform" action = "index.html" method = "POST" onsubmit = "complete2();" >
Name: <input type = "text" id = "inputname"> <br>
<input type = "submit" value = "Create">
</form>
</body>
</html>
So i know you dont want to use AJAX but if you want to load a differnt html file in your local app and Spotify doesnt respect a location header to an internal resource then youre stuck with ajax. However you can make it simialr to what would happen with a standard post something like:
$(function(){
$('#submitform').submit(function(e){
e.preventDefault();
$.post(
$(this).attr('action'),
{'inputname': $(this).find('#inputname').val()},
function(){ window.location.href = 'sp://yourapp/nextpage.html'; }
);
});
});
Here is a sample to get data
$.getJSON("http://www.exx.com/getjsondata.php", function (data) {
parsetheresponse(data) ;
}
Here is a sample to post data (still in JSON)
$.post("http://www.exx.com/postdataspotify.php", { albumname: "test", username: "test2" });
Hope it will help.
Don't forget to put http://www.exx.com in manifest file.
I am writing a website using Python / Django, and am currently doing server-side geocoding through the geopy module. I would like to implement a progressive enhancement of doing the geocoding via Javascript (to make sure I don't hit Google's daily limit of geocode requests / user), and then pass the results of the Javascript geocoding to the server for further processing.
What I am currently doing is using javascript and the Google geocoding API to get the (lat, lon) coordinates and add them as hidden inputs into my form. However, I am not sure how to submit this form with the (lat, lon) without running into an infinite loop. I would rather stay away from sending this via Ajax because for this particular page 99.9% of the page needs to be refreshed, and I would like to keep the design as simple as possible, so Ajax would be overkill.
I played around with putting the event.preventDefault() into an if statement testing for existence of the hidden lat/lon variables, but could not get it to work.
Any ideas? Thanks in advance! Vasiliy
$(document).ready(function() {
var g = new GoogleGeocode();
//bind event handler
$('#address_form').bind('submit', function(event){
//get address from form
var address = $('#id_address').val();
event.preventDefault();
//get lat/lon
g.geocode(address, function(data) {
if(data != null) {
alert(data.longitude + " " + data.latitude);
//insert lat and lon into the form
$('<input type="hidden" id="lat" name="lat" value="' + data.latitude + '"/>').appendTo('form');
$('<input type="hidden" id="lon" name="lon" value="' + data.longitude + '"/>').appendTo('form');
} else {
alert('ERROR! Unable to geocode address');
}
});
});
});
You should unbind the form submit event handler once you are sure you have to submit the form. Try this
$(document).ready(function() {
var g = new GoogleGeocode();
//bind event handler
$('#address_form').bind('submit', function(event){
//get address from form
var address = $('#id_address').val();
event.preventDefault();
//get lat/lon
g.geocode(address, function(data) {
if(data != null) {
alert(data.longitude + " " + data.latitude);
//insert lat and lon into the form
$('<input type="hidden" id="lat" name="lat" value="' + data.latitude + '"/>').appendTo('form');
$('<input type="hidden" id="lon" name="lon" value="' + data.longitude + '"/>').appendTo('form');
$('#address_form').unbind('submit').submit();
} else {
alert('ERROR! Unable to geocode address');
}
});
});
});
In the submit handler, check whether you already geocoded, and, if so, return. (not false).
If you return before doing anything, the browser will submit normally.
Alternatively, just unbind the handler after you geocode.
Try returning false at the end of the function