Drag and Drop input for files not handling files - javascript

I've made a form where the user can upload a file and I wanted to add a drag and drop feature so I made it like this:
<small id="filename"></small>
<!-- <input id="thumbnail" type="file" name="thumbnail" class="input input-thumbnail form-control"> -->
<div class="dropzone" id="droparea">
<div class="input-container">
<input type="file" class="dropzone-input" id="thumbnail-input" name="thumbnail">
</div>
<div class="overlay">
<small class="overlay-text" id="overlay-text">Drag and Drop</small>
</div>
</div>
<script>
(function() {
let dropzone = document.getElementById('droparea');
let dropzonetext = document.getElementById('overlay-text');
let filenametext = document.getElementById('filename');
let fileImput = document.getElementById('thumbnail-input');
dropzone.ondrop = function(e) {
e.preventDefault();
this.className = 'dropzone';
dropzonetext.className = 'overlay';
fileImput.files[0] = e.target.files[0];
filenametext = fileImput.files[0].name
};
dropzone.ondragover = function() {
this.className = 'dropzone dragover';
dropzonetext.className = 'overlay dragover';
return false;
};
dropzone.ondragleave = function() {
this.className = 'dropzone';
dropzonetext.className = 'overlay';
return false;
};
}());
</script>
The expected behavior of this code is that the box changes color (controlled by css) whenever the user hovers over it when holding 1 or multiple files (I want to limit it to only accept 1 file). But this does not work.
Also whenever the file is dropped on the box the value of the input should become the file dropped by the user but this is not happening either.
Instead I'm getting this error in the console whenever I drop the file there:
Uncaught TypeError: Cannot read property '0' of undefined
at HTMLDivElement.dropzone.ondrop ((index):79)
dropzone.ondrop # (index):79
With line 79 being: fileImput.files[0] = e.target.files[0];
What is causing this problem?
Here is also the css for the drag and drop:
.dropzone-input{
opacity: 0;
-moz-opacity: 0;
filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0)
width: 30vw;
height: 25vh;
z-index:0;
position: absolute;
color: #ccc;
line-height:25vh;
text-align: center;
}
.dragover{
border-color: black;
text-color: black;
}
.dropzone {
width: 30vw;
height: 25vh;
overflow: hidden;
position: relative;
border: 2px dashed #ccc;
}
.overlay {
width: 30vw;
height: 25vh;
position: absolute;
top: 0;
left: 0;
z-index:1;
line-height:25vh;
text-align: center;
}
.overlay-text{
color: darkgrey;
}
.overlay-text.dragover{
text-color: black;
}
**EDIT: **
Someone asked for a screenshot:
you can see the file selected is the one I dropped on the input

The result is that, e.target.files is null. Because you set event ondrop on dropzone, so e.target maybe any ele in dropzone where you drop (detail in the picture test on my local). But some elements don't have files property. The ele which have files property is the input. So you should try 1 option:
Option 1,set the ondrop event for only the input
Option 2, check the e.target .
if(e.target is input) use
fileImput.files[0] = e.target.files[0];
else use
fileImput.files[0] = e.target.parentElement.querySelector('#thumbnail-input').files[0];

Related

Replace CHOOSE FILE with an image

Hi I am trying to replace the choose file button with an image. I am using javascript to create the button but when I am inspecting the website, it shows me a html script of the button which is of type= file.
To create it, I used:
input = createFileInput(handleFile);
input.elt.style["width"] = "40%";
input.elt.style["font-size"]="3vmin";
function handleFile(file) {
print(file);
if (file.type === 'image') {
imgFile = file.data;
img = createImg(file.data);
img.hide();
canvas.image(img, 0, 0, 224, 224);
image(img, 0, 0, width, height/2);
img.remove();
}
mode = 1;
tint = false;
}
Can anyone suggest how I can change the generic button with an image.
I think you can cheat and position an image over the input, then add a click handler to the image and pass it through to the input button below.
Is this what you are trying to achieve?
const input = document.querySelector("#avatar");
const button = document.querySelector("#button");
button.addEventListener('click', event => input.click(event));
.body {
font-family: sans-serif;
}
label {
display: inline-block;
padding: 1em 0;
}
.wrapper {
position: relative;
}
#avatar {
display: block;
height: 50px;
border: 1px solid #333;
}
#button {
position: absolute;
left: 1px;
top: 1px;
}
<div class="body">
<label for="avatar">Choose a profile picture:</label>
<div class="wrapper">
<input type="file" id="avatar" name="avatar">
<img id="button" src="https://via.placeholder.com/75x50/333333/ffffff?text=Avatar"></img>
</div>
</div>

Cannot choose file by dropping file on area outside the button but inside input only when using Shadow DOM

In chrome, following input element can be dragged and dropped to the bottom area of the input.
I expect this behavior.
<input type="file" class="input-file">
<style>
.input-file {
display: block;
width: 300px;
height: 300px;
border: 1px solid red;
margin: 10px;
}
</style>
However, it is different inside Shadow DOM.
Following input element can be dragged and dropped to only the "choose file" button located the left top area of the input.
Dropped file is opened not chosen when file is droppped to the bottom area of the input.
customElements.define("foo-bar", class extends HTMLElement {
constructor(){
super()
this.attachShadow({mode: "open"}).innerHTML = `
<input type="file" class="input-file">
<style>
.input-file {
display: block;
width: 300px;
height: 300px;
border: 1px solid red;
margin: 10px;
}
</style>
`
}
})
document.body.innerHTML = "<foo-bar></foo-bar>"
I think that this is a bug caused by events not being notified outside of Shadow DOM.
Is there a workaround?
The workaround is to process the drop event.
this.shadowRoot.querySelector( 'input' ).ondrop = ev => {
console.log( 'dropped in', ev.dataTransfer.items.length )
for ( var i = 0 ; i < ev.dataTransfer.items.length ; i++ ) {
if (ev.dataTransfer.items[i].kind === 'file') {
var file = ev.dataTransfer.items[i].getAsFile()
console.log( 'file[' + i + '].name = ' + file.name )
}
}
ev.preventDefault()
}
Don't forget to invoke the preventDefault() method to abort the file opening.
I know this is an old question but the bug is still not fixed. I found a CSS workaround for Chrome to use the input button instead of the input for the draft & drop. In my case I used an opacity of 0 so the design is not an issue for me.
customElements.define("foo-bar", class extends HTMLElement {
constructor(){
super()
this.attachShadow({mode: "open"}).innerHTML = `
<input type="file" />
<style>
input {
display: block;
width: 300px;
height: 300px;
border: 1px solid red;
margin: 10px;
}
input::-webkit-file-upload-button {
width: 300px;
height: 300px;
}
</style>
`
}
})
<foo-bar></foo-bar>

Narrator should read "no search results" if there is no result while searching

I have a search box in my html page.
On enter key press - it filter out the data list to be shown below.
One of the screen reader requirement says that it should read out that No results are found when nothing matches.
As "no result found" is a non actionable element and ideally tab focus should not go that label. So how indicate that user of "No results found"
Not able to implement it using using
aria-label
aria-live
Sample Code :
HTML :
<input tabindex="1" type="text" id="textIn" />
<div tabindex="1" id="searchContent" style="width:100px;height:50px;" aria-live="assertive">
</div>
Javascript
$("#textIn").on('keydown', function (e) {
if(e.keyCode == '13') {
shout();
}
})
function shout() {
var searchContent = $('#searchContent');
var noResults = document.createElement('div');
noResults.innerHTML = '<label class="">No Results found</label>';
searchContent.append(noResults);
}
This ARIA alert support article addresses Narrator support. It references an alert test page so you can play around with the options.
I made a CodePen from the two examples that work in Narrator. The code can be optimized a lot, but it shows how role="alert" can be used in conjunction JS and CSS to do what you need.
HTML
<h2>Method 3: display error by Changing CSS display:none to inline</h2>
<p><input type="submit" value="Method 3 alert - display" onclick="displayError()"></p>
<h2>Method 4: display error by adding text using createTextNode()</h2>
<p><input type="submit" value="Method 4 alert - display" onclick="addError()"></p>
<div id="displayerror" class="display">
<div class="alert" id="displayerror1" role="alert">alert via display none to block</div>
</div>
<div id="display2" role="alert"><span id="add1"></span></div>
CSS
.display {
position: absolute;
top: 5px;
left: 200px;
height: 30px;
width: 200px;
}
#display2 {
position: absolute;
top: 5px;
left: 400px;
height: 30px;
width: 200px;
clip: rect(0px, 0px, 0px, 0px);
border: 1px dashed red;
text-align: center;
padding: 5px;
background: #ffff00;
font-weight: bold;
}
JS
function displayError() {
var elem = document.getElementById("displayerror");
document.getElementById('displayerror1').style.display = 'block';
}
function addError() {
var elem1 = document.getElementById("add1");
elem1.setAttribute("role", "alert");
document.getElementById('display2').style.clip = 'auto';
alertText = document.createTextNode("alert via createTextnode()");
elem1.appendChild(alertText);
elem1.style.display = 'none';
elem1.style.display = 'inline';
}

Display selected picture under it's button with multiple buttons

Sorry the title is not better. Here is where I'm at. I am trying to open an openFileDialog, then display the image I picked below that button. I can do it fine with one button and one image. However I can't seem to get the selected picture to show under only the appropriate button. My script looks like this.
$('.custom-upload input[type=file]').change(function() {
var parentCommentId = $(this).id
$(this).next().find('input').val($(this).val());
if (this.files && this.files[0]) {
var reader = new FileReader();
reader.onload = imageIsLoaded;
reader.readAsDataURL(this.files[0]);
}
function imageIsLoaded(e) {
$('img').attr('src', e.target.result);
$('img').fadeIn();
};
});
.custom-upload {
position: relative;
height: 40px;
width: 350px;
margin: 30px;
}
.custom-upload input[type=file] {
outline: none;
position: relative;
text-align: right;
-moz-opacity: 0;
filter: alpha(opacity: 0);
opacity: 0;
z-index: 2;
width: 100%;
height: 100%;
}
.custom-upload .fake-file {
background: url(http://www.fold3.com/i/upload-icon.png) center right no-repeat;
position: absolute;
top: 0px;
left: 0px;
width: 350px;
padding: 0;
margin: 0;
z-index: 1;
line-height: 100%;
}
.custom-upload .fake-file input {
font-size: 16px;
height: 40px;
width: 300px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div class="custom-upload" id="taco">
<input type="file" id="eatme">
<div class="fake-file">
<input disabled="disabled" id="damn">
</div>
<img id="myImg" src="#" style="display: none;height:400px;" width=100% height=100%/>
</div>
<br>
<div id="result"></div>
<div class="custom-upload">
<input type="file">
<div class="fake-file">
<input disabled="disabled">
</div>
<img id="myImg" src="#" alt="your image" style="display: none;" width=100% />
</div>
Using $("img") selects all img elements. You need to use DOM navigation methods to get from the input for which the change event occurred to its related img element.
$('.custom-upload input[type=file]').change(function() {
var $this = $(this); // store a reference to current input
$this.next().find('input').val($(this).val());
if (this.files && this.files[0]) {
var reader = new FileReader();
reader.onload = imageIsLoaded;
reader.readAsDataURL(this.files[0]);
}
function imageIsLoaded(e) {
$this.closest('.custom-upload')
.find('img')
.attr('src', e.target.result)
.fadeIn();
};
});
I've saved a reference to the current file input in the (new) variable $this. Then in the img onload handler I've used .closest() to navigate up to the containing div and then .find() to go back down to the img in that div.
(You could use $this.siblings("img") or $this.next().next(), but that would only work for your specific html structure as it is right now - using .closest() plus .find() will work even if you later add an extra (sub-)container div around the img or otherwise tweak the structure, just as long as the file input and img are still within the same containing div.)
Demo: http://jsfiddle.net/jndL3rj5/

Upload file styled button with preview feature in IE

I have styled filed upload button and added preview image before upload... basically everything works as a charm in all browsers except IE...
Now to bring you my idea closer it looks like:
http://postimage.org/gallery/22cvzh2g/bcd61d61/
Is there a reason why image isn't showing in IE? I tried in IE9, but I just get the path, while the $('#background-preview').removeClass('hidden'); seems not to be working as it's not removing class hidden...
...also in IE and Opera as file path you will note C:/fakepath/etc... while in FireFox, Chrome and normal browsers it displays just file name. Any help is highly appreciated!
Now in header I have:
<script>
function clearFileInput() {
var oldInput = document.getElementById("upload-bg");
var newInput = document.createElement("input");
newInput.type = "file";
newInput.id = oldInput.id;
newInput.name = oldInput.name;
newInput.onchange = oldInput.onchange;
newInput.className = oldInput.className;
newInput.style.cssText = oldInput.style.cssText;
// copy any other relevant attributes
oldInput.parentNode.replaceChild(newInput, oldInput);
$('#background-preview').addClass('hidden');
var oldInput1 = document.getElementById("FileField");
var newInput2 = document.createElement("input");
newInput2.type = "text";
newInput2.id = oldInput1.id;
newInput2.className = oldInput1.className;
newInput2.style.cssText = oldInput1.style.cssText;
oldInput1.parentNode.replaceChild(newInput2, oldInput1);
}
function readURL(input) {
if (input.files && input.files[0]) {
var reader = new FileReader();
reader.onload = function (e) {
$('#background-image')
.attr('src', e.target.result)
.width(250)
.height(170);
$('#background-preview').removeClass('hidden');
};
reader.readAsDataURL(input.files[0]);
}
}
In body section where actual button is:
<div id="FileUpload">
<input id="upload-bg" type='file' onchange="readURL(this);getElementById('FileField').value = getElementById('upload-bg').value;" />
<div id="BrowserVisible"><input type="text" id="FileField" /></div>
</div>
<div id="background-preview" class="hidden"><img id="background-image" src="#" alt="Bad Image File !" /> </div>
And the CSS that takes care for the customizing file input is:
#FileUpload {
position:relative;
height: 50px;
}
#BrowserVisible {
margin: 5px 0px 5px 0px;
position: absolute;
top: 0px;
left: 0px;
z-index: 1;
background:url(images/button-browse.png) 100% 0px no-repeat;
height:42px;
width:290px;
}
#FileField {
border: 1px solid #BDBDBD;
font-size: 13px;
height: 40px;
margin: 0;
padding: 0;
width: 215px;
}
#upload-bg {
position:relative;
width:290px;
height:43px;
text-align: right;
-moz-opacity:0;
filter:alpha(opacity: 0);
opacity: 0;
z-index: 2;
}
#clear-bg-upload {
position: absolute;
top: 0px;
right: 0px;
width: 16px;
height: 16px;
background: url(images/icon-delete-input.png) top center no-repeat;
}
#background-preview {
border: solid 1px #ccc;
padding: 5px;
position: relative;
display: inline-block;
border-radius: 5px;
-o-border-radius: 5px;
-icab-border-radius: 5px;
-khtml-border-radius: 5px;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
}
#background-preview.hidden {
display: none;
}
#background-preview img {
margin: 0px auto;
display: block;
max-height: 140px;
max-width: 180px;
width: auto;
}
----------------------------- EDITED -------------------------------
Ok, I went to different approach via Ajax (using) upload and all is wonderful... I just can't figure how to send field value only. Right now it's sent like form, but is there a way to trigger send only the field. Right now it's warped in #FileUploadForm, but I want to use this within a form and since forms can't be nested... I am kind of stuck... except having two forms like I have now, but I would like that file upload filed to be sent like it is now, just without having to wrap it in it's own form.
This is script I am using:
function showRequest(formData, jqForm, options) {
var fileToUploadValue = $('#fileToUpload').fieldValue();
if (!fileToUploadValue[0]) {
$('#result').html('Please select a file.');
return false;
}
$("#loading").show();
return true;
}
function showResponse(data, statusText) {
$("#loading").hide();
if (statusText == 'success') {
var msg = data.error.replace("##", "<br />");
if (data.img != '') {
$('#result').removeClass('hiddenmessage');
$('#result').html('<img src="uploads/thumbs/' + data.img + '" /> ');
// $('#message').html('Click here');
// $('#FileUploadForm').html('');
} else {
$('#result').removeClass('hiddenmessage');
$('#result').html(msg);
}
} else {
$('#result').removeClass('hiddenmessage');
$('#result').html('Unknown error!');
}
}
function StartFileUpload() {
$('#message').html('');
$('#FileUploadForm').ajaxSubmit({
beforeSubmit: showRequest,
success: showResponse,
url: 'upload.php',
dataType: 'json'
});
return false;
}
$('#fileToUpload').live('change', function () {
StartFileUpload();
});
Let's start with paths — in HTML/JS, you can't get full path to attached file due to security concerns. All you can get is file name.
Usually the best approach is to upload file to server with JavaScript when user selects file and then grab preview from that server. You could provide some "delete" button which would enable users to remove pics they uploaded by mistake.
This would enforce deep changes in your application. I recommend File Uploader plugin. Writing your own solution from scratch will be very painful because it requires many hacks for different browsers.

Categories