Javascript - getting path of file dropped into HTA - javascript

I am building a little HTA for personal use, and would like to be able to drag and drop a file to the interface. Once the file is dropped, I would either automatically run it (assuming it fits some parameters I set, like file extension), or at least fill in the input box on the HTA interface.
I've searched extensively, but can't find a solution. Thoughts?

Tomalak, is incorrect in his statement...there is way to do what you want except that you have to add the DropHandler in the registry for HTA files it's really easy to do and once done you will be able to do exactly what your trying to do. I couldn't find much documentation on it, but here is a link to an HTA that was written a long time ago by a guy named Michel Gallant, that shows you how to it: https://www.jensign.com/www/wsh/imager/
When the HTA is launched it looks to see if you have the DropHandler already configured. If you don't it gives you the option for it to configure it for you. Once configure all you have to do is close and reopen the HTA and wallah, there you go Drag and Drop support in HTA files.

If you don't want to enable the drop handler, I could imagine a way that this might be possible. It's a bit of a comedy chain, but I could see myself implementing this if I was backed into a corner and needed that functionality.
You can create an IFRAME which has its src as a temp folder somewhere. This folder will be displayed as an Explorer view. You can then drag files into that. Set up a polling routine against that folder to see if there are any new files. And voila, you have a lame way to support drag and drop operations with a given file.

An HTA obviously cannot be target of a shell drop operation – at least on my system, dropping something on an HTA is impossible.
This would imply you cannot directly do what you intend.
A .vbs can however be a drop target. Full paths of the dropped files are available via the WScript.Arguments.Unnamed collection.
HTA has access to it's command line arguments via the commandLine Property. This would mean you could build a small helper VBScript that translates the dropped files into a command line and calls the HTA for you.
Note that you cannot drop endless amounts of files on a .vbs, and command lines are not unlimited either. There will be a length limit in the area of a few kB (I have not tried to find where exactly the limit is, just be prepared to face a limit.)

Go and try google gears which supplies drag and drop.
You can even use mysql in hta.
Google Gears is not available in hta, however, you can create the activexobject in a html file, then include it using an iframe(<iframe application="true" src=".."></iframe>)
After that, you can use the activexobject through the iframe.

Regarding …
“would like to be able to drag and drop a file to the [HTA] interface”
… which I interpret as a desire to drop files to the HTA’ running window, rather than dropping files on the HTA file itself or a shortcut to it.
With HTML5 the dropping itself is easy. Use e.g. a <div> element as a drop area. For this element handle the events dragenter, dragover and drop. E.g. the drop handler can look like this:
function on_drop( e )
{
e.preventDefault(); // stops the browser from redirecting off to the file
var dt = e.dataTransfer
var is_file_transfer = false;
for( var i = 0; i < dt.types.length; ++i )
{
if( dt.types[i].toLowerCase() == 'files' )
{
is_file_transfer = true;
}
}
if( !is_file_transfer )
{
return false;
}
on_files_dropped( dt.files );
return true;
}
… where on_files_dropped is a function defined by you that handles a files drop.
Adding the event handlers dynamically in the document loaded event, can look like this:
var dropbox = document.getElementById( 'blah' );
dropbox.addEventListener( 'dragenter', on_dragenter, false );
dropbox.addEventListener( 'dragover', on_dragover, false );
dropbox.addEventListener( 'drop', on_drop, false );
So far so good.
However, security intervenes with a restriction: you do not get direct knowledge of the original file paths, only the file names and the file sizes. For this functionality is designed for the web, not for local trusted HTML applications. So it may or may not necessarily be a problem.
For the purpose of using a dropped file as a source for an HTML element, and generally for reading a dropped file, HTML5 provides a FileReader (there are a number of tutorials available, which link further to technical documentation).
Where a local path is needed, e.g. for playing a file in Windows Mediaplayer, you can assume that the drag operation originated with Windows Explorer, now also called File Explorer, and then just check which Explorer window, if any, contains a file with that name and size.
Hopefully not more than one such originating window will be found.
var shell = new ActiveXObject( "Shell.Application" );
var fso = new ActiveXObject( "Scripting.FileSystemObject" );
function possible_paths_for( filename )
{
var windows = shell.windows(); // Windows Explorer windows.
var n_windows = windows.Count;
var lowercase_filename = filename.toLowerCase();
var paths = Array();
for( var i = 0; i < n_windows; ++i )
{
var url = windows.Item(i).LocationURL;
var path = decodeURI( url.substr( 8 ) ).replace( /\//g, '\\' );
// The path can be the path of this HTML application (.hta file), so:
if( fso.FolderExists( path ) )
{
var folder = fso.GetFolder( path );
for( var it = new Enumerator( folder.Files ); !it.atEnd(); it.moveNext() )
{
var file = it.item();
if( file.Name.toLowerCase() == lowercase_filename )
{
paths.push( file.Path.toLowerCase() );
}
}
}
}
return paths;
}
Essentially that’s it. Except, maybe, since HTAs default to IE7, how does one get HTML5 functionality. Well may through doctype declaration, but so far in my little experimentation I just use the following:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<!-- A windows web control defaults to quirky IE7 semantics. Request for better: -->
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta http-equiv="MSThemeCompatible" content="yes">
This gives you latest Internet Explorer engine, but at the cost of no HTA element, and hence no direct access to the command line. I found that the command line can be retrieved by running Windows’ wmic program, but that’s an awful hack. This whole problem area, with most apparently open roads turning out to be closed, appears to be a consequence of Microsoft now considering HTAs a legacy technology, to be quietly phased out in favor of fullscreen ad-ridden Windows 8 AppStore apps.
Anyway, good luck!

I can confirm it is possible to obtain drag and drop functionality for files and folders in HTA window with "Web Browser" (no need to use any third party ActiveX objects, HTML5 or manipulations with the registry).
Its "RegisterAsDropTarget" parameter switched on allows such operations while "Web Browser" itself is built-in in every Windows since XP or 2000 by default. This way it works for input files named in any locale (Unicode names support) while, for example, WScript and CScript don't support this by default.
Below is a sample implemented as a self-contained component with many customized features including styles and backgrounds. It works both for 64 and 32 bit folder paths and can be inserted into DOM tree for certain window.
Save the source code below as a text file and then change its extension to "hta".
Then launch the obtained application by double click on it.
<script>
/*
Extended Drop Target v1.1.4 (https://tastyscriptsforfree.wix.com/page/scripts)
Copyright 2017-2020 Vladimir Samarets. All rights reserved.
tastyscriptsforfree#protonmail.com
Release date: November 9, 2020.
Use this script sample entirely at your own risk.
This script sample is copyrighted freeware and I am not responsible for any damage or data loss it could unintentionally cause.
You may modify it but please leave a comment with direct link to https://tastyscriptsforfree.wix.com/page/scripts in that case.
*/
offscreenBuffering = true; //postpone the application window appearance till its UI is ready
var O = function(o){return new ActiveXObject(o);},
WSS = O('WScript.Shell'),
env = WSS.Environment('Process'),
head = document.documentElement.firstChild, //head
PID; //PID of 64 bit HTA instance
if(!env('is64bit')) //indicates whether the application is launched as 64 bit or not
{
!function hide(e){try{moveTo(10000, 10000);}catch(e){try{hide();}catch(e){hide();}}}(); //hiding the application window
head.insertBefore(document.createElement('<hta:application showInTaskBar=0>'), head.firstChild); //hiding the application in the Taskbar
var WMI= //a small library written by me for obtaining WMI instance, its common methods and properties
{ //below is a sample of creating a process with certain window shifts and environment variables
//and obtaining its <ProcessId> by using WMI
SWL:new ActiveXObject('WbemScripting.SWbemLocator'),
PRMS:function(p)
{
var s = WMI.PS.SpawnInstance_();
for(var i in p)
s[i] = p[i];
return s;
},
Create:function(c, s, d)
{
var CreateIn = WMI.CreateIn.SpawnInstance_();
CreateIn.CommandLine = c;
CreateIn.ProcessStartupInformation = s;
CreateIn.CurrentDirectory = d;
return WMI.PRC.ExecMethod_('Create', CreateIn).ProcessId;
}
};
WMI.PRC = (WMI.WM = WMI.SWL.ConnectServer('.', 'root/cimv2')).Get('Win32_Process');
WMI.PS = WMI.WM.Get('Win32_ProcessStartup');
WMI.CreateIn = WMI.PRC.Methods_('Create').InParameters;
var ID = O('Scriptlet.TypeLib').GUID.substr(0, 38), //the unique ID obtaining
EV = 'is64bit='+ID; //passing the unique ID to 64 bit HTA instance as an Environment variable
for(var items = new Enumerator(env); !items.atEnd(); items.moveNext())
EV += '?' + items.item(); //obtaining all Environment variables for current process
PID = WMI.Create //obtaining PID of 64 bit HTA instance
(
'mshta "' + decodeURIComponent(location.pathname) + '"', //current path
WMI.PRMS
(
{
X:10000, Y:10000, //hiding the application window before it is shown in order to resize it smoothly
EnvironmentVariables:
EV.split('?') //obtaining an array of all Environment variables by using this approach is universal for different
//versions of Windows
/*
[ //another way to pass certain Environment variables
'is64bit='+ID, //indicates that the application is launched as 64 bit
'SystemRoot='+env('SystemRoot'), //for start
'SystemDrive='+env('SystemDrive'), //for hyperlinks
'TEMP='+env('TEMP'), //for "mailto" links
'CommonProgramW6432='+env('CommonProgramW6432') //for ADODB.Stream
]
*/
}
)
);
head.firstChild.insertAdjacentHTML('afterEnd', '<object id=' + ID + ' PID=' + PID +
' classid=clsid:8856F961-340A-11D0-A96B-00C04FD705A2><param name=RegisterAsBrowser value=1>'); //registering current HTA window in collection of windows
showModalDialog('javascript:for(var ws=new ActiveXObject("Shell.Application").Windows(),i=ws.Count;i-->0;)if((w=ws.Item(i))&&w.id=="'+ID+'"){w.s=document.Script;break;}', 0,
'dialogWidth:0;unadorned:1;'); //silent stop of the script and obtaining window focus for "AppActivate"
close();onerror=function(){close();};throw 0; //declining any further attempts of executing the rest statements of the code
}
var w,dt=new Date();
head.insertBefore(document.createElement('<hta:application contextMenu=no selection=no scroll=no>'), head.firstChild); //adding custom HTA header dynamically
document.title='Extended Drop Target';
resizeTo(800, 400);
for(var ws = O('Shell.Application').Windows(), i = ws.Count; i -- > 0;)
if((w = ws.Item(i)) && w.id == env('is64bit'))
{
PID = w.PID;
w.document.Script.WSS.AppActivate(PID); //using "WScript.Shell" in focus to activate
//the application window of 64 bit HTA instance;
//remember that "WScript.Shell" should be
//in focus in order "AppActivate" to work properly
break;
}
document.write('<body>'); //obtaining body
if(w && w.id == env('is64bit'))
w.s.close(); //closing previous 32 bit HTA instance while being in safe focus
document.body.appendChild(document.createTextNode('Debug screen (for test purposes only):'));
document.body.appendChild(document.createElement('br'));
document.body.appendChild(document.createElement('<textarea id=result cols=85 rows=5>'));
document.body.appendChild(document.createElement('p'));
document.body.appendChild(document.createTextNode('Extended Drop Target:'));
document.body.appendChild(document.createElement('br'));
document.body.appendChild
(
(
function createDropTarget(doc, filesAllowed, foldersAllowed, dTStyle, hdFont, wMColor, dMColor, pMColor, eMColor, process, resolve, msg, dBgImage, bBgImage,
description, redirect)
{
var dropTarget = doc.createElement('<span style="' + dTStyle + '">'),
ms = dropTarget.appendChild
(
doc.createElement('<span style="width:100%;height:100%;padding:10px;overflow:hidden;">')
), //message screen that hides Web Browser during dropped items processing
WB = '<object classid=clsid:8856F961-340A-11D0-A96B-00C04FD705A2 style="width:100%;height:100%;"><param name=Location value="about:<body onload=\'b=0;\'' +
' ondragover=(function(){clearTimeout(b);b=setTimeout(\'location.reload();\',100);}()) bgColor=' + dMColor +
' style=\'width:100%;height:100%;position:absolute;margin:0;border:0;overflow:hidden;\'>'+ (description || '') +'">',
processing = 1, //indicates whether a dropped item processing is started or not
processed = 1, //indicates whether a dropped item is processed or not
DBcatched = 1, //indicates whether DownloadBegin Web Browser event has been catched or not
allowed, //indicates whether drop target is allowed or not
allowedText = (filesAllowed ? foldersAllowed ? msg[32] : msg[33] : foldersAllowed ? msg[34] : ''), //"Drop a file or folder here."
WBTReset, //timeout for Web Browser reset
startProcessing = function(p) //processing the item path received after item drop (item path)
{
clearTimeout(WBTReset);
dropTarget.children[processed = 1].removeNode();
createDropTarget();
setTimeout(function()
{
var delay = 0;
if(p) //the item can be accessed
{
sM(msg[38] + p + '</div>', pMColor); //show "Processing"
var dt = new Date(), //date before processing
e; //error ID
try{e = process(p);}catch(e){e = 43;} //unknown error occured
dt = new Date() - dt; //date after processing
delay += dt>1000 ? 0 : 1000 - dt;
if(!e) //no errors occured
setTimeout(function(){sM(msg[39] + createDropTarget.timeToHMSR(dt) + ' =-</div>', pMColor);}, delay); //show "Processed in"
else //an error occured
{
var err;
try{resolve(e);}catch(err){;}
setTimeout(function(){sM(msg[39] + createDropTarget.timeToHMSR(dt) + ' =-</div><br>' + msg[e], eMColor);}, delay); //show "Processed in" with error
if(!redirect)
delay += 1000;
}
}
else //the item can't be accessed
{
sM(msg[40] + msg[41] + allowedText + msg[42], eMColor); //show "The item is not a file or folder, can't be accessed or its size is too big."
delay += 1000;
}
sDM(delay + 1000);
}, 1000);
},
setWBTReset = function(r) //setting a timeout for Web Browser reset (reset)
{
if(!processing)
{
processing = 1;
ms.style.display = '';
if(r)
WBTReset = setTimeout(startProcessing, 1000);
}
},
DB = function() //catching "DownloadBegin" Web Browser event
{
DBcatched = 1;
setWBTReset(1);
},
STC = function(p) //catching "StatusTextChange" Web Browser event (item path)
{
setWBTReset(p);
if(!processed && /file:|</.test(p))
{
if(/file:/.test(p))
startProcessing(filesAllowed ? decodeURIComponent(p).replace(/.+:((?:\/{3})|(?=\/{2}))(.+)...$/,'$2').replace(/\//g,'\\') : 0);
else if(/</.test(p))
{
if(!DBcatched) //indicates that drop target is leaved without drop
{
processed = 1;
clearTimeout(WBTReset);
sM(msg[31] + allowedText + msg[35] + '</div>', dMColor, dBgImage); //show "Drop a file or folder here."
allowed = 1;
ms.style.display = '';
}
else //shortcuts with complex structure
startProcessing();
}
}
},
NC2 = function(o, p) //catching "NavigateComplete2" Web Browser event (Web Browser object, item path)
{
if(!processed)
startProcessing(foldersAllowed && typeof p == 'string' && p.match(/^[^:]/) ? p : 0);
},
NE = function() //catching "NavigateError" Web Browser event
{
if(!processed)
startProcessing();
},
sM = function(m, c, bgImage) //show message (message, background or text color, background image)
{
if(dBgImage || bBgImage)
{
if(bgImage)
ms.style.backgroundImage = 'url(' + bgImage + ')';
ms.style.color = c;
}
else
ms.style.backgroundColor = c;
m = '<div style="font:' + hdFont + ';">' + m;
if(!redirect)
ms.innerHTML = m;
else
redirect(m);
},
sDM = function(delay) //show default message (delay)
{
setTimeout(function(){allowed = 1;}, delay);
setTimeout(function(){if(allowed)sM((allowedText ? msg[31] + allowedText + msg[35] : msg[36]) + '</div>', dMColor, dBgImage);}, delay + 100); //show "Drop a file or folder
//here." or "Drop Target is
} //disabled."
sM(msg[30], wMColor, dBgImage); //show welcome message
ms.ondragenter=function()
{
if(allowed && (filesAllowed || foldersAllowed) && !event.dataTransfer.getData('text')) //text dropping is not allowed
{
event.dataTransfer.dropEffect='move';
return false;
}
}
ms.ondragover = function()
{
if(allowed && (filesAllowed || foldersAllowed) && !event.dataTransfer.getData('text')) //text dropping is not allowed
{
event.dataTransfer.dropEffect='move';
if(!Math.abs(event.x - this.x) && !Math.abs(event.y - this.y)) //accepting only slow mouse motion
{
this.style.display = 'none';
DBcatched = allowed = processing = processed = 0;
sM(msg[37], dMColor, bBgImage); //show "Analysing..."
}
this.x = event.x;
this.y = event.y;
return false;
}
}
!(createDropTarget = function()
{
dropTarget.insertAdjacentHTML('beforeEnd', WB);
with(dropTarget.children[1])
{
RegisterAsDropTarget = Silent = Offline = 1;
attachEvent('DownloadBegin', DB);
attachEvent('StatusTextChange', STC);
attachEvent('NavigateComplete2', NC2);
attachEvent('NavigateError', NE);
}
})();
createDropTarget.timeToHMSR = function(d) //convert date to hours, minutes, seconds and remainder (milliseconds) notation (date)
{
var r = d % 3600000,
h = d / 3600000 ^ 0, //hours
m = r / 60000 ^ 0, //minutes
s = r % 60000 / 1000 ^ 0; //seconds
r = d % 1000; //remainder (milliseconds)
return ((h ? h + 'h' : '') + (m ? (h ? ' ' : '') + m + 'm' : '') + (s ? (h || m ? ' ' : '') + s + 's' : '') + (r ? (h || m || s ? ' ' : '') + r + 'ms' : '')) || '0ms';
},
sDM(3000); //postponing Web Browser access while it generates its events at start
return dropTarget;
}
(
//BEGIN of Extended Drop Target custom settings
document, //"document" object of certain window
1, //indicates whether processing of files is allowed or not
1, //indicates whether processing of folders is allowed or not
'width:350px;height:150px;border:2px blue solid;font:bold 10pt Arial;text-align:center;cursor:default;overflow:hidden;word-break:break-all;', //drop target style
'bold 12pt Tahoma', //message header font
'yellow', //welcome message background color if background image is not set or text color otherwise
'mediumaquamarine', //default message background color if background image is not set or text color otherwise
'limegreen', //processing message background color if background image is not set or text color otherwise
'salmon', //error message background color if background image is not set or text color otherwise
function(p) //data processing sample (file or folder path)
{
alert('Here goes data processing sample.\n\nProcessing:\n' + p);
//throw 1; //unknown error occured
//return 1; //certain error 1 occured
return 0; //no errors
},
function(e) //error resolving sample (error ID)
{
switch(e)
{
case 1:
result.value = '\nCertain error 1 is catched.'; //additional action sample for certain error 1
updateData1();
break;
default:
result.value = '\nAn unknown error is catched.'; //additional action sample for an unknown error
sendEmail();
break;
}
file.Close(); //built-in silent catching of errors if certain error resolving method is still inaccessible
},
{ //list of all messages for Extended Drop Target
30: 'Welcome!</div><br>Hello World!', //welcome message
31: 'Drop a ', //31, 32, 33, 34, 35 - "Drop a file or folder here."
32: 'file or folder',
33: 'file',
34: 'folder',
35: ' here.',
36: 'Drop Target is disabled.',
37: '-= Analysing... =-</div>',
38: '-= Processing =-</div><br><div style="text-align:left;">',
39: '-= Processed in ',
40: "-= Can't be processed =-</div><br>",
41: 'The item is not a ', //41, 32, 33, 34, 42 - "The item is not a file or folder,<br>can't be accessed or its size is too big."
42: ",<br>can't be accessed or its size is too big.",
43: 'An unknown error occured.', //unknown error message
1: 'Certain error 1 occured.' //certain error 1 message
//certain error # message
//...
}
//,'C:\\Windows\\Web\\Screen\\img103.png' //default background image or "undefined" (optional)
//,'C:\\Windows\\Web\\Screen\\img102.jpg' //busy mode background image or "undefined" (optional)
//,'<div style=\'font:10pt Tahoma;padding:20px;\'>List of files supported by default.</div>'
//description length depends on the message language or its actual bytes count or "undefined" (optional)
//,function(m){result.value = m;} //sample for receiving messages or "undefined" (optional)
//END of Extended Drop Target custom settings
)
)
);
result.value = '\nLoading time for 64 bit instance (if possible): ' + createDropTarget.timeToHMSR(new Date() - dt) + '.';
moveTo(screen.availWidth / 2 - 400, screen.availHeight / 2 - 200);
</script>

Related

How to get video's metadata (length) before uploading?

First thing first, I am trying to extract the video duration from the file and then display it without having to actually upload the file.
When a user selected a video - the information will be displayed below it includes file name, file size, file type. However much to my terrible skills - I cannot get the duration to display. I tried some code snippets i found from other sites as well as here but none of them seems to work. Just trying to find a simple code that would do the task.
I tried onloadedmetadata but I don't think that would even work.
Please note : I'm still learning javascript.
I also tried some sites tutorial & some code snippet I found via stackoverflow
function uploadFunction(){
//Variables
var cVideo = document.getElementById("fileUp");
var txtInfo = "";
//function
if ('files' in cVideo){
if(cVideo.files.length == 0){
txtInfo = "Please select a video";
} else {
for (var v = 0; v < cVideo.files.length; v++){
txtInfo += "<br><strong>#" + (v+1) + " File Information:</strong> <br>";
var infoFile = cVideo.files[v];
if('name' in infoFile){
txtInfo += "File name: " +infoFile.name +"<br>";
}
if('size' in infoFile){
txtInfo += "File size: " +infoFile.size +" Bytes <br>";
}
if('type' in infoFile){
txtInfo += "File Type: "+infoFile.type +" <br>";
}
if('duration' in infoFile){
txtInfo += "Duration : "+infoFile.duration +"<br>";
}
}
}
}
document.getElementById("information").innerHTML = txtInfo ;
}
HTML
<input type="file" id="fileUp" name="fileUpload" multiple size="50" onchange="uploadFunction()">
<p id="information"></p>
Can't get the duration to appear at all.
It's actually reasonably simple, but since this requires turning the file into a blob and then checking its duration with a video element, uploading a video longer than a couple of minutes will take a lot of processing and slow down your page immensly. I have added a filesize restriction (which is a reasonable start in my opinion). Here's a snippet (which will not work on Stack Overflow due to sandbox restrictions, but I did test it on a local server). I obviously also don't check MIME types or whether its a video at all, although loadedmetadata will not trigger if you upload anything that is not a video.
const fileInput = document.querySelector( 'input[type=file]' );
const fileDuration = document.getElementById( 'file-duration' )
// Listen for any change to the value of the file input
fileInput.addEventListener( 'change', event => {
fileDuration.textContent = 'Fetching video duration...';
// When the file selected by the user changes:
// - create a fresh <video> element that has not yet fired any events
// - create a file reader to safely retrieve and manipulate the file
const file = event.target.files[0];
const video = document.createElement( 'video' );
const reader = new FileReader();
// Cancel if the initial filesize just seems too large.
// If the maximum allowed is 30 seconds, then a Gigabyte of file seems too much.
if( file.size > 10000000 ){
fileDuration.textContent = `File is too large (${file.size}). Refused to read duration.`;
return;
}
// Before setting any source to the <video> element,
// add a listener for `loadedmetadata` - the event that fires
// when metadata such as duration is available.
// As well as an error event is case something goes wrong.
video.addEventListener( 'loadedmetadata', event => {
// When the metadata is loaded, duration can be read.
fileDuration.textContent = `Video is ${video.duration} seconds long.`;
});
video.addEventListener( 'error', event => {
// If the file isn't a video or something went wrong, print out an error message.
fileDuration.textContent = `Could not get duration of video, an error has occurred.`;
});
// Before reading any file, attach an event listener for
// when the file is fully read by the reader.
// After that we can use this read result as a source for the <video>
reader.addEventListener( 'loadend', function(){
// reader.result now contains a `blob:` url. This seems like a
// nonsensical collection of characters, but the <video> element
// should be able to play it.
video.src = reader.result;
// After we assigned it to the `src` of the <video>, it will be
// act like any other video source, and will trigger the related
// events such as `loadedmetadata`, `canplay`, etc...
});
// Instruct the reader to read the file as a url. When its done
// it will trigger the `loadend` event handler above.
reader.readAsDataURL( file );
});
<input type="file" />
<p id="file-duration">No video file</p>

Multiple HTML5 desktop notifications not displaying

In Chrome, things work OK (multiple notifications display at the same time). But in Firefox when there are multiple notifications NONE get displayed. Please see fiddle for a demo:
http://jsfiddle.net/e6ps2/3/
Scenario: I have a loop that reads through an array of things to be displayed as notifications.
I started off using Notify.js a nice javascript wrapper to the notifications api. And I thought the problem may be related to that. Then I tried using the api directly and the problem persisted.
So I would like to layer the notifications on top of each other if possible (which is how it should happen - and does happen in Chrome). But a possible fallback is to queue the notifications and using notify.js notifyClose() callback function to open the next notification in the queue - but have no idea how to do this.
if (Notify.isSupported()) {
//Notify wrapper Notifications
if (Notify.needsPermission()) Notify.requestPermission();
//var j = 1; //uncomment this for single notification
var j = 2;
for (var i=0;i<j;i++) {
var my_notification = new Notify('Hello World ' + i, {
body: 'Some message here ' + i,
tag: "notify_" + i
});
//uncomment below to show the notify plugin html5 notifications and then comment out the bare bones one
//my_notification.show();
//HTML5 bare nones Notifications
var notification = new Notification("Hi there! " + i, {
body: 'Some message here ' + i,
tag: "Hello_"+ i
});
}
} else {alert("not supported"); }
Hope this all makes sense.
Thanks
For first issue... If permission hasn't been given, you need to give a callback to fire when requesting permission. Moving the send code into a function, allow it to be called standalone, or as the callback
if (Notify.needsPermission()) {
Notify.requestPermission(queueNotifications);
} else {
queueNotifications();
}
Regarding the queueing... I've had a similar issue and Firefox's implementation is poor compared to Chromes. Nevertheless, notifications can be queued using the following method:
create timeinterval variable
using the Notification onshow event, set a timeout with the interval to hide the notification
move the sending of the notification into a separate function that can be called by a timeout inside of the for loop
if (Notify.isSupported()) {
var
showTimeout,
displayTime = 5000,
queueNotifications = function(){
var i,
j = 3;
for (i=0;i<j;i++) {
setTimeout(sendNotifications, displayTime*i, i);
}
},
sendNotifications = function(i){
var
hideTimeout,
onShow = function(){
var $this = this;
hideTimeout = setTimeout(function(){
$this.close();
}, displayTime);
},
my_notification = new Notify('Hello World ' + i, {
body: 'Some message here ' + i,
tag: "notify_" + i
});
my_notification.onShowNotification = onShow;
my_notification.show();
}
if (Notify.needsPermission()) {
Notify.requestPermission(queueNotifications);
} else {
queueNotifications();
}
I've updated your jsfiddle with the working version.
http://jsfiddle.net/e6ps2/5/
Cheers,
Dan
If you set the option 'requireInteraction: true' it will stack your notifications for you, they will not disappear automatically as default but you can use the following to hide them.
setTimeout(notification.close.bind(notification), 5000);

how to add an auto expand to ace editor

im using the ace editor and im unable to modify it to autoexpand when the user input is longer than the current size:
here is how i have it currently (it has a handler for shift+enter), and it does not work.
Typist.prototype.heightUpdateFunction = function() {
var newHeight =
ac.getSession().getScreenLength()
* ac.renderer.lineHeight
+ ac.renderer.scrollBar.getWidth();
$(settings.id).height(newHeight.toString() + "px");
ac.resize();
};
Typist.prototype.createinput = function(settings,handler) {
var that = this;
var $typepad = $("<div/>" ,{
id : settings.id,
}).css({position:'relative', height: '40px'}) ;
$(that.place).append($typepad);
var ac = ace.edit(settings.id);
ac.commands.addCommand({
name : 'catchKeys',
bindKey : { win : 'Shift-Enter', mac : 'Shift-Enter' },
exec : function (ac) {
if (typeof handler === "function") {
handler(ac);
}
},
readOnly : false
});
that.heightUpdateFunction();
ac.getSession().on('change', that.heightUpdateFunction);
return true;
};
how would i get it to work? this current code does not.
How would i access the object that called the height update? (or the "id" of the div containing the ace editor, since i have several, each with an id reachable by
a.inpid
given
a = new Typist()
my attempt comes from reading this similar kind of problem i dont want to go that way because i will have several ace editors on the page, and i need to know the id of the one to apply the height adjustment to.
turns out i missed something simple
Typist.prototype.heightUpdateFunction = function() {
var newHeight =
ac.getSession().getScreenLength()
* ac.renderer.lineHeight
+ ac.renderer.scrollBar.getWidth();
$("#"+settings.id).height(newHeight.toString() + "px"); // i forgot $() needs '#'
ac.resize();
};
my bad. This omission kept me awake for hours.
EDIT:
see comment in the code to find my correction

Chrome Extension doesn't considers optionvalue until reloaded

I'm working on my first Chrome Extension. After learning some interesting notions about jquery i've moved to raw javascript code thanks to "Rob W".
Actually the extension do an XMLHttpRequest to a remote page with some parameters and, after manipulating the result, render an html list into the popup window.
Now everything is up and running so i'm moving to add some option.
The first one was "how many elements you want to load" to set a limit to the element of the list.
I'm using fancy-setting to manage my options and here's the problem.
The extension act like there's a "cache" about the local storage settings.
If i do not set anything and perform a clean installation of the extension, the default number of element is loaded correctly.
If i change the value. I need to reload the extension to see the change.
Only if a remove the setting i see the extension work as intended immediately.
Now, i'm going a little more into specific information.
This is the popup.js script:
chrome.extension.sendRequest({action: 'gpmeGetOptions'}, function(theOptions) {
//Load the limit for topic shown
console.log('NGI-LH -> Received NGI "max_topic_shown" setting ('+theOptions.max_topic_shown+')');
//Initializing the async connection
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://gaming.ngi.it/subscription.php?do=viewsubscription&pp='+theOptions.max_topic_shown+'&folderid=all&sort=lastpost&order=desc');
xhr.onload = function() {
var html = "<ul>";
var doc = xhr.response;
var TDs = doc.querySelectorAll('td[id*="td_threadtitle_"]');
[].forEach.call(TDs, function(td) {
//Removes useless elements from the source
var tag = td.querySelector('img[src="images/misc/tag.png"]'); (tag != null) ? tag.parentNode.removeChild(tag) : false;
var div_small_font = td.querySelector('div[class="smallfont"]'); (small_font != null ) ? small_font.parentNode.removeChild(small_font) : false;
var span_small_font = td.querySelector('span[class="smallfont"]'); (small_font != null ) ? small_font.parentNode.removeChild(small_font) : false;
var span = td.querySelector('span'); (span != null ) ? span.parentNode.removeChild(span) : false;
//Change the look of some elements
var firstnew = td.querySelector('img[src="images/buttons/firstnew.gif"]'); (firstnew != null ) ? firstnew.src = "/img/icons/comment.gif" : false;
var boldtext = td.querySelector('a[style="font-weight:bold"]'); (boldtext != null ) ? boldtext.style.fontWeight = "normal" : false;
//Modify the lenght of the strings
var lenght_str = td.querySelector('a[id^="thread_title_"]');
if (lenght_str.textContent.length > 40) {
lenght_str.textContent = lenght_str.textContent.substring(0, 40);
lenght_str.innerHTML += "<span style='font-size: 6pt'> [...]</span>";
}
//Removes "Poll:" and Tabulation from the strings
td.querySelector('div').innerHTML = td.querySelector('div').innerHTML.replace(/(Poll)+(:)/g, '');
//Modify the URL from relative to absolute and add the target="_newtab" for the ICON
(td.querySelector('a[id^="thread_title"]') != null) ? td.querySelector('a[id^="thread_title"]').href += "&goto=newpost" : false;
(td.querySelector('a[id^="thread_goto"]') != null) ? td.querySelector('a[id^="thread_goto"]').href += "&goto=newpost": false;
(td.querySelector('a[id^="thread_title"]') != null) ? td.querySelector('a[id^="thread_title"]').target = "_newtab": false;
(td.querySelector('a[id^="thread_goto"]') != null) ? td.querySelector('a[id^="thread_goto"]').target = "_newtab": false;
//Store the td into the main 'html' variable
html += "<li>"+td.innerHTML+"</li>";
// console.log(td);
});
html += "</ul>";
//Send the html variable to the popup window
document.getElementById("content").innerHTML = html.toString();
};
xhr.responseType = 'document'; // Chrome 18+
xhr.send();
});
Following the background.js (the html just load /fancy-settings/source/lib/store.js and this script as Fancy-Setting How-To explains)
//Initialization fancy-settings
var settings = new Store("settings", {
"old_logo": false,
"max_topic_shown": "10"
});
//Load settings
var settings = settings.toObject();
//Listener who send back the settings
chrome.extension.onRequest.addListener(function(request, sender, sendResponse) {
if (request.action == 'gpmeGetOptions') {
sendResponse(settings);
}
});
The console.log show the value as it has been cached, as i said.
If i set the value to "20", It remain default until i reload the extension.
If i change it to 30, it remain at 20 until i reload the extension.
If something more is needed, just ask. I'll edit the question.
The problem appears to be a conceptual misunderstanding. The background.js script in a Chrome Extension is loaded once and continues to run until either the extension or the Chrome Browser is restarted.
This means in your current code the settings variable value is loaded only when the extension first starts. In order to access values that have been updated since the extension is loaded the settings variable value in background.js must be reloaded.
There are a number of ways to accomplish this. The simplest is to move the settings related code into the chrome.extension.onRequest.addListener callback function in background.js. This is also the most inefficient solution, as settings are reloaded every request whether they have actually been updated or not.
A better solution would be to reload the settings value in background.js only when the values are updated in the options page. This uses the persistence, or caching, of the settings variable to your advantage. You'll have to check the documentation for implementation details, but the idea would be to send a message from the options page to the background.js page, telling it to update settings after the new settings have been stored.
As an unrelated aside, the var keyword in the line var settings = settings.toObject(); is not needed. There is no need to redeclare the variable, it is already declared above.

Disappearing Menu Commands After User Input in Tampermonkey

Tampermonkey is an extension for Google Chrome that attempts to emulate the functionality of Greasemonkey. To be clear, I got my script to work in Chrome and the default JavaScript changes to show up. I wanted to test the menu commands, however, and entered a 6-digit hex color code after clicking on the command in the Tampermonkey menu. I reloaded the page, and the commands disappeared from the menu! My script was still there (and the checkbox was ticked).
No matter what I did or what code I changed, I could never emulate this initial functionality after that user-defined input was set. This leads me to believe that there's some persistent data that I can't delete that's causing my script to fail prematurely. NOTE: This exact script works perfectly and without errors in Firefox.
This is obviously not a Tampermonkey forum, but people here seem very knowledgeable about cross-platform compatility. I didn't hear a single peep from the Chrome console after all of the changes below, and I'm really just out of ideas at this point. Here are some things I've tried (with no success). Any console errors are listed:
Changing jQuery version from 1.5.1 to 1.3.2
Calling localStorage.getItem('prevoColor') from console after page load (both values null)
Changing client-side storage from localStorage to get/setValue
Calling GM_getValue from the console = ReferenceError: GM_getValue is not defined
Deleting localStorage entries for veekun.com in Chrome options
Refreshing, Re-installing the script, and restarting the browser more times than I can count
Repeating all of the above commands using Firebug Lite (bookmarklet)
Here's the code I was using:
// ==UserScript==
// #name Veekun Comparison Highlighter
// #namespace tag://veekun
// #description Highlights moves exclusive to pre-evolutions on veekun.com's family comparison pages (user-defined colors available)
// #include http://veekun.com/dex/gadgets/*
// #author Matthew Ammann
// #version 1.0.3
// #date 3/11/11
// #require http://sizzlemctwizzle.com/updater.php?id=98824
// #require http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js
// ==/UserScript==
/*
Goal: Change checkmark color & move name to user-specified color on family comparison pages if
[DONE] Baby poke has a LEVEL-UP move unlearned by any evolutions
[DONE] a) Make sure move is not a TM or tutor move
[DONE] Any other mid-evolution has a move unlearnable by a final evo (Caterpie, Weedle families)
[DONE] a) Make sure move is not a TM or tutor move
[DONE] Any pre-evo has a TUTOR move unlearned by any evo (Murkrow in HG/SS)
[] Implement auto-update after uploading to userscripts.org
Credits: Brock Adams, for helping with Chrome compatibility
Metalkid, for the jQuery consult
*/
var isLevelupMove = false;
var isTutorMove = false;
var isTM = false;
var TMhead = $('#moves\\:machine');
var hasSecondEvo = false;
var hasFinalEvo1 = false;
var hasFinalEvo2 = false;
var header = $('.header-row').eq(1);
var TMmoves = new Array();
//This section deals with the user-defined colors
GM_registerMenuCommand("Color for pre-evolutionary-only moves", prevoColorPrompt)
GM_registerMenuCommand("Color for first evolution-only moves", evoColorPrompt)
var prevoColor = GM_getValue('prevoColor', '#FF0000');
var evoColor = GM_getValue('evoColor', '#339900');
function prevoColorPrompt()
{
var input = prompt("Please enter a desired 6-digit hex color-code for pre-evolutionary pokemon:")
GM_setValue('prevoColor', '#'+input);
}
function evoColorPrompt()
{
var input = prompt("Please enter the desired 6-digit hex color-code for first-evolution pokemon:")
GM_setValue('evoColor', '#'+input);
}
//This loop tests each 'th' element in a sample header row, determining how many Evos are currently present in the chart.
$('.header-row').eq(1).find('th').each(function(index)
{
if($(this).find('a').length != 0)
{
switch(index)
{
case 2:
hasSecondEvo = true;
break;
case 3:
hasFinalEvo1 = true;
break;
case 4:
hasFinalEvo2 = true;
break;
}
}
});
//All 'tr' siblings are TM moves, since it's the last section on the page
//This array puts only the names of the available TMs into the TMmoves array
TMhead.nextAll().each(function(index)
{
TMmoves.push($(this).children(":first").find('a').eq(0).html());
});
$('tr').each(function(index)
{
var moveName = $(this).children(":first").find('a').eq(0).html();
moveName = $.trim(moveName);
switch($(this).attr('id'))
{
case 'moves:level-up':
isLevelupMove = true;
break;
case 'moves:egg':
isLevelupMove = false;
break;
case 'moves:tutor':
isTutorMove = true;
case 'moves:machine':
isTM = true;
}
if(isLevelupMove || isTutorMove)
{
var babyMoveCell = $(this).find('td').eq(0);
babyMoveText = $.trim(babyMoveCell.html());
secondEvoCell = babyMoveCell.next();
secondEvoText = $.trim(secondEvoCell.html());
finalEvo1Cell = secondEvoCell.next();
finalEvo1Text = $.trim(finalEvo1Cell.html());
finalEvo2Cell = finalEvo1Cell.next();
finalEvo2Text = $.trim(finalEvo2Cell.html());
//This checks if evolutions have checkmarks
if(babyMoveText.length > 0)
{
if(hasSecondEvo && secondEvoText.length == 0 || hasFinalEvo1 && finalEvo1Text.length == 0 ||
hasFinalEvo2 && finalEvo2Text.length == 0)
{
//See if the move is a TM before proceeding
var tm = tmCheck(moveName);
if(!tm)
{
if(secondEvoText.length > 0)
{
babyMoveCell.css("color", evoColor);
secondEvoCell.css("color", evoColor);
babyMoveCell.prev().find('a').eq(0).css("color", evoColor); //highlights move name
}
else
{
babyMoveCell.css("color", prevoColor);
babyMoveCell.prev().find('a').eq(0).css("color", prevoColor);
}
}
}
}
else if(secondEvoText.length > 0)
{
if(hasFinalEvo1 && finalEvo1Text.length == 0 || hasFinalEvo2 && finalEvo2Text.length == 0)
{
var tm = tmCheck(moveName);
if(!tm)
{
secondEvoCell.css("color", evoColor);
babyMoveCell.prev().find('a').eq(0).css("color", evoColor);
}
}
}
}
});
function tmCheck(input)
{
var isTM = false;
//Iterate through TMmoves array to see if the input matches any entries
for(var i = 0; i < TMmoves.length; i++)
{
if(input == TMmoves[i])
{
isTM = true;
break;
}
}
if(isTM == true)
return true;
else
return false;
}
//alert("evoColor: " + localStorage.getItem('evoColor') + ". prevoColor: " + localStorage.getItem('prevoColor'));
Any ideas as to why this is happening?
EDIT: I messaged sizzlemctwizzle about this problem, and this was his reply: "Tampermonkey’s #require implementation is incorrect. It downloads my updater far too often so I have banned it from using my updater via browser sniffing. My server just can’t handle the traffic it brings. The script it is downloading from my server shouldn’t have any actual code in it. Since it is causing errors with in your script I would guess Tampermonkey isn’t passing the User Agent header when it does these requests. I’m never tested my updater in Chrome so I have no idea why it breaks. Perhaps you could try and install NinjaKit instead."
What URL are you testing this on? I tested on http://veekun.com/dex/gadgets/stat_calculator.
Anyway, the script behavior, vis à vis the menu commands did seem erratic with Tampermonkey. I couldn't really tell / didn't really check if the rest of the script was working as it should.
The culprit seems to be the sizzlemctwizzle.com update check. Removing its // #require made the menu stable. Putting that directive back, broke the script again.
I've never been a fan of that update checker, so I'm not going to dive into why it appears to be breaking the Tampermonkey instance of this script. (That would be another question -- and one probably best directed at the 2 responsible developers.)
For now, suggest you just delete it. Your users will check for updates as needed :) .

Categories

Resources