I'm building a drag-and-drop-to-upload web application using HTML5, and I'm dropping the files onto a div and of course fetching the dataTransfer object, which gives me the FileList.
Now I want to remove some of the files, but I don't know how, or if it's even possible.
Preferably I'd like to just delete them from the FileList; I've got no use for them. But if that's not possible, should I instead write in checks in code that interacts with the FileList? That seems cumbersome.
If you want to delete only several of the selected files: you can't. The File API Working Draft you linked to contains a note:
The HTMLInputElement interface
[HTML5] has a readonly FileList
attribute, […]
[emphasis mine]
Reading a bit of the HTML 5 Working Draft, I came across the Common input element APIs. It appears you can delete the entire file list by setting the value property of the input object to an empty string, like:
document.getElementById('multifile').value = "";
BTW, the article Using files from web applications might also be of interest.
Since JavaScript FileList is readonly and cannot be manipulated directly,
BEST METHOD
You will have to loop through the input.files while comparing it with the index of the file you want to remove. At the same time, you will use new DataTransfer() to set a new list of files excluding the file you want to remove from the file list.
With this approach, the value of the input.files itself is changed.
removeFileFromFileList(index) {
const dt = new DataTransfer()
const input = document.getElementById('files')
const { files } = input
for (let i = 0; i < files.length; i++) {
const file = files[i]
if (index !== i)
dt.items.add(file) // here you exclude the file. thus removing it.
}
input.files = dt.files // Assign the updates list
}
ALTERNATIVE METHOD
Another simple method is to convert the FileList into an array and then splice it.
But this approach will not change the input.files
const input = document.getElementById('files')
// as an array, u have more freedom to transform the file list using array functions.
const fileListArr = Array.from(input.files)
fileListArr.splice(index, 1) // here u remove the file
console.log(fileListArr)
This question has already been marked answered, but I'd like to share some information that might help others with using FileList.
It would be convenient to treat a FileList as an array, but methods like sort, shift, pop, and slice don't work. As others have suggested, you can copy the FileList to an array. However, rather than using a loop, there's a simple one line solution to handle this conversion.
// fileDialog.files is a FileList
var fileBuffer=[];
// append the file list to an array
Array.prototype.push.apply( fileBuffer, fileDialog.files ); // <-- here
// And now you may manipulated the result as required
// shift an item off the array
var file = fileBuffer.shift(0,1); // <-- works as expected
console.info( file.name + ", " + file.size + ", " + file.type );
// sort files by size
fileBuffer.sort(function(a,b) {
return a.size > b.size ? 1 : a.size < b.size ? -1 : 0;
});
Tested OK in FF, Chrome, and IE10+
If you are targeting evergreen browsers (Chrome, Firefox, Edge, but also works in Safari 9+) or you can afford a polyfill, you can turn the FileList into an array by using Array.from() like this:
let fileArray = Array.from(fileList);
Then it's easy to handle the array of Files like any other array.
Since we are in the HTML5 realm, this is my solution. The gist is that you push the files to an Array instead of leaving them in a FileList, then using XHR2, you push the files to a FormData object. Example below.
Node.prototype.replaceWith = function(node)
{
this.parentNode.replaceChild(node, this);
};
if(window.File && window.FileList)
{
var topicForm = document.getElementById("yourForm");
topicForm.fileZone = document.getElementById("fileDropZoneElement");
topicForm.fileZone.files = new Array();
topicForm.fileZone.inputWindow = document.createElement("input");
topicForm.fileZone.inputWindow.setAttribute("type", "file");
topicForm.fileZone.inputWindow.setAttribute("multiple", "multiple");
topicForm.onsubmit = function(event)
{
var request = new XMLHttpRequest();
if(request.upload)
{
event.preventDefault();
topicForm.ajax.value = "true";
request.upload.onprogress = function(event)
{
var progress = event.loaded.toString() + " bytes transfered.";
if(event.lengthComputable)
progress = Math.round(event.loaded / event.total * 100).toString() + "%";
topicForm.fileZone.innerHTML = progress.toString();
};
request.onload = function(event)
{
response = JSON.parse(request.responseText);
// Handle the response here.
};
request.open(topicForm.method, topicForm.getAttribute("action"), true);
var data = new FormData(topicForm);
for(var i = 0, file; file = topicForm.fileZone.files[i]; i++)
data.append("file" + i.toString(), file);
request.send(data);
}
};
topicForm.fileZone.firstChild.replaceWith(document.createTextNode("Drop files or click here."));
var handleFiles = function(files)
{
for(var i = 0, file; file = files[i]; i++)
topicForm.fileZone.files.push(file);
};
topicForm.fileZone.ondrop = function(event)
{
event.stopPropagation();
event.preventDefault();
handleFiles(event.dataTransfer.files);
};
topicForm.fileZone.inputWindow.onchange = function(event)
{
handleFiles(topicForm.fileZone.inputWindow.files);
};
topicForm.fileZone.ondragover = function(event)
{
event.stopPropagation();
event.preventDefault();
};
topicForm.fileZone.onclick = function()
{
topicForm.fileZone.inputWindow.focus();
topicForm.fileZone.inputWindow.click();
};
}
else
topicForm.fileZone.firstChild.replaceWith(document.createTextNode("It's time to update your browser."));
I have found very quick & short workaround for this. Tested in many popular browsers (Chrome, Firefox, Safari);
First, you have to convert FileList to an Array
var newFileList = Array.from(event.target.files);
to delete the particular element use this
newFileList.splice(index,1);
I know this is an old question but it's ranking high on search engines in regards to this issue.
properties in the FileList object cannot be deleted but at least on Firefox they can be changed. My workaround this issue was to add a property IsValid=true to those files that passed check and IsValid=false to those that didn't.
then I just loop through the list to make sure that only the properties with IsValid=true are added to FormData.
This is extemporary, but I had the same problem which I solved this way. In my case I was uploading the files via XMLHttp request, so I was able to post the FileList cloned data through formdata appending. Functionality is that you can drag and drop or select multiple files as many times as you want (selecting files again won't reset the cloned FileList), remove any file you want from the (cloned) file list, and submit via xmlhttprequest whatever was left there. This is what I did. It is my first post here so code is a little messy. Sorry. Ah, and I had to use jQuery instead of $ as it was in Joomla script.
// some global variables
var clon = {}; // will be my FileList clone
var removedkeys = 0; // removed keys counter for later processing the request
var NextId = 0; // counter to add entries to the clone and not replace existing ones
jQuery(document).ready(function(){
jQuery("#form input").change(function () {
// making the clone
var curFiles = this.files;
// temporary object clone before copying info to the clone
var temparr = jQuery.extend(true, {}, curFiles);
// delete unnecessary FileList keys that were cloned
delete temparr["length"];
delete temparr["item"];
if (Object.keys(clon).length === 0){
jQuery.extend(true, clon, temparr);
}else{
var keysArr = Object.keys(clon);
NextId = Math.max.apply(null, keysArr)+1; // FileList keys are numbers
if (NextId < curFiles.length){ // a bug I found and had to solve for not replacing my temparr keys...
NextId = curFiles.length;
}
for (var key in temparr) { // I have to rename new entries for not overwriting existing keys in clon
if (temparr.hasOwnProperty(key)) {
temparr[NextId] = temparr[key];
delete temparr[key];
// meter aca los cambios de id en los html tags con el nuevo NextId
NextId++;
}
}
jQuery.extend(true, clon, temparr); // copy new entries to clon
}
// modifying the html file list display
if (NextId === 0){
jQuery("#filelist").html("");
for(var i=0; i<curFiles.length; i++) {
var f = curFiles[i];
jQuery("#filelist").append("<p id=\"file"+i+"\" style=\'margin-bottom: 3px!important;\'>" + f.name + "<a style=\"float:right;cursor:pointer;\" onclick=\"BorrarFile("+i+")\">x</a></p>"); // the function BorrarFile will handle file deletion from the clone by file id
}
}else{
for(var i=0; i<curFiles.length; i++) {
var f = curFiles[i];
jQuery("#filelist").append("<p id=\"file"+(i+NextId-curFiles.length)+"\" style=\'margin-bottom: 3px!important;\'>" + f.name + "<a style=\"float:right;cursor:pointer;\" onclick=\"BorrarFile("+(i+NextId-curFiles.length)+")\">x</a></p>"); // yeap, i+NextId-curFiles.length actually gets it right
}
}
// update the total files count wherever you want
jQuery("#form p").text(Object.keys(clon).length + " file(s) selected");
});
});
function BorrarFile(id){ // handling file deletion from clone
jQuery("#file"+id).remove(); // remove the html filelist element
delete clon[id]; // delete the entry
removedkeys++; // add to removed keys counter
if (Object.keys(clon).length === 0){
jQuery("#form p").text(Object.keys(clon).length + " file(s) selected");
jQuery("#fileToUpload").val(""); // I had to reset the form file input for my form check function before submission. Else it would send even though my clone was empty
}else{
jQuery("#form p").text(Object.keys(clon).length + " file(s) selected");
}
}
// now my form check function
function check(){
if( document.getElementById("fileToUpload").files.length == 0 ){
alert("No file selected");
return false;
}else{
var _validFileExtensions = [".pdf", ".PDF"]; // I wanted pdf files
// retrieve input files
var arrInputs = clon;
// validating files
for (var i = 0; i < Object.keys(arrInputs).length+removedkeys; i++) {
if (typeof arrInputs[i]!="undefined"){
var oInput = arrInputs[i];
if (oInput.type == "application/pdf") {
var sFileName = oInput.name;
if (sFileName.length > 0) {
var blnValid = false;
for (var j = 0; j < _validFileExtensions.length; j++) {
var sCurExtension = _validFileExtensions[j];
if (sFileName.substr(sFileName.length - sCurExtension.length, sCurExtension.length).toLowerCase() == sCurExtension.toLowerCase()) {
blnValid = true;
break;
}
}
if (!blnValid) {
alert("Sorry, " + sFileName + " is invalid, allowed extensions are: " + _validFileExtensions.join(", "));
return false;
}
}
}else{
alert("Sorry, " + arrInputs[0].name + " is invalid, allowed extensions are: " + _validFileExtensions.join(" or "));
return false;
}
}
}
// proceed with the data appending and submission
// here some hidden input values i had previously set. Now retrieving them for submission. My form wasn't actually even a form...
var fecha = jQuery("#fecha").val();
var vendor = jQuery("#vendor").val();
var sku = jQuery("#sku").val();
// create the formdata object
var formData = new FormData();
formData.append("fecha", fecha);
formData.append("vendor", encodeURI(vendor));
formData.append("sku", sku);
// now appending the clone file data (finally!)
var fila = clon; // i just did this because I had already written the following using the "fila" object, so I copy my clone again
// the interesting part. As entries in my clone object aren't consecutive numbers I cannot iterate normally, so I came up with the following idea
for (i = 0; i < Object.keys(fila).length+removedkeys; i++) {
if(typeof fila[i]!="undefined"){
formData.append("fileToUpload[]", fila[i]); // VERY IMPORTANT the formdata key for the files HAS to be an array. It will be later retrieved as $_FILES['fileToUpload']['temp_name'][i]
}
}
jQuery("#submitbtn").fadeOut("slow"); // remove the upload btn so it can't be used again
jQuery("#drag").html(""); // clearing the output message element
// start the request
var xhttp = new XMLHttpRequest();
xhttp.addEventListener("progress", function(e) {
var done = e.position || e.loaded, total = e.totalSize || e.total;
}, false);
if ( xhttp.upload ) {
xhttp.upload.onprogress = function(e) {
var done = e.position || e.loaded, total = e.totalSize || e.total;
var percent = done / total;
jQuery("#drag").html(Math.round(percent * 100) + "%");
};
}
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
var respuesta = this.responseText;
jQuery("#drag").html(respuesta);
}
};
xhttp.open("POST", "your_upload_handler.php", true);
xhttp.send(formData);
return true;
}
};
Now the html and styles for this. I'm quite a newbie but all this actually worked for me and took me a while to figure it out.
<div id="form" class="formpos">
<!-- Select the pdf to upload:-->
<input type="file" name="fileToUpload[]" id="fileToUpload" accept="application/pdf" multiple>
<div><p id="drag">Drop your files here or click to select them</p>
</div>
<button id="submitbtn" onclick="return check()" >Upload</button>
// these inputs are passed with different names on the formdata. Be aware of that
// I was echoing this, so that's why I use the single quote for php variables
<input type="hidden" id="fecha" name="fecha_copy" value="'.$fecha.'" />
<input type="hidden" id="vendor" name="vendorname" value="'.$vendor.'" />
<input type="hidden" id="sku" name="sku" value="'.$sku.'"" />
</div>
<h1 style="width: 500px!important;margin:20px auto 0px!important;font-size:24px!important;">File list:</h1>
<div id="filelist" style="width: 500px!important;margin:10px auto 0px!important;">Nothing selected yet</div>
The styles for that. I had to mark some of them !important to override Joomla behavior.
.formpos{
width: 500px;
height: 200px;
border: 4px dashed #999;
margin: 30px auto 100px;
}
.formpos p{
text-align: center!important;
padding: 80px 30px 0px;
color: #000;
}
.formpos div{
width: 100%!important;
height: 100%!important;
text-align: center!important;
margin-bottom: 30px!important;
}
.formpos input{
position: absolute!important;
margin: 0!important;
padding: 0!important;
width: 500px!important;
height: 200px!important;
outline: none!important;
opacity: 0!important;
}
.formpos button{
margin: 0;
color: #fff;
background: #16a085;
border: none;
width: 508px;
height: 35px;
margin-left: -4px;
border-radius: 4px;
transition: all .2s ease;
outline: none;
}
.formpos button:hover{
background: #149174;
color: #0C5645;
}
.formpos button:active{
border:0;
}
I hope this helps.
Thanks #Nicholas Anderson simple and straight , here is your code applied and working at my code using jquery.
HTML .
<input class="rangelog btn border-aero" id="file_fr" name="file_fr[]" multiple type="file" placeholder="{$labels_helpfiles_placeholder_file}">
<span style="cursor: pointer; cursor: hand;" onclick="cleanInputs($('#file_fr'))"><i class="fa fa-trash"></i> Empty chosen files</span>
JS CODE
function cleanInputs(fileEle){
$(fileEle).val("");
var parEle = $(fileEle).parent();
var newEle = $(fileEle).clone()
$(fileEle).remove();
$(parEle).prepend(newEle);
}
There might be a more elegant way to do this but here is my solution. With Jquery
fileEle.value = "";
var parEle = $(fileEle).parent();
var newEle = $(fileEle).clone()
$(fileEle).remove();
parEle.append(newEle);
Basically you cleat the value of the input. Clone it and put the clone in place of the old one.
If you have the luck to be sending a post request to the database with the files and you have the files you want to send in your DOM
you can simply check if the file in the file list is present in your DOM, and of course if it's not you just don't send that element to de DB.
I realize this is a pretty old question, however I am using an html multiple file selection upload to queue any number of files which can be selectively removed in a custom UI before submitting.
Save files in a variable like this:
let uploadedFiles = [];
//inside DOM file select "onChange" event
let selected = e.target.files[0] ? e.target.files : [];
uploadedFiles = [...uploadedFiles , ...selected ];
createElements();
Create UI with "remove a file":
function createElements(){
uploadedFiles.forEach((f,i) => {
//remove DOM elements and re-create them here
/* //you can show an image like this:
* let reader = new FileReader();
* reader.onload = function (e) {
* let url = e.target.result;
* // create <img src=url />
* };
* reader.readAsDataURL(f);
*/
element.addEventListener("click", function () {
uploadedFiles.splice(i, 1);
createElements();
});
}
}
Submit to server:
let fd = new FormData();
uploadedFiles.forEach((f, i) => {
fd.append("files[]", f);
});
fetch("yourEndpoint", {
method: "POST",
body: fd,
headers: {
//do not set Content-Type
}
}).then(...)
I mix the solutions of many developers and reach to this solution.
It change the original array list after deletion which means if we want to save the images then we can do so.
<script>
var images = [];
function image_select() {
var image = document.getElementById('image').files;
for (i = 0; i < image.length; i++) {
images.push({
"name" : image[i].name,
"url" : URL.createObjectURL(image[i]),
"file" : image[i],
})
}
document.getElementById('container').innerHTML = image_show();
}
function image_show() {
var image = "";
images.forEach((i) => {
image += `<div class="image_container d-flex justify-content-center position-relative">
<img src="`+ i.url +`" alt="Image">
<span class="position-absolute" onclick="delete_image(`+ images.indexOf(i) +`)">×</span>
</div>`;
})
return image;
}
function delete_image(e) {
images.splice(e, 1);
document.getElementById('container').innerHTML = image_show();
const dt = new DataTransfer()
const input = document.getElementById('image')
const { files } = input
for (let i = 0; i < files.length; i++) {
const file = files[i]
if (e !== i)
dt.items.add(file);
}
input.files = dt.files;
console.log(document.getElementById('image').files);
}
</script>
*******This is html code ******
<body>
<div class="container mt-3 w-100">
<div class="card shadow-sm w-100">
<div class="card-header d-flex justify-content-between">
<h4>Preview Multiple Images</h4>
<form class="form" action="{{route('store')}}" method="post" id="form" enctype="multipart/form-data">
#csrf
<input type="file" name="image[]" id="image" multiple onchange="image_select()">
<button class="btn btn-sm btn-primary" type="submit">Submit</button>
</form>
</div>
<div class="card-body d-flex flex-wrap justify-content-start" id="container">
</div>
</div>
</div>
</body>
******* This is CSS code ********
<style>
.image_container {
height: 120px;
width: 200px;
border-radius: 6px;
overflow: hidden;
margin: 10px;
}
.image_container img {
height: 100%;
width: auto;
object-fit: cover;
}
.image_container span {
top: -6px;
right: 8px;
color: red;
font-size: 28px;
font-weight: normal;
cursor: pointer;
}
</style>
You may wish to create an array and use that instead of the read-only filelist.
var myReadWriteList = new Array();
// user selects files later...
// then as soon as convenient...
myReadWriteList = FileListReadOnly;
After that point do your uploading against your list instead of the built in list. I am not sure of the context you are working in but I am working with a jquery plugin I found and what I had to do was take the plugin's source and put it in the page using <script> tags. Then above the source I added my array so that it can act as a global variable and the plugin could reference it.
Then it was just a matter of swapping out the references.
I think this would allow you to also add drag & drop as again, if the built in list is read-only then how else could you get the dropped files into the list?
:))
I solve it this way
//position -> the position of the file you need to delete
this.fileImgs.forEach((item, index, object) => {
if(item.idColor === idC){
if(item.imgs.length === 1){
object.splice(index,1) }
else{
const itemFileImgs = [...item.imgs];
itemFileImgs.splice(position,1)
item.imgs = [...itemFileImgs]
}
}});
console.log(this.fileImgs)
In vue js :
self.$refs.inputFile.value = ''
I just change the type of input to the text and back to the file :D
Related
I am trying to link my HTML form with my csv file to populate form field automatically. Based on what user selects in first field, second field should be automatically filled with the appropriate value. when the user starts typing in the first field, the input field automatically pulls data from csv file to show available options. Options appear after user completes writing 3 words in the field.
Further, to avoid any CORS issue in code, I have added additional URL in my CSV file URL which makes it accessible by any web application.
I was able to prepare this code with the help of examples available on web. However, my code is not working properly. I tried to solve this problem on my own. But I don't know about coding enough.
Can anyone please help me to solve this problem.
<script>
$(function() { function processData(allText) { var record_num = 2;
// or however many elements there are in each row
var allTextLines = allText.split(/\r\n|\n/); var lines = []; var headings = allTextLines.shift().split(','); while (allTextLines.length > 0) { var tobj = {}, entry; entry = allTextLines.shift().split(','); tobj['label'] = entry[0]; tobj['value'] = entry[1]; lines.push(tobj); } return lines; }
// Storage for lists of CSV Data
var lists = [];
// Get the CSV Content
$.get("https://cors-anywhere.herokuapp.com/www.coasilat.com/wp-content/uploads/2019/06/file.txt ", function(data) { lists = processData(data); }); $("#species").autocomplete({ minLength: 3, source: lists, select: function(event, ui) { $("#species").val(ui.item.label); $("#identifiant").val(ui.item.value); return false; } }); });)
</script>
<script src="http://code.jquery.com/jquery-1.10.1.min.js"></script>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
<form>
<div class="ui-widget"> <label for="species">Species: </label> <input id="species"> <label for="identifiant">Identifiant: </label> <input id="identifiant" style="width: 6em;"> </div></form>
Here's the modified answer, working with jquery-ui autocomplete.
The solution: the $.get() is an asynchronous function (the data is not readily available on page load), so jquery-ui autocomplete didn't work with the updated lists[] array, because it (seems so that it) doesn't work with dynamically generated data. So the source of autocomplete had to be refreshed with the newly arrived data in the $.get()'s callback function.
$("#species").autocomplete('option', 'source', lists) - this is the key line, as it updates autocomplete's source with the new data.
// Only needed for working example
var myCSV = "Species,Identifiant\r\n";
myCSV += "Species A,320439\r\n";
myCSV += "Species B,349450\r\n";
myCSV += "Species C,43435904\r\n";
myCSV += "Species D,320440\r\n";
myCSV += "Species E,349451\r\n";
myCSV += "Species F,43435905\r\n";
console.log(myCSV);
// Begin jQuery Code
$(function() {
function processData(allText) {
// var record_num = 2; // or however many elements there are in each row
var allTextLines = allText.split(/\r\n|\n/);
var lines = [];
var headings = allTextLines.shift().split(',');
while (allTextLines.length > 0) {
var tobj = {},
entry;
entry = allTextLines.shift().split(',');
/*
Normally we'd read the headers into the object.
Since we will be using Autocomplete, it's looking for an array of objects with 'label' and 'value' properties.
tobj[headings[0]] = entry[0];
tobj[headings[1]] = entry[1];
*/
if (typeof entry[1] !== 'undefined') {
let prefix = !entry[0].includes('Species') ? 'Species ' : ''
tobj['label'] = prefix + entry[0];
tobj['value'] = entry[1].trim();
lines.push(tobj);
}
}
return lines;
}
let lists = [];
// For working example
// lists = processData(myCSV);
// console.log('lists1', lists)
// In your script you will get this content from the CSV File
// Get the CSV Content
$.get("https://cors-anywhere.herokuapp.com/www.coasilat.com/wp-content/uploads/2019/06/file.txt", function(data) {
lists = processData(data);
$("#species").autocomplete('option', 'source', lists)
console.log('lists2', lists)
});
$("#species").autocomplete({
minLength: 3,
source: lists,
focus: function(event, ui) {
console.log(ui)
$("#species").val(ui.item.label);
return false;
},
select: function(event, ui) {
$("#species").val(ui.item.label);
$("#identifiant").val(ui.item.value);
return false;
}
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<link href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css" rel="stylesheet" />
<div class="ui-widget">
<label for="species">Species: </label>
<input id="species">
<label for="identifiant">Identifiant: </label>
<input id="identifiant" style="width: 6em;">
</div>
The processData() function didn't work as expected with the source you provided, so that had to be modified too.
My solution is a kinda' autocomplete - it's called typeahead.
I displayed the filtered list, so you see what's happening, but you can place that anywhere - in a dropdown below the input field, for example.
$(function() {
// processing CSV data
function processData(allText) {
// splitting lines
var allTextLines = allText.split(/\r\n|\n/);
const speciesData = []
// reading data into array, if it's not the first row (CSV header) AND
// if it's not 'Species'
let j = 0; // this will be the item's index
for (let i = 0; i < allTextLines.length - 1; i++) {
if (i !== 0 && allTextLines[i] !== 'Species') {
const record = allTextLines[i].split(',')
speciesData.push({
label: record[0],
value: record[1].trim(), // it has a lot of whitespace
index: j // adding this, so we can keep track of items
})
j++; // incrementing index
}
}
// returning processed data
return speciesData;
}
// Storage for lists of processed CSV Data
let lists = [];
// Get the CSV Content
$.get("https://cors-anywhere.herokuapp.com/www.coasilat.com/wp-content/uploads/2019/06/file.txt ", function(data) {
// making processed data availabel app-wide
lists = processData(data);
// filling the 'suggestions list' the first time
suggestionListHtml(lists, $('.suggestions-container'))
});
// actions on input field input event
// only the third param differs in filterSpecies()
$('#species').on('input', function(e) {
const filteredList = filterSpecies($(this).val(), lists, 'label')
suggestionListHtml(filteredList, $('.suggestions-container'))
})
$('#identifiant').on('input', function(e) {
const filteredList = filterSpecies($(this).val(), lists, 'value')
suggestionListHtml(filteredList, $('.suggestions-container'))
})
// clicking on an item in the 'suggestions list' fills out the input fields
$('.suggestions-container').on('click', '.suggestion', function(e) {
const item = lists[$(this).attr('data-listindex')]
$('#species').val(item.label)
$('#identifiant').val(item.value)
})
});
function suggestionListHtml(filteredList, container) {
// creating HTML template for the 'suggestions list'
let html = ''
filteredList.forEach(item => {
html += `<span class="suggestion" data-listindex="${item.index}">label: ${item.label} - value: ${item.value}</span>`
})
// modifying the displayed 'suggestions list'
container
.empty()
.append(html)
}
// filtering the processed list
// #param substr - the text from the input field
// #param list - the list to be filtered
// #param attr - one of the keys in the processed list (label or value)
function filterSpecies(substr, list, attr) {
// doing the actual filtering
const filteredList = list.filter(item => {
return item[attr].toLowerCase().includes(substr.toLowerCase())
})
return filteredList
}
.suggestions-container span {
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<form>
<div class="ui-widget">
<label for="species">Species: </label>
<input id="species">
<label for="identifiant">Identifiant: </label>
<input id="identifiant" style="width: 6em;">
</div>
<div class="suggestions-container">
</div>
</form>
Just trying to get some practice working with public APIs in Javascript. All I want to do is, when you submit the query, have the list of items I get back be sorted alphabetically by the h4 value (food name). I tried calling .sort() on the response item before calling .map(). Tried directly sorting the div elements after the fact using jquery, but in both cases, I just get a blank page back. I'm very new to javascript and writing this all in Notepad++, so there's probably a much better way to be doing all this, but I'm not aware of it.
Html code:
<!DOCTYPE html>
<html>
<form action="search">
<label>Search</label>
<input type="text">
<input type="submit">
<input type="reset">
</form>
<div class="result">
<h3>Result</h3>
</div>
</html>
Css code:
.result{
display:flex;
flex-wrap:wrap;
}
.item{
min-width:200px;
margin:20px auto;
}
JavaScript code:
var apiKey = "4b314d3e9171fbe99c6cdf16127ba93e";
var apiId = "4c143c55";
var queryItem;
var url = 'https://trackapi.nutritionix.com/v2/search/instant?query=';
var form = document.querySelector('form');
var input = document.querySelector('input[type="text"]');
var result = document.querySelector('.result');
function search(e){
e.preventDefault();
queryItem = input.value;
makeRequest(queryItem);
input.value= "";
}
function reset(){
var node = document.querySelector('.result');
while (node.firstChild) {
node.removeChild(node.firstChild);
}
}
function createFood(name, qty, unit, photo){
var item = document.createElement('div');
var foodName = document.createElement('h4');
var serving = document.createElement('p');
var img = document.createElement('img');
item.classList.add('item');
foodName.innerHTML = name;
serving.innerHTML = qty+' '+unit;
img.src = photo;
result.appendChild(item);
item.appendChild(img);
item.appendChild(foodName);
item.appendChild(serving)
}
function makeRequest(queryItem) {
reset();
xhr = new XMLHttpRequest();
xhr.onload = function() {
var response = JSON.parse(this.responseText);
response.common.map(function(food){
createFood(food.food_name,
food.serving_qty,
food.serving_unit,
food.photo.thumb
)
})
};
xhr.open(
"GET",
url+queryItem,
true
);
xhr.setRequestHeader('x-app-id',apiId);
xhr.setRequestHeader('x-app-key',apiKey);
xhr.send();
}
form.addEventListener('submit', search)
form.addEventListener('reset', reset)
You can use a generic function to sort by the attribute of your choice.
function sortByAttrAlphabeticallyASC(arr, attr) {
return arr.sort((a, b) =>
a[attr].toLocaleLowerCase().localeCompare(b[attr].toLocaleLowerCase())
);
}
Where arr is the array of Objects you want to sort and attr is the name of the attribute that will be compared.
As you can see here Array has a method that you can pass a callback with the comparation.
And String has a method to compare Strings as seen here witch can be passed inside Array::sort for comparation purposes.
In your case you would do:
sortByAttrAlphabeticallyASC(response.common, 'name').forEach(food => createFood(food.food_name,
food.serving_qty,
food.serving_unit,
food.photo.thumb
))
I'm working on a project with the following steps:
Creating the form with multiple images upload
Previewing, remove images on queue before upload
Handling the file input value before submit
Here is my code
var fileList = [];
function toObject(arr) {
var rv = {};
for (var i = 0; i < arr.length; ++i)
rv[i] = arr[i];
return rv;
}
//Image prev
// Multiple images preview in browser
var imagesPreview = function (input, imageContainer) {
if (input.files) {
var filesAmount = input.files.length;
$(imageContainer).html('');
for (i = 0; i < filesAmount; i++) {
var reader = new FileReader();
reader.onload = function (event) {
var html = '<div class="image-item col-sm-6 col-md-4 col-lg-3"><div class="image-wrapper">' +
' <img src="'
+ event.target.result + '"/></div></div>';
$(html).appendTo(imageContainer);
}
var files = input.files;
fileList.push(files[i]);
reader.readAsDataURL(input.files[i]);
}
input.files = toObject(fileList);
}
};
$('#input-image').on('change', function () {
imagesPreview(this, '.image-container');
});
<div class="image-item">
<!-- input the image from user -->
<input id="input-image" type="file" name="photos[]" multiple>
<hr>
<div class="image-container row">
<!-- Previewing the image thumbnail -->
</div>
</div>
My questions: Can I set the value of the input-image with fileList variable because I set it but error occurs
Sry, a bit tired, will not go into depth or solve the hole thing...
There is only one way to change the value of file inputs and that is with another FileList instance. the only way you can get them is with some new api's so it won't work in all browsers. I made a function to help you with that.
var fileList = [];
// only way to change input[type=file] value is with a other FileList instance
// and this is the only way to construct a new FileList
function createFileList(a) {
a = [].slice.call(Array.isArray(a) ? a : arguments)
for (var c, b = c = a.length, d = !0; b-- && d;) d = a[b] instanceof File
if (!d) throw new TypeError('expected argument to FileList is File or array of File objects')
for (b = (new ClipboardEvent('')).clipboardData || new DataTransfer; c--;) b.items.add(a[c])
return b.files
}
// This is what you got to do later when they remove a image
//
// this will trigger a change event so you maybe want to temporary
// turn off the change event listener
//
// input.files = createFileList(fileList)
// Image prev
// Multiple images preview in browser
function imagesPreview(input, imageContainer) {
$(imageContainer).empty();
var URL = window.URL || window.webkitURL;
var $html = $(
'<div class="image-item col-sm-6 col-md-4 col-lg-3">'+
'<div class="image-wrapper"> <img></div></div>'
);
$.each(input.files, function(i, file) {
var $clone = $html.clone()
// could be a good idea to revoke url also
$clone.find('img').attr('src', URL.createObjectURL(file))
$clone.appendTo(imageContainer);
fileList.push(file);
});
}
$('#input-image').on('change', function () {
imagesPreview(this, '.image-container');
});
The other solution is to append each file into a formdata and send the form using ajax
I'm trying to make a small script that allows for a little notes section. This section would have an input box that allows for adding elements to the list; which will be saved in localStorage so they are not lost when I refresh or close the browser. The code I have is as follows (it's all done through JS even the html, but ignore that.)
var notes = [];
var listthings = "<h2 id=\"titlething\">Notes</h2>" +
"<ul id=\"listing\">" +
"</ul>"
"<input type=\"text\" name=\"item\" id=\"textfield\">" +
"<input type=\"submit\" id=\"submitthing\" value=\"Submit\">";
JSON.parse(localStorage.getItem('notes')) || [].forEach( function (note) {
"<li id=\"listitem\">" + notes + "</li>";
})
$('#submitthing').click(function() {
notes.push($('#textfield').val());
});
localStorage.setItem('notes', JSON.stringify(notes));
Also, how would I go about appending the latest added li between the opening and closing tag? Obviously I'd usually do it using jQuery, but this is puzzling me a little. However, only the 'Notes' loads at the top, any ideas?
Your approach is way off the mark. You don't need JSON at all (this just confuses things) and you don't need to manually create HTML.
Also, you can use an array to store the notes, but since localStorage is the storage area, so an array is redundant. Additionally, without using an array, you don't need JSON. The entire problem becomes much easier to solve.
Unfortunately, the following won't run here in this snippet editor, due to security issues, but it would do what you are asking. This fiddle shows it working: https://jsfiddle.net/Lqjwbn1r/14/
// Upon the page being ready:
window.addEventListener("DOMContentLoaded", function(){
// Get a reference to the empty <ul> element on the page
var list = document.getElementById("notes");
// Loop through localStorage
for (var i = 0; i < localStorage.length; i++){
// Make sure that we only read the notes from local storage
if(localStorage.key(i).indexOf("note") !== -1){
// For each item, create a new <li> element
var item = document.createElement("li");
// Populate the <li> with the contents of the current
// localStorage item's value
item.textContent = localStorage.getItem(localStorage.key(i));
// Append the <li> to the page's <ul>
list.appendChild(item);
}
}
// Get references to the button and input
var btn = document.getElementById("btnSave");
var note = document.getElementById("txtNote");
// Store a note count:
var noteCount = 1;
// When the button is clicked...
btn.addEventListener("click", function(){
// Get the value of the input
var noteVal = note.value;
// As long as the value isn't an empty string...
if(noteVal.trim() !== ""){
// Create the note in localStorage using the
// note counter so that each stored item gets
// a unique key
localStorage.setItem("note" + noteCount, noteVal);
// Create a new <li>
var lstItem = document.createElement("li");
// Set the content of the <li>
lstItem.textContent = noteVal;
// Append the <li> to the <ul>
list.appendChild(lstItem);
// Bump up the note counter
noteCount++;
}
});
});
<input type=text id=txtNote><input type=button value=Save id=btnSave>
<ul id=notes></ul>
This is how I would approach it using jquery. but depens how complex this should be. this is just simple demo.
<input type="text" id="note" />
<button id="add">add note</button>
<ul id="notes"></ul>
javascript and jquery
function addNote(){
var data = localStorage.getItem("notes")
var notes = null;
if(data != null)
{
notes = JSON.parse(data);
}
if(notes == null){
notes = [];
}
notes.push($("#note").val());
localStorage.setItem("notes", JSON.stringify(notes));
refreshNotes();
}
function refreshNotes(){
var notesElement =$("#notes");
notesElement.empty();
var notes = JSON.parse(localStorage.getItem("notes"));
for(var i = 0; i< notes.length; i++){
var note = notes[i];
notesElement.append("<li>"+note+"</li>");
}
}
$(function(){
refreshNotes();
$("#add").click(function(){
addNote();
});
})
example:
http://codepen.io/xszaboj/pen/dOXEey?editors=1010
I'm creating a Time table generating website as a part of my project and I am stuck at one point.
Using for loop, I am generating user selected text boxes for subjects and faculties. Now the problem is that I cannot get the values of those dynamically generated text boxes. I want to get the values and store it into array so that I can then later on store it to database
If I am using localstorage, then it sometimes shows NaN or undefined. Please help me out.
Following is my Jquery code
$.fn.CreateDynamicTextBoxes = function()
{
$('#DynamicTextBoxContainer, #DynamicTextBoxContainer2').css('display','block');
InputtedValue = $('#SemesterSubjectsSelection').val();
SubjectsNames = [];
for (i = 0; i < InputtedValue; i++)
{
TextBoxContainer1 = $('#DynamicTextBoxContainer');
TextBoxContainer2 = $('#DynamicTextBoxContainer2');
$('<input type="text" class="InputBoxes" id="SubjectTextBoxes'+i+'" placeholder="Subject '+i+' Name" style="margin:5px;" value=""><br>').appendTo(TextBoxContainer1);
$('<input type="text" class="InputBoxes" id="FacultyTextBoxes'+i+'" placeholder="Subject '+i+' Faculty Name" style="margin:5px;" value=""><br>').appendTo(TextBoxContainer2);
SubjectsNames['SubjectTextBoxes'+i];
}
$('#DynamicTextBoxContainer, #UnusedContainer, #DynamicTextBoxContainer2').css('border-top','1px solid #DDD');
}
$.fn.CreateTimeTable = function()
{
for (x = 0; x < i; x++)
{
localStorage.setItem("Main"+x, +SubjectsNames[i]);
}
}
I am also posting screenshot for better understanding
I understand you create 2 text boxes for each subject, one for subject, and second one for faculty. And you want it as a jQuery plugin.
First of all, I think you should create single plugin instead of two, and expose what you need from the plugin.
You should avoid global variables, right now you have InputtedValue, i, SubjectsNames, etc. declared as a global variables, and I believe you should not do that, but keep these variables inside you plugin and expose only what you really need.
You declare your SubjectNames, but later in first for loop you try to access its properties, and actually do nothing with this. In second for loop you try to access it as an array, but it's empty, as you did not assign any values in it.
Take a look at the snippet I created. I do not play much with jQuery, and especially with custom plugins, so the code is not perfect and can be optimized, but I believe it shows the idea. I pass some selectors as in configuration object to make it more reusable. I added 2 buttons to make it more "playable", but you can change it as you prefer. Prepare button creates your dynamic text boxes, and button Generate takes their values and "print" them in result div. generate method is exposed from the plugin to take the values outside the plugin, so you can do it whatever you want with them (e.g. store them in local storage).
$(function() {
$.fn.timeTables = function(config) {
// prepare variables with jQuery objects, based on selectors provided in config object
var numberOfSubjectsTextBox = $(config.numberOfSubjects);
var subjectsDiv = $(config.subjects);
var facultiesDiv = $(config.faculties);
var prepareButton = $(config.prepareButton);
var numberOfSubjects = 0;
prepareButton.click(function() {
// read number of subjects from the textbox - some validation should be added here
numberOfSubjects = +numberOfSubjectsTextBox.val();
// clear subjects and faculties div from any text boxes there
subjectsDiv.empty();
facultiesDiv.empty();
// create new text boxes for each subject and append them to proper div
// TODO: these inputs could be stored in arrays and used later
for (var i = 0; i < numberOfSubjects; i++) {
$('<input type="text" placeholder="Subject ' + i + '" />').appendTo(subjectsDiv);
$('<input type="text" placeholder="Faculty ' + i + '" />').appendTo(facultiesDiv);
}
});
function generate() {
// prepare result array
var result = [];
// get all text boxes from subjects and faculties divs
var subjectTextBoxes = subjectsDiv.find('input');
var facultiesTextBoxes = facultiesDiv.find('input');
// read subject and faculty for each subject - numberOfSubjects variable stores proper value
for (var i = 0; i < numberOfSubjects; i++) {
result.push({
subject: $(subjectTextBoxes[i]).val(),
faculty: $(facultiesTextBoxes[i]).val()
});
}
return result;
}
// expose generate function outside the plugin
return {
generate: generate
};
};
var tt = $('#container').timeTables({
numberOfSubjects: '#numberOfSubjects',
subjects: '#subjects',
faculties: '#faculties',
prepareButton: '#prepare'
});
$('#generate').click(function() {
// generate result and 'print' it to result div
var times = tt.generate();
var result = $('#result');
result.empty();
for (var i = 0; i < times.length; i++) {
$('<div>' + times[i].subject + ': ' + times[i].faculty + '</div>').appendTo(result);
}
});
});
#content div {
float: left;
}
#content div input {
display: block;
}
#footer {
clear: both;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div id="container">
<div id="header">
<input type="text" id="numberOfSubjects" placeholder="Number of subjects" />
<button id="prepare">
Prepare
</button>
</div>
<div id="content">
<div id="subjects">
</div>
<div id="faculties">
</div>
</div>
</div>
<div id="footer">
<button id="generate">Generate</button>
<div id="result">
</div>
</div>