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.
Related
I have an asp.net application, where I need to add a script in the section of my html, and a value in that script needs to change based on the environment (TEST, QA etc..). For simplicity lets just say this is my script, where DisplayValue is the value that needs to be dynamic:
<head>
<script>
alert("Hello World! Value='" + DisplayValue + "'");
</script>
</head>
This is an asp.net application, but there is no corresponding .aspx file, just plain old html. Is it possible to read the value from the web.config? Some other way I haven't thought of?
My solution was to create a separate script file for each environment. Then, based off the url of the page, determine which environment I am running in and dynamically load the correct script.
<script>
function loadJS(url, async = true) {
let scriptElement = document.createElement("script");
scriptElement.setAttribute("src", url);
scriptElement.setAttribute("type", "text/javascript");
scriptElement.setAttribute("async", async);
document.head.appendChild(scriptElement);
//success
scriptElement.addEventListener("load", () => {
console.log("Script file loaded.")
});
//error
scriptElement.addEventListener("error", (ev) => {
console.log("Error loading script file", ev);
});
}
var url = window.location.href.toUpperCase();
if (url.indexOf("WWWQA.") >= 0) {
//Load script for QA environment
loadJS("Scripts/QA-SCRIPT.js", true);
}
else if (url.indexOf("WWW.") >= 0) {
//Load script for PROD environment
loadJS("Scripts/PROD-SCRIPT.js", true);
}
</script>
I'm making a web app that requires that I check to see if remote servers are online or not. When I run it from the command line, my page load goes up to a full 60s (for 8 entries, it will scale linearly with more).
I decided to go the route of pinging on the user's end. This way, I can load the page and just have them wait for the "server is online" data while browsing my content.
If anyone has the answer to the above question, or if they know a solution to keep my page loads fast, I'd definitely appreciate it.
I have found someone that accomplishes this with a very clever usage of the native Image object.
From their source, this is the main function (it has dependences on other parts of the source but you get the idea).
function Pinger_ping(ip, callback) {
if(!this.inUse) {
this.inUse = true;
this.callback = callback
this.ip = ip;
var _that = this;
this.img = new Image();
this.img.onload = function() {_that.good();};
this.img.onerror = function() {_that.good();};
this.start = new Date().getTime();
this.img.src = "http://" + ip;
this.timer = setTimeout(function() { _that.bad();}, 1500);
}
}
This works on all types of servers that I've tested (web servers, ftp servers, and game servers). It also works with ports. If anyone encounters a use case that fails, please post in the comments and I will update my answer.
Update: Previous link has been removed. If anyone finds or implements the above, please comment and I'll add it into the answer.
Update 2: #trante was nice enough to provide a jsFiddle.
http://jsfiddle.net/GSSCD/203/
Update 3: #Jonathon created a GitHub repo with the implementation.
https://github.com/jdfreder/pingjs
Update 4: It looks as if this implementation is no longer reliable. People are also reporting that Chrome no longer supports it all, throwing a net::ERR_NAME_NOT_RESOLVED error. If someone can verify an alternate solution I will put that as the accepted answer.
Ping is ICMP, but if there is any open TCP port on the remote server it could be achieved like this:
function ping(host, port, pong) {
var started = new Date().getTime();
var http = new XMLHttpRequest();
http.open("GET", "http://" + host + ":" + port, /*async*/true);
http.onreadystatechange = function() {
if (http.readyState == 4) {
var ended = new Date().getTime();
var milliseconds = ended - started;
if (pong != null) {
pong(milliseconds);
}
}
};
try {
http.send(null);
} catch(exception) {
// this is expected
}
}
you can try this:
put ping.html on the server with or without any content, on the javascript do same as below:
<script>
function ping(){
$.ajax({
url: 'ping.html',
success: function(result){
alert('reply');
},
error: function(result){
alert('timeout/error');
}
});
}
</script>
You can't directly "ping" in javascript.
There may be a few other ways:
Ajax
Using a java applet with isReachable
Writing a serverside script which pings and using AJAX to communicate to your serversidescript
You might also be able to ping in flash (actionscript)
You can't do regular ping in browser Javascript, but you can find out if remote server is alive by for example loading an image from the remote server. If loading fails -> server down.
You can even calculate the loading time by using onload-event. Here's an example how to use onload event.
Pitching in with a websocket solution...
function ping(ip, isUp, isDown) {
var ws = new WebSocket("ws://" + ip);
ws.onerror = function(e){
isUp();
ws = null;
};
setTimeout(function() {
if(ws != null) {
ws.close();
ws = null;
isDown();
}
},2000);
}
Update: this solution does not work anymore on major browsers, since the onerror callback is executed even if the host is a non-existent IP address.
To keep your requests fast, cache the server side results of the ping and update the ping file or database every couple of minutes(or however accurate you want it to be). You can use cron to run a shell command with your 8 pings and write the output into a file, the webserver will include this file into your view.
The problem with standard pings is they're ICMP, which a lot of places don't let through for security and traffic reasons. That might explain the failure.
Ruby prior to 1.9 had a TCP-based ping.rb, which will run with Ruby 1.9+. All you have to do is copy it from the 1.8.7 installation to somewhere else. I just confirmed that it would run by pinging my home router.
There are many crazy answers here and especially about CORS -
You could do an http HEAD request (like GET but without payload).
See https://ochronus.com/http-head-request-good-uses/
It does NOT need a preflight check, the confusion is because of an old version of the specification, see
Why does a cross-origin HEAD request need a preflight check?
So you could use the answer above which is using the jQuery library (didn't say it) but with
type: 'HEAD'
--->
<script>
function ping(){
$.ajax({
url: 'ping.html',
type: 'HEAD',
success: function(result){
alert('reply');
},
error: function(result){
alert('timeout/error');
}
});
}
</script>
Off course you can also use vanilla js or dojo or whatever ...
If what you are trying to see is whether the server "exists", you can use the following:
function isValidURL(url) {
var encodedURL = encodeURIComponent(url);
var isValid = false;
$.ajax({
url: "http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20html%20where%20url%3D%22" + encodedURL + "%22&format=json",
type: "get",
async: false,
dataType: "json",
success: function(data) {
isValid = data.query.results != null;
},
error: function(){
isValid = false;
}
});
return isValid;
}
This will return a true/false indication whether the server exists.
If you want response time, a slight modification will do:
function ping(url) {
var encodedURL = encodeURIComponent(url);
var startDate = new Date();
var endDate = null;
$.ajax({
url: "http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20html%20where%20url%3D%22" + encodedURL + "%22&format=json",
type: "get",
async: false,
dataType: "json",
success: function(data) {
if (data.query.results != null) {
endDate = new Date();
} else {
endDate = null;
}
},
error: function(){
endDate = null;
}
});
if (endDate == null) {
throw "Not responsive...";
}
return endDate.getTime() - startDate.getTime();
}
The usage is then trivial:
var isValid = isValidURL("http://example.com");
alert(isValid ? "Valid URL!!!" : "Damn...");
Or:
var responseInMillis = ping("example.com");
alert(responseInMillis);
const ping = (url, timeout = 6000) => {
return new Promise((resolve, reject) => {
const urlRule = new RegExp('(https?|ftp|file)://[-A-Za-z0-9+&##/%?=~_|!:,.;]+[-A-Za-z0-9+&##/%=~_|]');
if (!urlRule.test(url)) reject('invalid url');
try {
fetch(url)
.then(() => resolve(true))
.catch(() => resolve(false));
setTimeout(() => {
resolve(false);
}, timeout);
} catch (e) {
reject(e);
}
});
};
use like this:
ping('https://stackoverflow.com/')
.then(res=>console.log(res))
.catch(e=>console.log(e))
I don't know what version of Ruby you're running, but have you tried implementing ping for ruby instead of javascript? http://raa.ruby-lang.org/project/net-ping/
let webSite = 'https://google.com/'
https.get(webSite, function (res) {
// If you get here, you have a response.
// If you want, you can check the status code here to verify that it's `200` or some other `2xx`.
console.log(webSite + ' ' + res.statusCode)
}).on('error', function(e) {
// Here, an error occurred. Check `e` for the error.
console.log(e.code)
});;
if you run this with node it would console log 200 as long as google is not down.
You can run the DOS ping.exe command from javaScript using the folowing:
function ping(ip)
{
var input = "";
var WshShell = new ActiveXObject("WScript.Shell");
var oExec = WshShell.Exec("c:/windows/system32/ping.exe " + ip);
while (!oExec.StdOut.AtEndOfStream)
{
input += oExec.StdOut.ReadLine() + "<br />";
}
return input;
}
Is this what was asked for, or am i missing something?
just replace
file_get_contents
with
$ip = $_SERVER['xxx.xxx.xxx.xxx'];
exec("ping -n 4 $ip 2>&1", $output, $retval);
if ($retval != 0) {
echo "no!";
}
else{
echo "yes!";
}
It might be a lot easier than all that. If you want your page to load then check on the availability or content of some foreign page to trigger other web page activity, you could do it using only javascript and php like this.
yourpage.php
<?php
if (isset($_GET['urlget'])){
if ($_GET['urlget']!=''){
$foreignpage= file_get_contents('http://www.foreignpage.html');
// you could also use curl for more fancy internet queries or if http wrappers aren't active in your php.ini
// parse $foreignpage for data that indicates your page should proceed
echo $foreignpage; // or a portion of it as you parsed
exit(); // this is very important otherwise you'll get the contents of your own page returned back to you on each call
}
}
?>
<html>
mypage html content
...
<script>
var stopmelater= setInterval("getforeignurl('?urlget=doesntmatter')", 2000);
function getforeignurl(url){
var handle= browserspec();
handle.open('GET', url, false);
handle.send();
var returnedPageContents= handle.responseText;
// parse page contents for what your looking and trigger javascript events accordingly.
// use handle.open('GET', url, true) to allow javascript to continue executing. must provide a callback function to accept the page contents with handle.onreadystatechange()
}
function browserspec(){
if (window.XMLHttpRequest){
return new XMLHttpRequest();
}else{
return new ActiveXObject("Microsoft.XMLHTTP");
}
}
</script>
That should do it.
The triggered javascript should include clearInterval(stopmelater)
Let me know if that works for you
Jerry
You could try using PHP in your web page...something like this:
<html><body>
<form method="post" name="pingform" action="<?php echo $_SERVER['PHP_SELF']; ?>">
<h1>Host to ping:</h1>
<input type="text" name="tgt_host" value='<?php echo $_POST['tgt_host']; ?>'><br>
<input type="submit" name="submit" value="Submit" >
</form></body>
</html>
<?php
$tgt_host = $_POST['tgt_host'];
$output = shell_exec('ping -c 10 '. $tgt_host.');
echo "<html><body style=\"background-color:#0080c0\">
<script type=\"text/javascript\" language=\"javascript\">alert(\"Ping Results: " . $output . ".\");</script>
</body></html>";
?>
This is not tested so it may have typos etc...but I am confident it would work. Could be improved too...
I want to put a text file into a javascript function and then somehow display that function in the html.
The javascript can't be inside the html file; it has to be referenced from outside the file like a:
Here is a picture of what I am trying to do (I don't have enough rep points):
https://i.ibb.co/xGxw1bm/1.png
I have tried:
I tried using "XMLHttpRequest" to try and display the text file in the front end html by uploading my text file to dropbox so I can be using the "https" method to communicated instead of "file://" since that doesn't work in the twitch developer rig
const Http = new XMLHttpRequest();
const url=' https://cors-anywhere.herokuapp.com/https://app.box.com/s/zkt7pbsv0cnnogafcxq8n5elmc9plxbz';
\\later in the code; because I didn't know where to put the rest of the http commands so I put them in the twitch.onAuthorized function which needs to be ran in this script anyway. I don't know what it does but it needs to be there and since its already a function I figured it would be better there. (Unless someone can make a function where all the https stuff is in one function.)
twitch.onAuthorized(function (auth) {
// save our credentials
token = auth.token;
tuid = auth.userId;
Http.open("GET", url);
Http.send();
Http.onreadystatechange=(e)=>{
console.log(Http.responseText);
setAuth(token);
$.ajax(requests.get)
}
});
I tried the document.getelement thingy but that doesn't ever explain to me how to put this in the html as text:
document.getElementById("demo").innerHTML
Everything I have seen on this has always said you can manipulate using a tag but I have not seen one example where this document.get thingy is in another javascript file and they have to reference it to the html. I always see it in the same file with the html.
What function can I use to extract that function into the html file without using a button? I just want it to display on like an object tag or an iframe tag.
I tried using the object tag....
It seems to work however......
I notice it pulls the website like an html file. Is there anyway to direct link a text file to the object tag but just the text data shows? Do I have to upload my text to a secure https? Can I even pull a text file from the web without it pulling in the html?
Here is the html file exact: (Note: This is testing, so none of the words matter in the html file. I am just trying to learn how to put text on here from another javascript file or backend.js javascript file. What tag or reference can I use to put a function from javascript into here without a button? Just on the screen I need the text.:
<!DOCTYPE html>
<html>
<head>
<title>Viewer Page</title>
</head>
<body style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;">
<div id="app" class="full-height"></div>
<script src="twitch-ext.min.js"></script>
<script src="jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous"></script>
<script src="viewer.js" type="text/javascript"></script>
<h2>Hello, World!</h2>
<p>Would you care to cycle a color?</p>
<object type="text/html" data="https://www.w3.org/TR/PNG/iso_8859-1.txt"></object>
</body>
</html>
Here is the viwer.js (this is where the javascript for getting the text file needs to be):
const Http = new XMLHttpRequest();
const url=' https://cors-anywhere.herokuapp.com/https://app.box.com/s/zkt7pbsv0cnnogafcxq8n5elmc9plxbz';
let token = '';
let tuid = '';
const twitch = window.Twitch.ext;
// create the request options for our Twitch API calls
const requests = {
set: createRequest('POST', 'output'),
};
function createRequest (type, method) {
return {
type: type,
url: location.protocol + '//localhost:8081/musicoutput/' + method,
success: updateBlock,
error: logError
};
}
function setAuth (token) {
Object.keys(requests).forEach((req) => {
twitch.rig.log('Setting auth headers');
requests[req].headers = { 'Authorization': 'Bearer ' + token };
});
}
twitch.onContext(function (context) {
twitch.rig.log(context);
});
twitch.onAuthorized(function (auth) {
// save our credentials
token = auth.token;
tuid = auth.userId;
Http.open("GET", url);
Http.send();
Http.onreadystatechange=(e)=>{
console.log(Http.responseText);
setAuth(token);
$.ajax(requests.get)
}
});
function updateBlock (hex) {
twitch.rig.log('Updating music info');
}
function logError(_, error, status) {
twitch.rig.log('EBS request returned '+status+' ('+error+')');
}
function logSuccess(hex, status) {
twitch.rig.log('EBS request returned '+hex+' ('+status+')');
}
I'm trying to use jQuery to read all text files in a folder and display their contents, but then, filter which should ones should be displayed based on the name of the folder.
Here's the JavaScript:
var obj = {
"01600610/9874565214_789545621.txt": "",
"01600610/9874565214_789545622.txt": "",
"01600610/9874565214_789545623.txt": ""
};
$.each( obj, function(SavedText) {
$.get(SavedText, function(data){
$('#NewMessagesHolder').prepend('<div class="MessageClass">'+ '<span class="ChatName">' + CookieName + ' ('+ time + ')</span>' + ': '+ data +'</div>')
}, 'text');
});
On this:
var obj = {
"01600610/9874565214_789545621.txt": "",
"01600610/9874565214_789545622.txt":"",
"01600610/9874565214_789545623.txt":""
};
Q1: How do I get all text files inside a folder instead of specifying the file I want?
Q2: How can I filter? For example, how can I only get files ending or starting with 789545.
When these files are on your local filesystem, you can use the HTML5 Filesystem API.
If the files are located on a server, there is no way listing them from plain (client-side) javascript (maybe except a brute-force method, which shouldn't be considered of course). Then you have to write a server script (in PHP/Node/Perl/Phython/...) which will respond to a ajax request with a file list.
If you are using a server script, you should do the filtering on the server (so the answer depends on the language).
Otherwise you should use Regular Expressions:
var search = new RegExp("789545");
for(var file in obj) {
if(search.test(file))
alert(file+": "+obj[file]);
}
This would search for files with a name containing the pattern 789545
Please create index.php file and add this content to it. Create there a folder called "files" and add your files there
<?php
function contains($haystack, $needle){
if (strpos($haystack,$needle) !== false) {
return true;
}
return false;
}
if(isset($_POST['get_files'])){
$folder = "";
$filter = "";
if(isset($_POST['folder'])){
$folder = $_POST['folder'];
}
if(isset($_POST['filter'])){
$filter = $_POST['filter'];
}
$files = array();
foreach(glob($folder.'/*.*') as $filename){
if($filter != ""){
if(contains($filename, $filter)){
$files[] = $filename;
}
}else{
$files[] = $filename;
}
}
print_r($files);
exit;
}
?>
<html>
<head>
<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script type="text/javascript">
function getData(){
jQuery.post('index.php',{
get_files:true,
folder:"files",
filter:""
},function(data){
jQuery('#container').html(data);
});
}
</script>
</head>
<body>
<input type="button" value="Get data" onclick="getData();" />
<div id="container"></div>
</body>
</html>
You can specify different folder, and filename filter in jQuery post request call
U can use regular expressions to filter the filenames. And for getting all files in a folder, U want to run a server side script. I use perl or php, to do this kinda stuff. U can use ajax to post the request and make the server script to return the file contents.
http://perlmeme.org/faqs/file_io/directory_listing.html
This link is just an example to do what u need in perl. And u could possibly do the same with other languages too
I want to reload an image on a page if it has been updated on the server. In other questions it has been suggested to do something like
newImage.src = "http://localhost/image.jpg?" + new Date().getTime();
to force the image to be re-loaded, but that means that it will get downloaded again even if it really hasn't changed.
Is there any Javascript code that will cause a new request for the same image to be generated with a proper If-Modified-Since header so the image will only be downloaded if it has actually changed?
UPDATE: I'm still confused: if I just request the typical URL, I'll get the locally cached copy. (unless I make the server mark it as not cacheable, but I don't want to do that because the whole idea is to not re-download it unless it really changes.) if I change the URL, I'll always re-download, because the point of the new URL is to break the cache. So how do I get the in-between behavior I want, i.e. download the file only if it doesn't match the locally cached copy?
Javascript can't listen for an event on the server. Instead, you could employ some form of long-polling, or sequential calls to the server to see if the image has been changed.
You should have a look at the xhr.setRequestHeader() method. It's a method of any XMLHttpRequest object, and can be used to set headers on your Ajax queries. In jQuery, you can easily add a beforeSend property to your ajax object and set up some headers there.
That being said, caching with Ajax can be tricky. You might want to have a look at this thread on Google Groups, as there's a few issues involved with trying to override a browser's caching mechanisms. You'll need to ensure that your server is returning the proper cache control headers in order to be able to get something like this to work.
One way of doing this is to user server-sent events to have the server push a notification whenever the image has been changed. For this you need a server-side script that will periodically check for the image having been notified. The server-side script below ensures that the server sends an event at least once every (approximately) 60 seconds to prevent timeouts and the client-side HTML handles navigation away from and to the page:
sse.py
#!/usr/bin/env python3
import time
import os.path
print("Content-Type: text/event-stream\n\n", end="")
IMG_PATH = 'image.jpg'
modified_time = os.path.getmtime(IMG_PATH)
seconds_since_last_send = 0
while True:
time.sleep(1)
new_modified_time = os.path.getmtime(IMG_PATH)
if new_modified_time != modified_time:
modified_time = new_modified_time
print('data: changed\n\n', end="", flush=True)
seconds_since_last_send = 0
else:
seconds_since_last_send += 1
if seconds_since_last_send == 60:
print('data: keep-alive\n\n', end="", flush=True)
seconds_since_last_send = 0
And then your HTML would include some JavaScript code:
sse.html
<html>
<head>
<meta charset="UTF-8">
<title>Server-sent events demo</title>
</head>
<body>
<img id="img" src="image.jpg">
<script>
const img = document.getElementById('img');
let evtSource = null;
function setup_sse()
{
console.log('Creating new EventSource.');
evtSource = new EventSource('sse.py');
evtSource.onopen = function() {
console.log('Connection to server opened.');
};
// if we navigate away from this page:
window.onbeforeunload = function() {
console.log('Closing connection.');
evtSource.close();
evtSource = null;
};
evtSource.onmessage = function(e) {
if (e.data == 'changed')
img.src = 'image.jpg?version=' + new Date().getTime();
};
evtSource.onerror = function(err) {
console.error("EventSource failed:", err);
};
}
window.onload = function() {
// if we navigate back to this page:
window.onfocus = function() {
if (!evtSource)
setup_sse();
};
setup_sse(); // first time
};
</script>
</body>
</html>
Here am loading an image, tree.png, as binary data dynamically with AJAX and saving the Last-Modified header. Periodically (every 5 second in the code below). I issue another download request sending backup a If-Modified-Since header using the saved last-modified header. I check to see if data has been returned and re-create the image with the data if present:
<!doctype html>
<html>
<head>
<title>Test</title>
<script>
window.onload = function() {
let image = document.getElementById('img');
var lastModified = ''; // 'Sat, 11 Jun 2022 19:15:43 GMT'
function _arrayBufferToBase64(buffer) {
var binary = '';
var bytes = new Uint8Array(buffer);
var len = bytes.byteLength;
for (var i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
return window.btoa( binary );
}
function loadImage()
{
var request = new XMLHttpRequest();
request.open("GET", "tree.png", true);
if (lastModified !== '')
request.setRequestHeader("If-Modified-Since", lastModified);
request.responseType = 'arraybuffer';
request.onload = function(/* oEvent */) {
lastModified = request.getResponseHeader('Last-Modified');
var response = request.response;
if (typeof response !== 'undefined' && response.byteLength !== 0) {
var encoded = _arrayBufferToBase64(response);
image.src = 'data:image/png;base64,' + encoded;
}
window.setTimeout(loadImage, 5000);
};
request.send();
}
loadImage();
};
</script>
</head>
<body>
<img id="img">
</body>
</html>
You can write a server side method which just returns last modified date of the image resource,
Then you just use polling to check for the modified date and then reload if modified date is greater than previous modified date.
pseudo code (ASP.NET)
//server side ajax method
[WebMethod]
public static string GetModifiedDate(string resource)
{
string path = HttpContext.Current.Server.MapPath("~" + resource);
FileInfo f = new FileInfo(path);
return f.LastWriteTimeUtc.ToString("yyyy-dd-MMTHH:mm:ss", CultureInfo.InvariantCulture);//2020-05-12T23:50:21
}
var pollingInterval = 5000;
function getPathFromUrl(url) {
return url.split(/[?#]/)[0];
}
function CheckIfChanged() {
$(".img").each(function (i, e) {
var $e = $(e);
var jqxhr = $.ajax({
type: "POST",
contentType: "application/json; charset=utf-8",
url: "/Default.aspx/GetModifiedDate",
data: "{'resource':'" + getPathFromUrl($e.attr("src")) + "'}"
}).done(function (data, textStatus, jqXHR) {
var dt = jqXHR.responseJSON.d;
var dtCurrent = $e.attr("data-lastwrite");
if (dtCurrent) {
var curDate = new Date(dtCurrent);
var dtLastWrite = new Date(dt);
//refresh if modified date is higher than current date
if (dtLastWrite > curDate) {
$e.attr("src", getPathFromUrl($e.attr("src")) + "?d=" + new Date());//fool browser with date querystring to reload image
}
}
$e.attr("data-lastwrite", dt);
});
}).promise().done(function () {
window.setTimeout(CheckIfChanged, pollingInterval);
});
}
$(document).ready(function () {
window.setTimeout(CheckIfChanged, pollingInterval);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<img class="img" src="/img/rick.png" alt="rick" />
If you are going to check whether files has changed on the server you have to make http request from the server for the file time, because there is no other way for your check the file time once page get loaded to the browser.
So that time check script will like
filetimecheck.php
<?php
echo filemtime(string $filename);
?>
Then you can check the file time using your Javascript. BTW I have put jQuery $.get for check the file time.
dusplayimage.php
<img id="badge" src="image.jpg"> />
<script>
var image_time = <?php echo filemtime(string $filename); ?>;
var timerdelay = 5000;
function imageloadFunction(){
$.get("filetimecheck.php", function(data, status){
console.log("Data: " + data + "\nStatus: " + status);
if(image_time < parseInt(data)) {
document.getElementById('yourimage').src = "image.jpg?random="+new Date().getTime();
}
});
setTimeout(imageloadFunction, timerdelay);
}
imageloadFunction();
</script>
You will be using extra call to the server to check the file time which you can't avoid however you can use the time delay to fine-tune the polling time.
Yes, you can customize this behavior. Even with virtually no change to your client code.
So, you will need a ServiceWorker (caniuse 96.59%).
ServiceWorker can proxy your http requests. Also, ServiceWorker has already built-in storage for the cache. If you have not worked with ServiceWorker, then you need to study it in detail.
The idea is the following:
When requesting a picture (in fact, any file), check the cache.
If there is no such picture in the cache, send a request and fill the cache storage with the date of the request and the file.
If the cache contains the required file, then send only the date and path of the file to the special API to the server.
The API returns either the file and modification date at once (if the file was updated), or the response that the file has not changed {"changed": false}.
Then, based on the response, the worker either writes a new file to the cache and resolves the request with the new file, or resolves the request with the old file from the cache.
Here is an example code (not working, but for understanding)
s-worker.js
self.addEventListener('fetch', (event) => {
if (event.request.method !== 'GET') return;
event.respondWith(
(async function () {
const cache = await caches.open('dynamic-v1');
const cachedResponse = await cache.match(event.request);
if (cachedResponse) {
// check if a file on the server has changed
const isChanged = await fetch('...');
if (isChanged) {
// give file, and in the background write to the cache
} else {
// return data
}
return cachedResponse;
} else {
// request data, send from the worker and write to the cache in the background
}
})()
);
});
In any case, look for "ways to cache statics using ServiceWorker" and change the examples for yourself.
WARNING this solution is like taking a hammer to crush a fly
You can use sockets.io to pull information to browser.
In this case you need to monitor image file changes on the server side, and then if change occur emit an event to indicate the file change.
On client (browser) side listen to the event and then then refresh image each time you get the event.
set your image source in a data-src property,
and use javascript to periodicaly set it to the src attribute of that image with a anchor (#) the anchor tag in the url isn't send to the server.
Your webserver (apache / nginx) should respond with a HTTP 304 if the image wasn't changed, or a 200 OK with the new image in the body, if it was
setInterval(function(){
l= document.getElementById('logo');
l.src = l.dataset.src+'#'+ new Date().getTime();
},1000);
<img id="logo" alt="awesome-logo" data-src="https://upload.wikimedia.org/wikipedia/commons/1/11/Test-Logo.svg" />
EDIT
Crhome ignores http cache-control headers, for subsequent image reloads.
but the fetch api woks as expected
fetch('https://upload.wikimedia.org/wikipedia/commons/1/11/Test-Logo.svg', { cache: "no-cache" }).then(console.log);
the no-cache instructs the browser to always revalidate with the server, and if the server responds with 304, use the local cached version.