Good afternoon !
The idea behind my project is to send made-by-hand logs to a webpage so that I don't have to rely solely on the Serial Monitor.
I am sending Strings from ESP32 to a webpage that is hosted by the same ESP32.
To do that I am using ESPAsyncWebServer library that let's me use placeholders inside the HTML. The ESP then uses:
String processor(const String& var)
and
server.on("/logs", HTTP_GET, [](AsyncWebServerRequest* request){
request->send(SPIFFS, "/events.html", "text/html", false, processor);
});
to replace the placeholder it founds with whatever piece of data I want.
I am using a circular buffer so that I don't end up using all the memory.
I googled how should I refresh a div inside a html and everywhere I looked there was a .load('file.php'). I don't have any .php files for my project.
The question is what do I load instead of that php file ?
Loading the same .html page did not work.
events.html
<!DOCTYPE html>
<html>
<head>
<meta charset = "utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel = "stylesheet" type = "text/css" href = "master.css">
<title>Events Log</title>
<script type="text/javascript" src="jquery-1.9.0.min.js"></script>
<script>
$(document).ready( function(){
$('#big-box').load('events.html');
refresh();
});
function refresh()
{
setTimeout( function() {
$('#big-box').fadeOut('slow').load('events.html').fadeIn('slow');
refresh();
}, 2000);
}
</script>
</head>
<body>
<div id="big-box">
<div class="textbox">%PLACEHOLDER_1%</div>
<div class="textbox">%PLACEHOLDER_2%</div>
<div class="textbox">%PLACEHOLDER_3%</div>
<div class="textbox">%PLACEHOLDER_4%</div>
<div class="textbox">%PLACEHOLDER_5%</div>
</div>
</body>
main.cpp
#include <Arduino.h>
#include <ESPAsyncWebServer.h>
#include <SPIFFS.h>
#include <cstdio>
#include <memory>
#include <mutex>
void logOutput(String string);
//---------- Circular Buffer
template <class T>
class circular_buffer {
public:
explicit circular_buffer(size_t size) :
buf_(std::unique_ptr<T[]>(new T[size])),
max_size_(size)
{
}
void put(T item){
std::lock_guard<std::mutex> lock(mutex_);
buf_[head_] = item;
if(full_){
tail_ = (tail_ + 1) % max_size_;
}
head_ = (head_ + 1) % max_size_;
full_ = head_ == tail_;
}
T get() {
std::lock_guard<std::mutex> lock(mutex_);
if(empty()) {
return T();
}
//Read data and advance the tail (we now have a free space)
auto val = buf_[tail_];
full_ = false;
tail_ = (tail_ + 1) % max_size_;
return val;
}
T get2() {
std::lock_guard<std::mutex> lock(mutex_);
if(empty()) {
return T();
}
auto val = buf_[tail_];
return val;
}
void reset() {
std::lock_guard<std::mutex> lock(mutex_);
head_ = tail_;
full_ = false;
}
bool empty() const {
//if head and tail are equal, we are empty
return (!full_ && (head_ == tail_));
}
bool full() const {
//If tail is ahead the head by 1, we are full
return full_;
}
size_t capacity() const {
return max_size_;
}
size_t size() const {
size_t size = max_size_;
if(!full_) {
if(head_ >= tail_) {
size = head_ - tail_;
} else {
size = max_size_ + head_ - tail_;
}
}
return size;
}
private:
std::mutex mutex_;
std::unique_ptr<T[]> buf_;
size_t head_ = 0;
size_t tail_ = 0;
const size_t max_size_;
bool full_ = 0;
}; //---------- Circular Buffer
circular_buffer<String> circle(20);
void logOutput(String string) {
delay(2000);
circle.put(string);
Serial.println(string);
}
AsyncWebServer server(80);
const char* ssid = "metrici.ro";
const char* password = "cocoscocos";
String processor(const String& var) {
if(var == "PLACEHOLDER_1"){
return circle.get();
} else if(var == "PLACEHOLDER_2") {
return circle.get();
} else if(var == "PLACEHOLDER_3") {
return circle.get();
} else if(var == "PLACEHOLDER_4") {
return circle.get();
} else if(var == "PLACEHOLDER_5") {
return circle.get();
}
return String();
}
void setup() {
Serial.begin(115200);
delay(2000);
if(!SPIFFS.begin(true)) {
logOutput("ERROR ! SPIFFS file system was not mounted. Reformatting !");
}
WiFi.begin(ssid, password);
delay(1000);
int k = 0;
while(WiFi.status() != WL_CONNECTED && k<20) {
delay(1000);
k++;
logOutput("Connecting to WiFi");
}
if(WiFi.status() == WL_CONNECTED) {
logOutput((String)"Connected to: " + ssid + " with IP: " + WiFi.localIP().toString());
} else {
logOutput("Couldn't connect to WiFi ! Restarting in 5 seconds");
delay(5000);
ESP.restart();
}
server.on("/logs", HTTP_GET, [](AsyncWebServerRequest* request){
request->send(SPIFFS, "/events.html", "text/html", false, processor);
});
server.on("/master.css", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(SPIFFS, "/master.css", "text/css");
});
server.on("/back-image.jpg", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(SPIFFS, "/back-image.jpg", "image/jpeg");
});
server.on("/logo.png", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(SPIFFS, "/logo.png", "image/png");
});
server.begin();
delay(5000);
logOutput("After server.begin()");
for(int i = 1; i<=10;i++){
logOutput((String)"Linia " + i);
}
}
void loop() {
logOutput("Beginning the loop()");
logOutput("\n");
delay(10000);
}
TL;DR The ESP server doesn't serve the /jquery-1.9.0.min.js and /events.html files that the original HTML references. Read on for details.
When $('#big-box').load('events.html') code executes, the browser makes a HTTP GET call in background to your ESP server, at path /event.html. The requests on that path doesn't seem to be handled on ESP side. To handle requests on that path, you'd need something like this:
server.on('/event.html`, [](AsyncWebServerRequest *request) {
request->send(SPIFFS, "/events-log.html", "text/html", false, processor);
})`.
Additionally, you'd need to create events-log.html file with following content (basically everything that big-box div contained):
<div class="textbox">%PLACEHOLDER_1%</div>
<div class="textbox">%PLACEHOLDER_2%</div>
<div class="textbox">%PLACEHOLDER_3%</div>
<div class="textbox">%PLACEHOLDER_4%</div>
<div class="textbox">%PLACEHOLDER_5%</div>
Also note, that the jquery-1.9.0.min.js file that you included in your HTML file (using script tag) wouldn't be loaded for same reason. The browser will try to send a HTTP GET request to /jquery-1.9.0.min.js, and since request on that path isn't handled, browser will get 404 error.
If the computer on which you are accessing this page has internet access, then you can load jQuery from CDN by changing your script tag to following snippet. If the machine doesn't have internet access then you'd need to download the jQuery file, save it to SPIFFS, and serve it in same manner as the other files.
<script type="text/javascript" src="https://code.jquery.com/jquery-1.12.4.min.js"></script>
Related
I've been using Python and Selenium to try to automate an export process in Qualtrics. I have the web driver navigate to the login page, input my login credentials, and then navigate to the Data and Analytics page of the relevant survey. However, once I navigate to the Data and Analysis page, I'm having trouble selecting the element for the dropdown icon under the action header, which I need to click in order to export the responses.
I've tried to use this code (omitting the part where I login):
from selenium import webdriver
survey_url= {The Survey Url}
browser= webdriver.Chrome()
browser.get(survey_url)
browser.find_element_by_xpath("//div[#id='itemContext']").click()
However, I keep getting this error: NoSuchElementException: Message: no such element: Unable to locate element: {"method":"xpath","selector":"//div[#id='itemContext']"}
I've tried using both the absolute (/html/body/div[6]) and the relative (//*[#id="itemContext"]) xpath given to me by inspect element, but neither worked.
This is the HTML that I see for the element:
<div id="itemContext" ng-click="removeSelf()" qmenu items="recordActions" on-select="selectRecordAction()(value,rowId, recordIndex)" class="response-context ng-scope ng-isolate-scope" style="left:1315px;top:357px">
<span ng-transclude></span></div>
Here is the HTML for the webpage:
<html ng-app="responses-client">
<head>
<meta charset="utf-8">
<title>Data | Qualtrics Experience Management</title>
<script>
(function (window) {
'use strict';
/*
* This function is used to attempt to get the initial definition before
* angular is loaded. If this fails for some reason the definition will
* still be loaded, but not via this function. See fast-definition-service.
*/
function rejectError(data, status, statusText) {
data = data || {};
window.fastDefinitionState = 'ERROR';
window.fastDefinitionError = {
status: status || 500,
statusText: statusText || 'Internal Server Error',
data: data,
config: {
method: 'GET',
url: url
}
};
}
function handleResponse() {
var status = request.status;
var data;
try {
data = JSON.parse(request.responseText);
} catch (e) {
data = request.responseText;
}
function handleSuccess() {
window.returnedDefinition = { data };
window.fastDefinitionState = 'SUCCESS';
}
if (status >= 200 && status < 400) {
return handleSuccess();
} else {
return rejectError(data, status, request.statusText);
}
}
function attemptRequest() {
request.open('GET', url, true);
request.onload = handleResponse; // Recieved response from server
request.onerror = rejectError; // Connection to server error
request.send();
window.fastDefinitionState = 'PENDING';
}
var surveyIdRegex = /surveys\/(SV_[a-zA-Z0-9]{15})/;
var canonicalId = (surveyIdRegex.exec(window.location.hash) || [])[1];
var fieldsetIdRegex = /fieldsets\/([\w-]+)/;
var fieldsetId = (fieldsetIdRegex.exec(window.location.hash) || [])[1];
var conjointIdRegex = /surveys\/SV_[a-zA-Z0-9]{15}\?conjointProjectId=(.+)/;
var conjointId = (conjointIdRegex.exec(window.location.hash) || [])[1];
var idpProjectRegex = /fieldsets\/[\w-]+\?(?:projectId|idpProjectId)=(IDP_.+|PROJ_.+)/;
var idpProjectId = (idpProjectRegex.exec(window.location.hash) || [])[1];
var resourceIdRegex = /surveys\/SV_[a-zA-Z0-9]{15}\?resourceId=(.+)/;
var resourceId = (resourceIdRegex.exec(window.location.hash) || [])[1];
var projectIdRegex = /surveys\/SV_[a-zA-Z0-9]{15}\?projectId=(.+)/;
var projectId = (projectIdRegex.exec(window.location.hash) || [])[1];
var url;
if (canonicalId) {
url = '/responses/surveys/' + canonicalId;
} else if (fieldsetId) {
url = '/responses/fieldsets/' + fieldsetId;
} else {
return rejectError(new Error('Missing surveyId for fast definition'));
}
if (conjointId) {
url += '?optProjectId=' + conjointId;
} else if (idpProjectId) {
url += '?idpProjectId=' + idpProjectId;
} else if (resourceId) {
url += '?resourceId=' + resourceId;
} else if (projectId) {
url += '?projectId=' + projectId;
}
window.fastDefinitionUrl = url;
var request = new XMLHttpRequest();
return attemptRequest();
})(window);
</script>
<link rel="icon" href="/brand-management/favicon">
<link rel="apple-touch-icon-precomposed" href="/brand-management/apple-touch-icon">
<link href="/responses/static/vendor/vendor.min.a113b2563.css" rel="stylesheet" type="text/css">
<link href="/responses/static/css/responsesclient.min.a49de54f5.css" rel="stylesheet" type="text/css">
</head>
<body ng-click="checkMenu()" ng-controller="MainController" keep-alive style="margin:0px;">
<div class="dp-container">
<div ng-view></div>
<div q-footer user-id="userId" language="userLanguage"></div>
</div>
</body>
<script src="/responses/static/vendor/vendor.min.a4f381856.js"></script>
<script src="/responses/static/js/responsesclient.min.ad8d40058.js"></script>
<script src="/wrapper/static/client.js"></script>
<script type="text/javascript" src="/project-workflow/static/bundle.js"></script>
<script type="text/javascript" src="https://static-assets.qualtrics.com/static/expert-review-ui/prod/response-iq.js"></script>
<script>
window.Qualtrics = {"User":{"brandId":"mindshare";
function hackAFixForProjectFramework() {
var surveyIdRegex = /surveys\/(SV_[a-zA-Z0-9]{15})/;
var fieldsetIdRegex = /fieldsets\/([\w-]+)/;
var canonicalId = (surveyIdRegex.exec(window.location.hash) || [])[1];
var fieldsetId = (fieldsetIdRegex.exec(window.location.hash) || [])[1];
window.Qualtrics.surveyId = canonicalId || fieldsetId;
}
// Hack a fix for updating to jquery 3.5 breaking project framework as they assumed it was ready sooner with jquery 3.5. Couldn't discover why, but it's somewhere in their code base.
hackAFixForProjectFramework();
var user = _.get(window, 'Qualtrics.User', {}),
requiredUserKeys = [
'userId',
'brandId',
'brandName',
'brandType',
'language',
'email',
'userName',
'firstName',
'lastName',
'accountType',
'accountCreationDate'
];
angular.module('responses-client').config([
'rsw.userServiceProvider',
function(userServiceProvider) {
try {
userServiceProvider.setUser(_.pick(user, requiredUserKeys));
} catch (e) {
console.log(e);
}
}
]);
</script>
</html>
I suspect the problem may be that the website is using a lot of javascript to dynamically change the responses. Above the HTML element I showed above, there are a bunch of "<'script type...'>" and src programs that I don't really understand, though I can guess from the names that they are handling the display of responses. Thinking it might be a loading problem, I've also tried making the program wait before searching for the element, but that didn't work either.
Cheers!
I found interesting code on stack overflow, but the only thing I don't like about it is that it uses JQuery that is imported via the Internet, and I need it all to work without connecting to the Internet. Can you please tell me how this can be changed?
Code:
void handleRoot() {
snprintf ( htmlResponse, 3000,
"<!DOCTYPE html>\
<html lang=\"en\">\
<head>\
<meta charset=\"utf-8\">\
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\
</head>\
<body>\
<h1>Time</h1>\
<input type='text' name='date_hh' id='date_hh' size=2 autofocus> hh \
<div>\
<br><button id=\"save_button\">Save</button>\
</div>\
<script src=\"https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js\"></script>\
<script>\
var hh;\
$('#save_button').click(function(e){\
e.preventDefault();\
hh = $('#date_hh').val();\
$.get('/save?hh=' + hh , function(data){\
console.log(data);\
});\
});\
</script>\
</body>\
</html>");
server.send ( 200, "text/html", htmlResponse );
}
void handleSave() {
if (server.arg("hh")!= ""){
Serial.println("Hours: " + server.arg("hh"));
}
}
void setup() {
// Start serial
Serial.begin(115200);
delay(10);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
server.on ( "/", handleRoot );
server.on ("/save", handleSave);
server.begin();
}
void loop() {
server.handleClient();
}
The minified jquery javascript can be stored on the ESP and served up by the module when the browser requests it.
One easy way to do this is to use the SPI Flash File System to serve up the HTML as well as the JQuery javascript.
This means creating an index.html in a data sub-directory in the sketch. Add the HTML in the original sketch into the file. Also change the script source in this file to a relative path:
<script src="jquery.min.js"></script>
Then download jquery.min.js and copy this into the data sub-directory as well.
The example code at https://tttapa.github.io/ESP8266/Chap11%20-%20SPIFFS.html can be used as a starting point for the rest of the code. The main parts of this involve initializing SPIFFS and setting up the handler for the file request:
SPIFFS.begin();
server.onNotFound([]() {
if (!handleFileRead(server.uri()))
server.send(404, "text/plain", "404: Not Found");
});
// retain the save endpoint
server.on("/save", handleSave);
server.begin();
Then implement the file handler and its content type handler:
String getContentType(String filename)
{
if (filename.endsWith(".html")) return "text/html";
else if (filename.endsWith(".css")) return "text/css";
else if (filename.endsWith(".js")) return "application/javascript";
else if (filename.endsWith(".ico")) return "image/x-icon";
return "text/plain";
}
bool handleFileRead(String path) {
Serial.println("handleFileRead: " + path);
if (path.endsWith("/"))
{
path += "index.html";
}
String contentType = getContentType(path);
if (SPIFFS.exists(path))
{
File file = SPIFFS.open(path, "r");
size_t sent = server.streamFile(file, contentType);
file.close();
return true;
}
Serial.println("\tFile Not Found");
return false;
}
Alternate Approach: Remove the JQuery dependency
An alternative approach is to rewrite the javascript so that JQuery is not required.
This involves registering an onclick handler on the button (https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Event_handlers), getting the value from the input field (https://stackoverflow.com/a/11563667/1373856) and sending a GET request (https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/send)
You just has to include it as script-tag with the local path on your machine.
<script src="path-to-jquery/jquery.min.js" type="text/javascript"></script>
Edit: First you have to download the needed jquery file and store it in your local path. The needed path-to-jquery should than be a relative path from the html-file to the jquery.
I am trying to send an image by drag and drop in send message area but its failing to do so. I dont know how to send an image in this code. Can someone please help me out?
ChatEndpoint.java
This is serverendpoint of websocket.I have four methods.onOpen,onClose,onMessage and onError.I am not doing anything in onOpen method.This method gets called when websocket establishes the connection.When the websocket tries to send message to the client,onMessage method gets called.Here the message sent from the jsp page(client) sends it in json string format to the server.The object mapper converts it to java object.I have also made an enum for MessageType having two options LOGIN and MESSAGE.I am checking inf the java object matches with the login message type then it stores the name of chat user from properties of user obtained from session object.It also then sends the session object to join method where the session is added to the list of session object.It also send the message the user has joined the chat room.If messagetype is a message then it sends it to send message method of room class where the message is sent back to the client using sendText.
#ServerEndpoint(value = "/chat")
public class ChatEndpoint {
private Logger log = Logger.getLogger(ChatEndpoint.class.getSimpleName());
private Room room = Room.getRoom();
#OnOpen
public void onOpen(final Session session, EndpointConfig config) {}
#OnMessage
public void onMessage(final Session session, final String messageJson) {
ObjectMapper mapper = new ObjectMapper();
ChatMessage chatMessage = null;
try {
chatMessage = mapper.readValue(messageJson, ChatMessage.class);
} catch (IOException e) {
String message = "Badly formatted message";
try {
session.close(new CloseReason(CloseReason.CloseCodes.CANNOT_ACCEPT, message));
} catch (IOException ex) { log.severe(ex.getMessage()); }
} ;
Map<String, Object> properties = session.getUserProperties();
if (chatMessage.getMessageType() == MessageType.LOGIN) {
String name = chatMessage.getMessage();
properties.put("name", name);
room.join(session);
room.sendMessage(name + " - Joined the chat room");
}
else {
String name = (String)properties.get("name");
room.sendMessage(name + " - " + chatMessage.getMessage());
}
}
#OnClose
public void OnClose(Session session, CloseReason reason) {
room.leave(session);
room.sendMessage((String)session.getUserProperties().get("name") + " - Left the room");
}
#OnError
public void onError(Session session, Throwable ex) { log.info("Error: " + ex.getMessage()); }
}
Room.java
public class Room {
private static Room instance = null;
private List<Session> sessions = new ArrayList<Session>();
public synchronized void join(Session session) { sessions.add(session); }
public synchronized void leave(Session session) { sessions.remove(session); }
public synchronized void sendMessage(String message) {
for (Session session: sessions) {
if (session.isOpen()) {
try { session.getBasicRemote().sendText(message); }
catch (IOException e) { e.printStackTrace(); }
}
}
}
public synchronized static Room getRoom() {
if (instance == null) { instance = new Room(); }
return instance;
}
}
index.jsp
Here i am sending the wsUri which is stored in context param as ws://localhost:8080/single-room-chat/chat
<%# page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<% String WsUrl = getServletContext().getInitParameter("WsUrl"); %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name='viewport' content='minimum-scale=1.0, initial-scale=1.0,
width=device-width, maximum-scale=1.0, user-scalable=no'/>
<title>single-room-chat</title>
<link rel="stylesheet" type="text/css" href="content/styles/site.css">
<script type="text/javascript" src="scripts/chatroom.js"></script>
<script type="text/javascript">
var wsUri = '<%=WsUrl%>';
var proxy = CreateProxy(wsUri);
document.addEventListener("DOMContentLoaded", function(event) {
console.log(document.getElementById('loginPanel'));
proxy.initiate({
loginPanel: document.getElementById('loginPanel'),
msgPanel: document.getElementById('msgPanel'),
txtMsg: document.getElementById('txtMsg'),
txtLogin: document.getElementById('txtLogin'),
msgContainer: document.getElementById('msgContainer')
});
});
</script>
</head>
<body>
<div id="container">
<div id="loginPanel">
<div id="infoLabel">Type a name to join the room</div>
<div style="padding: 10px;">
<input id="txtLogin" type="text" class="loginInput"
onkeyup="proxy.login_keyup(event)" />
<button type="button" class="loginInput" onclick="proxy.login()">Login</button>
</div>
</div>
<div id="msgPanel" style="display: none">
<div id="msgContainer" style="overflow: auto;"></div>
<div id="msgController">
<textarea id="txtMsg"
title="Enter to send message"
onkeyup="proxy.sendMessage_keyup(event)"
style="height: 20px; width: 100%"></textarea>
<button style="height: 30px; width: 100px" type="button"
onclick="proxy.logout()">Logout</button>
</div>
</div>
</div>
</body>
</html>
chatroom.js
var CreateProxy = function(wsUri) {
var websocket = null;
var audio = null;
var elements = null;
var playSound = function() {
if (audio == null) {
audio = new Audio('content/sounds/beep.wav');
}
audio.play();
};
var showMsgPanel = function() {
elements.loginPanel.style.display = "none";
elements.msgPanel.style.display = "block";
elements.txtMsg.focus();
};
var hideMsgPanel = function() {
elements.loginPanel.style.display = "block";
elements.msgPanel.style.display = "none";
elements.txtLogin.focus();
};
var displayMessage = function(msg) {
if (elements.msgContainer.childNodes.length == 100) {
elements.msgContainer.removeChild(elements.msgContainer.childNodes[0]);
}
var div = document.createElement('div');
div.className = 'msgrow';
var textnode = document.createTextNode(msg);
div.appendChild(textnode);
elements.msgContainer.appendChild(div);
elements.msgContainer.scrollTop = elements.msgContainer.scrollHeight;
};
var clearMessage = function() {
elements.msgContainer.innerHTML = '';
};
return {
login: function() {
elements.txtLogin.focus();
var name = elements.txtLogin.value.trim();
if (name == '') { return; }
elements.txtLogin.value = '';
// Initiate the socket and set up the events
if (websocket == null) {
websocket = new WebSocket(wsUri);
websocket.onopen = function() {
var message = { messageType: 'LOGIN', message: name };
websocket.send(JSON.stringify(message));
};
websocket.onmessage = function(e) {
displayMessage(e.data);
showMsgPanel();
playSound();
};
websocket.onerror = function(e) {};
websocket.onclose = function(e) {
websocket = null;
clearMessage();
hideMsgPanel();
};
}
},
sendMessage: function() {
elements.txtMsg.focus();
if (websocket != null && websocket.readyState == 1) {
var input = elements.txtMsg.value.trim();
if (input == '') { return; }
elements.txtMsg.value = '';
var message = { messageType: 'MESSAGE', message: input };
// Send a message through the web-socket
websocket.send(JSON.stringify(message));
}
},
login_keyup: function(e) { if (e.keyCode == 13) { this.login(); } },
sendMessage_keyup: function(e) { if (e.keyCode == 13) { this.sendMessage(); } },
logout: function() {
if (websocket != null && websocket.readyState == 1) { websocket.close();}
},
initiate: function(e) {
elements = e;
elements.txtLogin.focus();
}
}
};
In Microsoft Visual Studio Express I have started a new project using the "Windows Phone HTML5 App" template. If I run the emulator, everything works fine. Next I added the following JavaScript to the index.html page:
<script type="text/javascript">
window.onload = function(){
alert(window.location.href); // --> x-wmapp0:/Html/index.html
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange=function()
{
alert('ON READY STATE CHANGE');
if(xmlhttp.readyState==4){
alert(xmlhttp.responseText);
}
}
//xmlhttp.open("GET","text.txt",true); // I have tried all of these
//xmlhttp.open("GET","Html/text.txt",true);
//xmlhttp.open("GET","/Html/text.txt",true);
xmlhttp.open("GET","x-wmapp0:/Html/text.txt",true);
xmlhttp.send();
}
</script>
Now when I run the app in the emulator I get the first alert with the window location, but do not get any alerts from the readyState or onreadystatechange. The text.txt file is on the same level as the index.html. I have run this code in IE10 and it works just fine. Any ideas on what I am doing wrong?
Update: I have deployed this on an actual Windows 8 phone and got the same result
Cheers
Here is what Microsoft told me from MSDN
XMLHttpRequest only works for retrieving network resources. i.e. You cannot use it to access content from your applications local storage, i.e. XAP or IsolatedStorage.
Here is an example of script + code which I have used in the past to work around this limitation:
HTML Page with JavaScript:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>test</title>
<script type="text/javascript">
function LoadFile(SourceURL) {
try {
var httpfreq = new XMLHttpRequest();
httpfreq.onreadystatechange = function () {
filecontent.innerText = "httpfreq.onreadystatechange fired, readyState = " + httpfreq.readyState.toString();
if (httpfreq.readyState = 4) {
filecontent.innerText = "Status = " + httpfreq.status.toString();
if (httpfreq.status = 200) {
window.external.notify("Received content" + httpfreq.responseText);
filecontent.innerHTML = httpfreq.responseText;
}
else {
window.external.notify("Error loading page: " + SourceURL);
filecontent.innerText = "Error loading page " + SourceURL;
}
}
};
httpfreq.open("GET", SourceURL);
httpfreq.send(null);
}
catch (e) {
if (e.number = 0x80070005) {
LoadLocalFile(SourceURL, "GetResourceCallback");
}
else {
alert(e.name + " " + e.number.toString());
}
}
}
function LoadLocalFile(SourceURL, callbackfn) {
window.external.notify("GetResource?file=" + SourceURL + ";callback=" + callbackfn);
}
function GetResourceCallback(StringContent) {
filecontent.innerText = StringContent;
}
</script>
</head>
<body>
<p>
test page: notes.html
</p>
<p><input type="button" onclick="LoadFile('text.txt')" value="Load Local" /> </p>
<p><input type="button" onclick="LoadFile('http://www.somedomain.com/text.txt')" value="Load remote" /> </p>
<p>---------------------------</p>
<div id="filecontent"></div>
<p>---------------------------</p>
</body>
</html>
And the required App Host code (c#)
private void webBrowser1_ScriptNotify(object sender, NotifyEventArgs e)
{
System.Diagnostics.Debug.WriteLine("Script Notify : {0}",e.Value);
if (e.Value.Contains("GetResource?file="))
{
Dispatcher.BeginInvoke(() =>
{
String szArgs = e.Value;
string szResource = null;
string szCallbackFn = null;
char[] separators = new char[2] {'?',';'};
string[] parms = szArgs.Split(separators);
for (int i = 1; i < parms.Length; i++ )
{
if (parms[i].Contains("file="))
{
szResource = parms[i].Substring(5);
}
else if (parms[i].Contains("callback="))
{
szCallbackFn = parms[i].Substring(9);
}
}
if (!String.IsNullOrWhiteSpace(szResource) && !String.IsNullOrWhiteSpace(szCallbackFn))
{
// read local resource.
string szFileContent= "Resource not found!";
try
{
if (String.IsNullOrEmpty(webBrowser1.Base))
{
// if Base is not set then assume XAP file content.
szFileContent = ReadXAPResource(szResource);
}
else
{
// else assume IsolatedStorage
szFileContent = ReadISOFile(webBrowser1.Base, szResource);
}
}
catch (Exception)
{}
webBrowser1.InvokeScript(szCallbackFn, szFileContent);
}
});
}
}
private string ReadXAPResource(string szFile)
{
string szContent = "File Not Found";
try
{
// in my project HTML files are in the HelpContent folder...
StringBuilder szPath = new StringBuilder("HelpContent");
if (!szFile.StartsWith("/"))
szPath.Append("/");
szPath.Append(szFile);
StreamResourceInfo sri = Application.GetResourceStream(new Uri(szPath.ToString(), UriKind.Relative));
if (null != sri)
{
StreamReader strm = new StreamReader(sri.Stream);
szContent = strm.ReadToEnd();
}
}
catch (Exception) { }
return szContent;
}
private string ReadISOFile(string szBase, string szFile)
{
string szContent = "File Not Found";
try
{
string fullPath = szBase + szFile;
IsolatedStorageFile isf = IsolatedStorageFile.GetUserStoreForApplication();
IsolatedStorageFileStream isfsInput = isf.OpenFile(fullPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
if (null != isfsInput)
{
StreamReader strm = new StreamReader(isfsInput);
szContent = strm.ReadToEnd();
}
}
catch (Exception) { }
return szContent;
}
I'm trying to debug an asynchronous file uploader that I built some time ago which is no longer working, I've spent already a good deal of time without success.
The stream that the server is receiving is always corrupted in fact the file (image) that I save cannot be opened.
To simplify debugging I have setup a brand new ASP.NET project, with two main files, the HTML file with the form field and the ASP.NET handler.
Despite the code here being very trivial, I'm still out of luck! :(
Any help is highly appreciated, many thanks!
<!DOCTYPE html>
<html>
<head>
<title>Upload Files using XMLHttpRequest - Minimal</title>
<script type="text/javascript">
function uploadFile() {
var fd = new FormData();
fd.append("fileToUpload", document.getElementById('fileToUpload').files[0]);
var xhr = new XMLHttpRequest();
xhr.addEventListener("load", uploadComplete, false);
xhr.addEventListener("error", uploadFailed, false);
xhr.addEventListener("abort", uploadCanceled, false);
xhr.open("POST", "Handler1.ashx");
xhr.send(fd);
}
function uploadComplete(evt) {
/* This event is raised when the server send back a response */
alert(evt.target.responseText);
}
function uploadFailed(evt) {
alert("There was an error attempting to upload the file.");
}
function uploadCanceled(evt) {
alert("The upload has been canceled by the user or the browser dropped the connection.");
}
</script>
</head>
<body>
<form id="form1" enctype="multipart/form-data" method="post" action="Handler1.ashx">
<input type="file" name="fileToUpload" id="fileToUpload"/>
<input type="button" onclick="uploadFile()" value="Upload" />
</form>
</body>
</html>
and here is the ashx handler:
using System;
using System.Collections.Generic;
using System.Web.Extensions;
using System.Linq;
using System.Web;
using System.Web.Services;
using System.IO;
namespace MultipleFileUploadTest
{
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class Handler1 : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
var stream = context.Request.InputStream;
MemoryStream memoryStream;
ReadFully(stream, out memoryStream);
Byte[] ba = memoryStream.ToArray();
var path = #"C:\Users\giuseppe.JHP\Desktop\Image upload test\uploaded.gif";
using (FileStream fs = new FileStream(path, FileMode.OpenOrCreate))
{
fs.Write(ba, 0, ba.Length);
}
//DEBUGGING CODE
//I'm opening the same file that was originally picked by the input form field and I'm now comparing the original file with the one received within the context stream. They always differ!
Byte[] ba2 = File.ReadAllBytes(#"C:\Users\giuseppe.JHP\Desktop\Image upload test\a.gif");
//equal evaluates always to false
bool equal = ba.Length == ba2.Length;
if (equal)
{
for (var i = 0; i < ba2.Length; i++)
{
if (ba[i] != ba2[i])
{
equal = false;
i = ba2.Length;
}
}
}
//equal is always false
//if (!equal)
//{
// throw Exception("Stream is not valid");
//}
//The code below will throw a Parameter is invalid exception
//System.Drawing.Image mediaObject = System.Drawing.Image.FromStream(memoryStream);
memoryStream.Close();
}
public static void ReadFully(Stream input, out MemoryStream ms)
{
ms = new MemoryStream();
byte[] buffer = new byte[16 * 1024];
int read;
while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
{
ms.Write(buffer, 0, read);
}
}
public bool IsReusable
{
get
{
return false;
}
}
}
}
In case it helped someone else, I've got the code to work, here is what it is changed:
public void ProcessRequest(HttpContext context)
{
if (context.Request.Files != null && context.Request.Files.Count > 0)
{
var file = context.Request.Files[0];
file.SaveAs(#"C:\Users\giuseppe.JHP\Desktop\Image upload test\uploaded.gif");
}
}