Related
I have an HTML form within a Web App, created with GAS.
This HTML form was created from this file upload script here:
drive-multi-upload
This is the HTML form:
The point is that I needed the files to be uploaded in folders that follow the following pattern:
The first number refers to the model selected on the form, the second number refers to the slot used.
Therefore, it was necessary to create a function to identify the input chosen in the Model and, according to this input, check which is the first empty folder, then take the ID of that folder and pass it to the client side to upload the file inside it.
With the help of some members of the community, some adaptations were made and the final function was this:
/** Modified version of script written by Tanaike */
function createOrGetFolder(folderName, parentFolderId) {
try {
var parentFolder = DriveApp.getFolderById(parentFolderId), folder;
if (parentFolder) {
var foldersIter = parentFolder.getFoldersByName("Video");
if (foldersIter.hasNext()) {
var videoFolder = foldersIter.next();
var nextFolderName = folderName + "-01";
while (!folder) {
video_folder = videoFolder.getFoldersByName(nextFolderName);
if (video_folder.hasNext()) {
folder = video_folder.next();
var files = folder.getFiles();
if (files.hasNext()) {
var [a, b] = nextFolderName.split("-");
nextFolderName = `${a}-${String(Number(b) + 1).padStart(2, "0")}`;
folder = null;
}
} else {
folder = videoFolder.createFolder(nextFolderName);
}
}
} else {
folder = parentFolder.createFolder("Video");
folder = folder.createFolder(folderName);
}
} else {
throw new Error("Parent Folder with id: " + parentFolderId + " not found");
}
return folder.getId();
} catch (error) {
return error;
}
}
It works perfectly, the point is that this form also has a function that generates a .csv file when the form is submitted, the function is this one:
.gs file:
const saveDataAsCSV = (data, folderId) => DriveApp.getFolderById(folderId).createFile("Sample.csv", data);
HTML file:
var name = $('#name01').val();
var description = $('#description').val();
var model = $('#Model').val();
upload_folder = model;
var color = $('#Color').val();
var form_values = [name, description, model, color];
var data = form_values.join(",");
google.script.run.saveDataAsCSV(data, uploadParentFolderId);
My goal is to make the .csv file be generated with the same name as the folder where the file was uploaded, that is, if the file is uploaded in folder 01-01, the file name has to be 01-01.csv, if the file is uploaded in the 02-02 folder, the file name has to be 02-02.csv, and so on.
How can I achieve this?
The complete script code can be viewed here:
.gs file:
function doGet(e) {
return HtmlService.createTemplateFromFile('forms0101.html').evaluate();
}
function getOAuthToken() {
return ScriptApp.getOAuthToken();
}
function getParent(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var id = ss.getId();
var parent = DriveApp.getFileById(id).getParents().next().getId();
return parent
}
function getLimitFolder(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var pastapai = DriveApp.getFileById(ss.getId()).getParents();
var limitfolder = pastapai.next().getFoldersByName("_").next().getId();
return limitfolder;
}
function getFilesLimit(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var pastapai = DriveApp.getFileById(ss.getId()).getParents();
var files = [];
Logger.log(pastapai);
//var limitfolder = pastapai.next().getFoldersByName("_").next().getId();
var limitfolder = pastapai.next().getFoldersByName("_").next().getFiles();
while(limitfolder.hasNext()){
var file = limitfolder.next();
files.push([file.getId()]);
}
console.log(files.length);
return files.length;
}
//function testenumeroarquivos(){
//console.log(checkForFiles()); // When you use this line, you can see the filename of the files.
//}
/**
* creates a folder under a parent folder, and returns it's id. If the folder already exists
* then it is not created and it simply returns the id of the existing one
*/
function createOrGetFolder(folderName, parentFolderId) {
try {
var parentFolder = DriveApp.getFolderById(parentFolderId), folder;
if (parentFolder) {
var foldersIter = parentFolder.getFoldersByName("Video");
if (foldersIter.hasNext()) {
var videoFolder = foldersIter.next();
var nextFolderName = folderName + "-01";
while (!folder) {
video_folder = videoFolder.getFoldersByName(nextFolderName);
if (video_folder.hasNext()) {
folder = video_folder.next();
var files = folder.getFiles();
if (files.hasNext()) {
var [a, b] = nextFolderName.split("-");
nextFolderName = `${a}-${String(Number(b) + 1).padStart(2, "0")}`;
folder = null;
}
} else {
folder = videoFolder.createFolder(nextFolderName);
}
}
} else {
folder = parentFolder.createFolder("Video");
folder = folder.createFolder(folderName);
}
} else {
throw new Error("Parent Folder with id: " + parentFolderId + " not found");
}
console.log("Test" + nextFolderName)
return folder.getId();
} catch (error) {
return error;
}
}
const saveDataAsCSV = (data, folderId) => {
const folder = DriveApp.getFolderById(folderId);
folder.createFile(`${nextFolderName.getName()}.csv`, data);
}
const increaseRequest = (data, folderId) => DriveApp.getFolderById(folderId).createFile("IncreaseRequest.csv", data);
// NOTE: always make sure we use DriveApp, even if it's in a comment, for google to import those
// libraries and allow the rest of the app to work. see https://github.com/tanaikech/Resumable_Upload_For_WebApps
HTML file:
<!DOCTYPE html>
<html>
<head>
<base target="_blank">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Drive Multi Large File Upload</title>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.5/css/materialize.min.css">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<style>
#import url('https://fonts.googleapis.com/css2?family=Rubik:wght#400;600;700&display=swap');
.disclaimer{
width: 480px;
color: #646464;
margin: 20px auto;
padding:0 16px;
text-align:center;
font:400 12px Rubik,sans-serif;
}
h5.center-align.teal-text {
font:700 26px Rubik,sans-serif;
color: #00F498!important;
}
.row {
font:600 14px Rubik,sans-serif;
}
.btn {
background-color: black;
}
.btn:hover {
background-color: #00F498;
}
body {
margin-top: -40px;
}
#progress {
color: #00000;
}
.disclaimer a{
color: #00BCAA;
}
#credit{
display:none
}
.btnOpenModal {
font-size: x-large;
padding: 10px;
border: none;
border-radius: 5px;
background-color: blueviolet;
color: white;
cursor: pointer;
}
hr {
border: 0px;
border-top: 1px solid lightgray;
}
.modal-container {
width: 100vw;
position: fixed;
top: 30px;
display: none;
z-index: 999;
justify-content: center;
}
.modal {
display: flex;
flex-direction: column;
padding: 30px;
background-color: white;
border-radius: 10px;
width: 50%;
}
.active {
display: flex;
}
.active .modal {
animation: modal .4s;
}
#keyframes modal {
from {
opacity: 0;
transform: translate3d(0, -60px, 0);
}
to {
opacity: 1;
transform: translate3d(0, 0, 0);
}
}
.btns {
display: flex;
justify-content: space-between;
margin-top: 10px;
}
.btns button {
font-size: medium;
padding: 10px;
border: none;
border-radius: 5px;
color: white;
cursor: pointer;
}
.btnOK {
background-color: blue!important;
}
.btnClose {
background-color: brown!important;
}
</style>
</head>
<body>
<form class="main" id="form" novalidate="novalidate" style="max-width: 480px;margin: 40px auto;">
<div id="forminner">
<div class="modal-container">
<div class="modal">
<h2>Info</h2>
<hr />
<span>
Lorem Ipsum is simply dummy text of the printing and typesetting
industry. Lorem Ipsum has been the industry's standard dummy text
ever since the 1500s, when an unknown printer took a galley of
type and scrambled it to make a type specimen book.
</span>
<hr />
<div class="btns">
<button class="btnOK" onclick="increaseRequest(); closeModal();">OK</button>
<button class="btnClose" onclick="closeModal()">Close</button>
</div>
</div>
</div>
<h5 class="center-align teal-text" style="margin-bottom: -10px; font-size: 20px; font-family: Rubik; ">YOUR NAME</h5>
<div class="row">
<div class="input-field col s12">
<input id="name01" type="text" name="Name" class="validate" required="required" aria-required="true">
<label for="name" class="">Name</label>
</div>
</div>
<h5 class="center-align teal-text" style="margin-bottom: -10px; font-size: 20px; font-family: Rubik; ">SOME DESCRIPTION</h5>
<div class="row">
<div class="input-field col s12">
<input id="description" type="text" name="Description" class="validate" required="required" aria-required="true">
<label for="name">Description</label>
</div>
</div>
<div class="row">
<div class="col-8 col-md-4">
<h6>Model</h6>
<select class="custom-select" id="Model">
<option selected="">Choose...</option>
<option value="01">01</option>
<option value="02">02</option>
<option value="03">03</option>
</select>
<h6>Color</h6>
<select class="custom-select" id="Color">
<option selected="">Choose...</option>
<option value="Red">Red</option>
<option value="Green">Green</option>
</select>
</div>
</div>
<div class="row">
<div class="col s12">
<h5 class="center-align teal-text">Upload the Video File</h5>
</div>
</div>
<div class="row">
<div class="file-field input-field col s12">
<div id="input-btn" class="btn">
<span>File</span>
<input id="files" type="file" single="">
</div>
<div class="file-path-wrapper">
<input class="file-path validate" type="text" placeholder="Select the file">
</div>
</div>
</div>
<div class="row">
<div class="input-field col s6">
<button id="submit-btn" class="waves-effect waves-light btn submit-btn" type="submit" onclick="submitForm(); return false;">Submit</button>
</div>
</div>
<div class="row">
<div class="input-field col s12 hide" id="update">
<hr>
<p>
Por favor, aguarde enquanto seu arquivo está sendo carregado.<br><span style="color: #00000;"><b>Não feche ou atualize a janela durante o upload.</b></span>
</p>
</div>
</div>
<div class="row">
<div class="input-field col s12" id="progress">
</div>
</div>
</div>
</div>
<div id="success" style="display:none">
<h5 class="center-align teal-text">Tudo certo!</h5>
<p>Se você já preencheu todos os campos é só fechar essa janela e clicar em enviar!</p>
<button id="fechar" class="waves-effect waves-light btn submit-btn" style ="transform: translateX(160%);" type="button" onclick="google.script.host.close()">Fechar</button>
</div>
</form>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.5/js/materialize.min.js"></script>
<script src="https://gumroad.com/js/gumroad.js"></script>
<script>
var upload_folder = "01";
const chunkSize = 5242880;
const uploadParentFolderId = <?=getParent()?>; // creates a folder inside of this folder
const limitfolder = <?=getLimitFolder()?>;
const fileslimit = <?=getFilesLimit()?>;
const modal = document.querySelector('.modal-container');
var name = $('#name01').val();
var description = $('#description').val();
var model = $('#Model').val();
upload_folder = model;
var color = $('#Color').val();
var form_values = [name, description, model, color];
form_values = form_values.map(r => r.replaceAll(",", "#")); // Essa linha substitui todas as "," por "#" antes de gerar o .csv
var data = form_values.join(",");
function submitForm() {
var name = $('#name01').val();
var description = $('#description').val();
var model = $('#Model').val();
upload_folder = model;
var color = $('#Color').val();
var form_values = [name, description, model, color];
form_values = form_values.map(r => r.replaceAll(",", "#")); // Essa linha substitui todas as "," por "#" antes de gerar o .csv
var data = form_values.join(",");
// Added the below script.
if ($('#submit-btn.disabled')[0]) return; // short circuit
var name = upload_folder
var files = [...$('#files')[0].files]; // convert from FileList to array
if (files.length === 0) {
showError("Por favor, selecione um arquivo");
return;
}
google.script.run.saveDataAsCSV(data, uploadParentFolderId);
google.script.run.saveDataAsCSV(data, limitfolder);
disableForm(); // prevent re submission
// the map and reduce are here to ensure that only one file is uploaded at a time. This allows
// the promises to be run sequentially
files.map(file => uploadFilePromiseFactory(file))
.reduce((promiseChain, currentTask) => {
return promiseChain.then(currentTask);
}, Promise.resolve([])).then( () => {
console.log("Completed all files upload");
showSuccess();
});
}
function checkforfiles() {
if (fileslimit <= 2) {
submitForm();
}
if (fileslimit == 3) {
openModal();
}
if (fileslimit >= 4) {
showError("erro");
return;
}
}
function openModal() {
modal.classList.add('active');
}
function closeModal() {
modal.classList.remove('active');
}
function increaseRequest() {
google.script.run.increaseRequest(data, limitfolder);
}
function disableForm() {
$('#submit-btn').addClass('disabled');
$('#input-btn').addClass('disabled');
$('#update').removeClass('hide');
$('#update').removeClass('hide');
}
function closer(){
google.script.host.close();
}
function uploadFilePromiseFactory(file) {
return () => {
console.log("Processing: ", file.name);
return new Promise((resolve, reject) => {
showProgressMessage("Seu arquivo está sendo carregado");
var fr = new FileReader();
fr.fileName = file.name;
fr.fileSize = file.size;
fr.fileType = file.type;
// not sure of a better way of passing the promise functions down
fr.resolve = () => resolve();
fr.reject = (error) => reject(error);
fr.onload = onFileReaderLoad;
fr.readAsArrayBuffer(file);
});
};
}
/**
* Gets called once the browser has loaded a file. The main logic that creates a folder
* and initiates the file upload resides here
*/
function onFileReaderLoad(onLoadEvent) {
var fr = this;
var newFolderName = upload_folder
createOrGetFolder(newFolderName, uploadParentFolderId).then(newFolderId => {
console.log("Found or created guest folder with id: ", newFolderId);
uploadFileToDriveFolder.call(fr, newFolderId).then(() => {
fr.resolve();
}, (error) => {
fr.reject(error);
});
},
(error) => {
if (error) {
showError(error.toString());
}
console.log("onFileReaderLoad Error2: ", error);
});
}
/**
* call to the DriveApp api. Wrapped in a promise in case I want to address timing issues between a
* createFolder and findFolderById
*/
function createOrGetFolder(folderName, parentFolderId) {
return new Promise((resolve, reject) => {
google.script.run.withSuccessHandler(response => {
console.log("createOrGetFolder response: ", response);
if (response && response.length) {
resolve(response);
}
reject(response);
}).createOrGetFolder(folderName, parentFolderId);
});
}
/**
* Helper functions modified from:
* https://github.com/tanaikech/Resumable_Upload_For_WebApps
*/
function uploadFileToDriveFolder(parentFolderId) {
var fr = this;
return new Promise((resolve, reject) => {
var fileName = fr.fileName;
var fileSize = fr.fileSize;
var fileType = fr.fileType;
console.log({fileName: fileName, fileSize: fileSize, fileType: fileType});
var buf = fr.result;
var chunkpot = getChunkpot(chunkSize, fileSize);
var uint8Array = new Uint8Array(buf);
var chunks = chunkpot.chunks.map(function(e) {
return {
data: uint8Array.slice(e.startByte, e.endByte + 1),
length: e.numByte,
range: "bytes " + e.startByte + "-" + e.endByte + "/" + chunkpot.total,
};
});
google.script.run.withSuccessHandler(oAuthToken => {
var xhr = new XMLHttpRequest();
xhr.open("POST", "https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable");
xhr.setRequestHeader('Authorization', "Bearer " + oAuthToken);
xhr.setRequestHeader('Content-Type', "application/json");
xhr.send(JSON.stringify({
mimeType: fileType,
name: fileName,
parents: [parentFolderId]
}));
xhr.onload = () => {
doUpload(fileName, {
location: xhr.getResponseHeader("location"),
chunks: chunks,
}).then(success => {
resolve(success);
console.log("Successfully uploaded: ", fileName);
},
error => {
reject(error);
});
};
xhr.onerror = () => {
console.log("ERROR: ", xhr.response);
reject(xhr.response);
};
}).getOAuthToken();
});
}
function showSuccess() {
$('#forminner').hide();
$('#success').show();
$('#fechar').show();
}
function showError(e) {
$('#progress').addClass('red-text').html(e);
}
function showMessage(e) {
$('#update').html(e);
}
function showProgressMessage(e) {
$('#progress').removeClass('red-text').html(e);
}
/**
* Helper functions modified from:
* https://github.com/tanaikech/Resumable_Upload_For_WebApps
*/
function doUpload(fileName, e) {
return new Promise((resolve, reject) => {
showProgressMessage("Carregando: <span style='color: #00F498 ;'>" + "0%</span>");
var chunks = e.chunks;
var location = e.location;
var cnt = 0;
var end = chunks.length;
var temp = function callback(cnt) {
var e = chunks[cnt];
var xhr = new XMLHttpRequest();
xhr.open("PUT", location, true);
console.log("content range: ", e.range);
xhr.setRequestHeader('Content-Range', e.range);
xhr.send(e.data);
xhr.onloadend = function() {
var status = xhr.status;
cnt += 1;
console.log("Uploading: " + status + " (" + cnt + " / " + end + ")");
showProgressMessage("Carregando: <span style='color: #00F498 ;'>"
+ Math.floor(100 * cnt / end) + "%</span>" );
if (status == 308) {
callback(cnt);
} else if (status == 200) {
$("#progress").text("Done.");
resolve();
} else {
$("#progress").text("Error: " + xhr.response);
reject();
}
};
}(cnt);
});
}
/**
* Helper functions modified from:
* https://github.com/tanaikech/Resumable_Upload_For_WebApps
*/
function getChunkpot(chunkSize, fileSize) {
var chunkPot = {};
chunkPot.total = fileSize;
chunkPot.chunks = [];
if (fileSize > chunkSize) {
var numE = chunkSize;
var endS = function(f, n) {
var c = f % n;
if (c == 0) {
return 0;
} else {
return c;
}
}(fileSize, numE);
var repeat = Math.floor(fileSize / numE);
for (var i = 0; i <= repeat; i++) {
var startAddress = i * numE;
var c = {};
c.startByte = startAddress;
if (i < repeat) {
c.endByte = startAddress + numE - 1;
c.numByte = numE;
chunkPot.chunks.push(c);
} else if (i == repeat && endS > 0) {
c.endByte = startAddress + endS - 1;
c.numByte = endS;
chunkPot.chunks.push(c);
}
}
} else {
var chunk = {
startByte: 0,
endByte: fileSize - 1,
numByte: fileSize,
};
chunkPot.chunks.push(chunk);
}
return chunkPot;
}
</script>
</body>
</html>
It can also be viewed by accessing this worksheet
Although I'm not sure whether I could correctly understand your actual expected result, please test the following modification.
Google Apps Script side:
Please modify saveDataAsCSV of Google Apps Script as follows.
const saveDataAsCSV = (data, folderId1, folderId2 = null) => {
if (folderId2) {
DriveApp.getFolderById(folderId1).createFile(`${DriveApp.getFolderById(folderId2).getName()}.csv`, data);
} else {
DriveApp.getFolderById(folderId1).createFile("sample.csv", data);
}
}
Javascript side:
Please remove google.script.run.saveDataAsCSV(data, uploadParentFolderId); in the function submitForm.
And, please modify onFileReaderLoad of Javascript as follows.
function onFileReaderLoad(onLoadEvent) {
var fr = this;
var newFolderName = upload_folder
createOrGetFolder(newFolderName, uploadParentFolderId).then(newFolderId => {
google.script.run.saveDataAsCSV(data, uploadParentFolderId, newFolderId);
console.log("Found or created guest folder with id: ", newFolderId);
uploadFileToDriveFolder.call(fr, newFolderId).then(() => {
fr.resolve();
}, (error) => {
fr.reject(error);
});
},
(error) => {
if (error) {
showError(error.toString());
}
console.log("onFileReaderLoad Error2: ", error);
});
}
By this modification, I think that a CSV file like 01-01.csv is created to uploadParentFolderId folder.
I have written the following code, which works OK to convert text to speech and play it through the speaker. I am using Microsoft Cognitive Services.
<html lang="en">
<head>
<title>Microsoft Cognitive Services Speech SDK JavaScript Sample for Speech Synthesis</title>
<meta charset="utf-8" />
<style>
body {
font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif;
font-size: 14px;
}
table, th, td {
border: 1px solid #f1f1f1;
border-collapse: collapse;
}
th, td {
padding: 10px;
}
textarea {
font-family: Arial,sans-serif;
}
.mode {
font-size: 18px;
}
.highlight{
background-color: yellow;
}
input:not(disabled) {
font-weight: bold;
color: black;
}
button {
padding: 4px 8px;
background: #0078d4;
color: #ffffff;
}
button:disabled {
padding: 4px 8px;
background: #ccc;
color: #666;
}
input[type=radio] {
position: relative;
z-index: 1;
}
input[type=radio] + label {
padding: 8px 4px 8px 30px;
margin-left: -30px;
}
input[type=radio]:checked + label {
background: #0078d4;
color: #ffffff;
}
</style>
</head>
<body>
<div id="warning">
<h1 style="font-weight:500;">Speech Speech SDK not found
(microsoft.cognitiveservices.speech.sdk.bundle.js missing).</h1>
</div>
<div id="content" style="display:none">
<table>
<tr>
<td></td>
<td><h1 style="font-weight:500;">Microsoft Cognitive Services</h1></td>
</tr>
<tr>
<td align="right">
<label for="subscriptionKey">
<a href="https://learn.microsoft.com/azure/cognitive-services/speech-service/get-started"
rel="noreferrer noopener"
target="_blank">Subscription Key</a>
</label>
</td>
<td><input id="subscriptionKey" type="text" size="40" placeholder="YourSubscriptionKey"></td>
</tr>
<tr>
<td align="right"><label for="regionOptions">Region</label></td>
<td>
<!-- see https://aka.ms/csspeech/region for more details-->
<select id="regionOptions">
<option value="uksouth">UK South</option>
</select>
</td>
</tr>
<tr>
<td align="right"><label for="voiceOptions">Voice</label></td>
<td>
<button id="updateVoiceListButton">Update Voice List</button>
<select id="voiceOptions" disabled>
<option>Please update voice list first.</option>
</select>
</td>
</tr>
<tr>
<td align="right"><label for="isSSML">Is SSML</label><br></td>
<td>
<input type="checkbox" id="isSSML" name="isSSML" value="ssml">
</td>
</tr>
<tr>
<td align="right"><label for="synthesisText">Text</label></td>
<td>
<textarea id="synthesisText" style="display: inline-block;width:500px;height:100px"
placeholder="Input text or ssml for synthesis."></textarea>
</td>
</tr>
<tr>
<td></td>
<td>
<button id="startSynthesisAsyncButton">Start synthesis</button>
<button id="pauseButton">Pause</button>
<button id="resumeButton">Resume</button>
</td>
</tr>
<tr>
<td align="right" valign="top"><label for="resultsDiv">Results</label></td>
<td><textarea id="resultsDiv" readonly style="display: inline-block;width:500px;height:50px"></textarea></td>
</tr>
<tr>
<td align="right" valign="top"><label for="eventsDiv">Events</label></td>
<td><textarea id="eventsDiv" readonly style="display: inline-block;width:500px;height:200px"></textarea></td>
</tr>
<tr>
<td align="right" valign="top"><label for="highlightDiv">Highlight</label></td>
<td><div id="highlightDiv" style="display: inline-block;width:800px;"></div></td>
</tr>
</table>
</div>
<!-- Speech SDK reference sdk. -->
<script src="microsoft.cognitiveservices.speech.sdk.bundle.js"></script>
<!-- Speech SDK Authorization token -->
<script>
// Note: Replace the URL with a valid endpoint to retrieve
// authorization tokens for your subscription.
var authorizationEndpoint = "token.php";
function RequestAuthorizationToken() {
if (authorizationEndpoint) {
var a = new XMLHttpRequest();
a.open("GET", authorizationEndpoint);
a.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
a.send("");
a.onload = function() {
var token = JSON.parse(atob(this.responseText.split(".")[1]));
serviceRegion.value = token.region;
authorizationToken = this.responseText;
subscriptionKey.disabled = true;
subscriptionKey.value = "using authorization token (hit F5 to refresh)";
console.log("Got an authorization token: " + token);
}
}
}
</script>
<!-- Speech SDK USAGE -->
<script>
// On document load resolve the Speech SDK dependency
function Initialize(onComplete) {
if (!!window.SpeechSDK) {
document.getElementById('content').style.display = 'block';
document.getElementById('warning').style.display = 'none';
onComplete(window.SpeechSDK);
}
}
</script>
<!-- Browser Hooks -->
<script>
// status fields and start button in UI
var resultsDiv, eventsDiv;
var highlightDiv;
var startSynthesisAsyncButton, pauseButton, resumeButton;
var updateVoiceListButton;
// subscription key and region for speech services.
var subscriptionKey, regionOptions;
var authorizationToken;
var voiceOptions, isSsml;
var SpeechSDK;
var synthesisText;
var synthesizer;
var player;
var wordBoundaryList = [];
document.addEventListener("DOMContentLoaded", function () {
startSynthesisAsyncButton = document.getElementById("startSynthesisAsyncButton");
updateVoiceListButton = document.getElementById("updateVoiceListButton");
pauseButton = document.getElementById("pauseButton");
resumeButton = document.getElementById("resumeButton");
subscriptionKey = document.getElementById("subscriptionKey");
regionOptions = document.getElementById("regionOptions");
resultsDiv = document.getElementById("resultsDiv");
eventsDiv = document.getElementById("eventsDiv");
voiceOptions = document.getElementById("voiceOptions");
isSsml = document.getElementById("isSSML");
highlightDiv = document.getElementById("highlightDiv");
setInterval(function () {
if (player !== undefined) {
const currentTime = player.currentTime;
var wordBoundary;
for (const e of wordBoundaryList) {
if (currentTime * 1000 > e.audioOffset / 10000) {
wordBoundary = e;
} else {
break;
}
}
if (wordBoundary !== undefined) {
highlightDiv.innerHTML = synthesisText.value.substr(0, wordBoundary.textOffset) +
"<span class='highlight'>" + wordBoundary.text + "</span>" +
synthesisText.value.substr(wordBoundary.textOffset + wordBoundary.wordLength);
} else {
highlightDiv.innerHTML = synthesisText.value;
}
}
}, 50);
updateVoiceListButton.addEventListener("click", function () {
var request = new XMLHttpRequest();
request.open('GET',
'https://' + regionOptions.value + ".tts.speech." +
(regionOptions.value.startsWith("china") ? "azure.cn" : "microsoft.com") +
"/cognitiveservices/voices/list", true);
if (authorizationToken) {
request.setRequestHeader("Authorization", "Bearer " + authorizationToken);
} else {
if (subscriptionKey.value === "" || subscriptionKey.value === "subscription") {
alert("Please enter your Microsoft Cognitive Services Speech subscription key!");
return;
}
request.setRequestHeader("Ocp-Apim-Subscription-Key", subscriptionKey.value);
}
request.onload = function() {
if (request.status >= 200 && request.status < 400) {
const response = this.response;
const neuralSupport = (response.indexOf("JessaNeural") > 0);
const defaultVoice = neuralSupport ? "JessaNeural" : "JessaRUS";
let selectId;
const data = JSON.parse(response);
voiceOptions.innerHTML = "";
data.forEach((voice, index) => {
voiceOptions.innerHTML += "<option value=\"" + voice.Name + "\">" + voice.Name + "</option>";
if (voice.Name.indexOf(defaultVoice) > 0) {
selectId = index;
}
});
voiceOptions.selectedIndex = selectId;
voiceOptions.disabled = false;
} else {
window.console.log(this);
eventsDiv.innerHTML += "cannot get voice list, code: " + this.status + " detail: " + this.statusText + "\r\n";
}
};
request.send()
});
pauseButton.addEventListener("click", function () {
player.pause();
pauseButton.disabled = true;
resumeButton.disabled = false;
});
resumeButton.addEventListener("click", function () {
player.resume();
pauseButton.disabled = false;
resumeButton.disabled = true;
});
startSynthesisAsyncButton.addEventListener("click", function () {
startSynthesisAsyncButton.disabled = true;
resultsDiv.innerHTML = "";
eventsDiv.innerHTML = "";
wordBoundaryList = [];
synthesisText = document.getElementById("synthesisText");
// if we got an authorization token, use the token. Otherwise use the provided subscription key
var speechConfig;
if (authorizationToken) {
speechConfig = SpeechSDK.SpeechConfig.fromAuthorizationToken(authorizationToken, serviceRegion.value);
} else {
if (subscriptionKey.value === "" || subscriptionKey.value === "subscription") {
alert("Please enter your Microsoft Cognitive Services Speech subscription key!");
return;
}
speechConfig = SpeechSDK.SpeechConfig.fromSubscription(subscriptionKey.value, regionOptions.value);
}
speechConfig.speechSynthesisVoiceName = voiceOptions.value;
// The SDK uses Media Source Extensions (https://www.w3.org/TR/media-source/) for playback.
// Mp3 format is supported in most browsers.
speechConfig.speechSynthesisOutputFormat = SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz32KBitRateMonoMp3;
player = new SpeechSDK.SpeakerAudioDestination();
player.onAudioEnd = function (_) {
window.console.log("playback finished");
eventsDiv.innerHTML += "playback finished" + "\r\n";
startSynthesisAsyncButton.disabled = false;
pauseButton.disabled = true;
resumeButton.disabled = true;
wordBoundaryList = [];
};
var audioConfig = SpeechSDK.AudioConfig.fromSpeakerOutput(player);
var audioConfig = AudioConfig.FromWavFileOutput("/var/www/html/unitypst/customfiles/tts/ttsfile.wav");
synthesizer = new SpeechSDK.SpeechSynthesizer(speechConfig, audioConfig);
// The event synthesizing signals that a synthesized audio chunk is received.
// You will receive one or more synthesizing events as a speech phrase is synthesized.
// You can use this callback to streaming receive the synthesized audio.
synthesizer.synthesizing = function (s, e) {
window.console.log(e);
eventsDiv.innerHTML += "(synthesizing) Reason: " + SpeechSDK.ResultReason[e.result.reason] +
"Audio chunk length: " + e.result.audioData.byteLength + "\r\n";
};
// The synthesis started event signals that the synthesis is started.
synthesizer.synthesisStarted = function (s, e) {
window.console.log(e);
eventsDiv.innerHTML += "(synthesis started)" + "\r\n";
pauseButton.disabled = false;
};
// The event synthesis completed signals that the synthesis is completed.
synthesizer.synthesisCompleted = function (s, e) {
console.log(e);
eventsDiv.innerHTML += "(synthesized) Reason: " + SpeechSDK.ResultReason[e.result.reason] +
" Audio length: " + e.result.audioData.byteLength + "\r\n";
};
// The event signals that the service has stopped processing speech.
// This can happen when an error is encountered.
synthesizer.SynthesisCanceled = function (s, e) {
const cancellationDetails = SpeechSDK.CancellationDetails.fromResult(e.result);
let str = "(cancel) Reason: " + SpeechSDK.CancellationReason[cancellationDetails.reason];
if (cancellationDetails.reason === SpeechSDK.CancellationReason.Error) {
str += ": " + e.result.errorDetails;
}
window.console.log(e);
eventsDiv.innerHTML += str + "\r\n";
startSynthesisAsyncButton.disabled = false;
pauseButton.disabled = true;
resumeButton.disabled = true;
};
// This event signals that word boundary is received. This indicates the audio boundary of each word.
// The unit of e.audioOffset is tick (1 tick = 100 nanoseconds), divide by 10,000 to convert to milliseconds.
synthesizer.wordBoundary = function (s, e) {
window.console.log(e);
eventsDiv.innerHTML += "(WordBoundary), Text: " + e.text + ", Audio offset: " + e.audioOffset / 10000 + "ms." + "\r\n";
wordBoundaryList.push(e);
};
const complete_cb = function (result) {
if (result.reason === SpeechSDK.ResultReason.SynthesizingAudioCompleted) {
resultsDiv.innerHTML += "synthesis finished";
} else if (result.reason === SpeechSDK.ResultReason.Canceled) {
resultsDiv.innerHTML += "synthesis failed. Error detail: " + result.errorDetails;
}
window.console.log(result);
synthesizer.close();
synthesizer = undefined;
};
const err_cb = function (err) {
startSynthesisAsyncButton.disabled = false;
phraseDiv.innerHTML += err;
window.console.log(err);
synthesizer.close();
synthesizer = undefined;
};
if (isSsml.checked) {
synthesizer.speakSsmlAsync(synthesisText.value,
complete_cb,
err_cb);
} else {
synthesizer.speakTextAsync(synthesisText.value,
complete_cb,
err_cb);
}
});
Initialize(function (speechSdk) {
SpeechSDK = speechSdk;
startSynthesisAsyncButton.disabled = false;
pauseButton.disabled = true;
resumeButton.disabled = true;
saveButton.disabled = true;
// in case we have a function for getting an authorization token, call it.
if (typeof RequestAuthorizationToken === "function") {
RequestAuthorizationToken();
}
});
});
</script>
</body>
</html>
What I would like to do now is add the option to save the synthesized speech data to disk, and prompt the user to download the file. Is this possible using the Microsoft Cognitive Services Speech SDK? How should I modify the code to support a "save" or "download" option?
we have a sample to show how to save the TTS audio under browser using Azure Speech SDK.
Now the button is activated if one of the fields is valid.
As I understand it, it is necessary to select all errors, and if there are more than 1 errors - disabled
Code validation
var forms = document.querySelectorAll('.validate');
for (var i = 0; i < forms.length; i++) {
forms[i].setAttribute('novalidate', true);
forms[i].lastElementChild.setAttribute('disabled', true);
}
// function has error
var hasError = function(field) {
if (field.disabled || field.type === 'file' || field.type === 'reset' || field.type === 'submit' || field.type === 'button') return;
var validity = field.validity;
if (validity.valid) {
return;
}
if (validity.valueMissing) {
return 'This is a required field';
}
if (validity.typeMismatch) {
// URL
if (field.type === 'url') {
return 'There should be a link';
}
}
if (validity.tooShort) {
return 'Must be from ' + field.getAttribute('minLength') + ' to ' + field.getAttribute('maxLength') + ' characters';
}
if (validity.tooLong) {
return 'Must be from ' + field.getAttribute('minLength') + ' to ' + field.getAttribute('maxLength') + ' characters';
}
return 'Error';
};
// function show error
var showError = function(field, error) {
field.classList.add('error');
var id = field.id || field.name;
if (!id) return;
var message = field.form.querySelector('.error-message#error-for-' + id);
if (!message) {
message = document.createElement('div');
message.className = 'error-message';
message.id = 'error-for-' + id;
field.parentNode.insertBefore(message, field.nextSibling);
}
field.setAttribute('aria-describedby', 'error-for-' + id);
message.innerHTML = error;
message.style.display = 'block';
message.style.visibility = 'visible';
};
// function remove error
var removeError = function(field) {
field.classList.remove('error');
field.removeAttribute('aria-describedby');
var id = field.id || field.name;
if (!id) return;
var message = field.form.querySelector('.error-message#error-for-' + id + '');
if (!message) return;
message.innerHTML = '';
message.style.display = 'none';
message.style.visibility = 'hidden';
};
// function check field
function checkInput(event) {
if (!event.target.form.classList.contains('validate')) return;
var error = hasError(event.target);
if (error) {
event.target.parentElement.parentElement.lastElementChild.setAttribute('disabled', true);
showError(event.target, error);
return;
}
removeError(event.target);
event.target.parentElement.parentElement.lastElementChild.removeAttribute('disabled');
}
// function check fields
function validationForm(event) {
if (!event.target.classList.contains('validate')) return;
var fields = event.target.elements;
var error, hasErrors;
for (var i = 0; i < fields.length; i++) {
error = hasError(fields[i]);
if (error) {
showError(fields[i], error);
if (!hasErrors) {
hasErrors = fields[i];
}
}
}
if (hasErrors) {
event.preventDefault();
hasErrors.focus();
}
}
document.addEventListener('input', checkInput);
document.addEventListener('submit', validationForm);
input {
display: inline-block;
font-size: 1em;
margin-bottom: 5px;
padding: 0.25em 0.5em;
width: 100%;
}
.button[disabled],
.button[disabled]:active,
.button[disabled]:focus,
.button[disabled]:hover {
border-color: #ccc;
background-color: #ccc;
}
.button {
background-color: #0088cc;
border: 1px solid #0088cc;
color: #ffffff;
display: inline-block;
font-size: 0.9375em;
font-weight: normal;
line-height: 1.2;
margin-right: 0.3125em;
margin-bottom: 0.3125em;
padding: 0.5em 0.6875em;
width: auto;
}
.button:active,
.button:focus,
.button:hover {
background-color: #005580;
border-color: #005580;
color: #ffffff;
text-decoration: none;
}
/**
* Errors
*/
.error {
border-color: red;
}
.error-message {
color: red;
font-size: 1rem;
margin-bottom: 15px;
}
<form class="validate">
<div>
<input type="text" minlength="3" maxlength="40" name="name" placeholder="Enter name" required>
</div>
<div>
<input type="url" name="url" pattern="^(?:(?:https?|HTTPS?|ftp|FTP):\/\/)(?:\S+(?::\S*)?#)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-zA-Z\u00a1-\uffff0-9]-*)*[a-zA-Z\u00a1-\uffff0-9]+)(?:\.(?:[a-zA-Z\u00a1-\uffff0-9]-*)*[a-zA-Z\u00a1-\uffff0-9]+)*(?:\.(?:[a-zA-Z\u00a1-\uffff]{2,}))\.?)(?::\d{2,5})?(?:[/?#]\S*)?$"
placeholder="Enter url" required>
</div>
<input type="submit" class="button" value="Submit">
</form>
<br>
<form class="validate">
<div>
<input type="user" minlength="4" maxlength="40" id="user" placeholder="Enter user" required>
</div>
<div>
<input type="position" minlength="5" maxlength="50" id="position" placeholder="Enter position" required>
</div>
<input type="submit" class="button" value="Submit">
</form>
I guess this is the problem part of the code
// function check field
function checkInput(event) {
if (!event.target.form.classList.contains('validate')) return;
var error = hasError(event.target);
if (error) {
event.target.parentElement.parentElement.lastElementChild.setAttribute('disabled', true);
showError(event.target, error);
return;
}
removeError(event.target);
event.target.parentElement.parentElement.lastElementChild.removeAttribute('disabled');
}
I expect the button to activate only if both fields have passed validation.
But now the button is activated if one of the fields is valid
You would need to update your check function. Current function is just Validating input that you are changing as you have added change input listener. you would need to add loop that Validate other fields for error.
// function check field
function checkInput(event) {
if (!event.target.form.classList.contains('validate')) return;
var error = hasError(event.target);
if (error) {
event.target.parentElement.parentElement.lastElementChild.setAttribute('disabled', true);
showError(event.target, error);
return;
}
//To Check All fields If There is still an error
var fields = event.target.parentElement.parentElement.elements;
for (var i = 0; i < fields.length; i++) {
error = hasError(fields[i]);
if (error) {
removeError(event.target);
event.target.parentElement.parentElement.lastElementChild.setAttribute('disabled', true);
return;
}
}
removeError(event.target);
event.target.parentElement.parentElement.lastElementChild.removeAttribute('disabled');
}
The solution:
var forms = document.querySelectorAll('.validate');
for (var i = 0; i < forms.length; i++) {
forms[i].setAttribute('novalidate', true);
forms[i].lastElementChild.setAttribute('disabled', true);
}
// function has error
var hasError = function(field) {
if (field.disabled || field.type === 'file' || field.type === 'reset' || field.type === 'submit' || field.type === 'button') return;
var validity = field.validity;
if (validity.valid) {
return;
}
if (validity.valueMissing) {
return 'This is a required field';
}
if (validity.typeMismatch) {
// URL
if (field.type === 'url') {
return 'There should be a link';
}
}
if (validity.tooShort) {
return 'Must be from ' + field.getAttribute('minLength') + ' to ' + field.getAttribute('maxLength') + ' characters';
}
if (validity.tooLong) {
return 'Must be from ' + field.getAttribute('minLength') + ' to ' + field.getAttribute('maxLength') + ' characters';
}
return 'Error';
};
// function show error
var showError = function(field, error) {
field.classList.add('error');
var id = field.id || field.name;
if (!id) return;
var message = field.form.querySelector('.error-message#error-for-' + id);
if (!message) {
message = document.createElement('div');
message.className = 'error-message';
message.id = 'error-for-' + id;
field.parentNode.insertBefore(message, field.nextSibling);
}
field.setAttribute('aria-describedby', 'error-for-' + id);
message.innerHTML = error;
message.style.display = 'block';
message.style.visibility = 'visible';
};
// function remove error
var removeError = function(field) {
field.classList.remove('error');
field.removeAttribute('aria-describedby');
var id = field.id || field.name;
if (!id) return;
var message = field.form.querySelector('.error-message#error-for-' + id + '');
if (!message) return;
message.innerHTML = '';
message.style.display = 'none';
message.style.visibility = 'hidden';
};
// function check field
function checkInput(event) {
if (!event.target.form.classList.contains('validate')) return;
var error = hasError(event.target);
if (error) {
event.target.parentElement.parentElement.lastElementChild.setAttribute('disabled', true);
showError(event.target, error);
return;
}
//To Check All fields If There is still an error
var fields = event.target.parentElement.parentElement.elements;
for (var i = 0; i < fields.length; i++) {
error = hasError(fields[i]);
if (error) {
removeError(event.target);
event.target.parentElement.parentElement.lastElementChild.setAttribute('disabled', true);
return;
}
}
removeError(event.target);
event.target.parentElement.parentElement.lastElementChild.removeAttribute('disabled');
}
// function check fields
function validationForm(event) {
if (!event.target.classList.contains('validate')) return;
var fields = event.target.elements;
var error, hasErrors;
for (var i = 0; i < fields.length; i++) {
error = hasError(fields[i]);
if (error) {
showError(fields[i], error);
if (!hasErrors) {
hasErrors = fields[i];
}
}
}
if (hasErrors) {
event.preventDefault();
hasErrors.focus();
}
}
document.addEventListener('input', checkInput);
document.addEventListener('submit', validationForm);
input {
display: inline-block;
font-size: 1em;
margin-bottom: 5px;
padding: 0.25em 0.5em;
width: 100%;
}
.button[disabled],
.button[disabled]:active,
.button[disabled]:focus,
.button[disabled]:hover {
border-color: #ccc;
background-color: #ccc;
}
.button {
background-color: #0088cc;
border: 1px solid #0088cc;
color: #ffffff;
display: inline-block;
font-size: 0.9375em;
font-weight: normal;
line-height: 1.2;
margin-right: 0.3125em;
margin-bottom: 0.3125em;
padding: 0.5em 0.6875em;
width: auto;
}
.button:active,
.button:focus,
.button:hover {
background-color: #005580;
border-color: #005580;
color: #ffffff;
text-decoration: none;
}
/**
* Errors
*/
.error {
border-color: red;
}
.error-message {
color: red;
font-size: 1rem;
margin-bottom: 15px;
}
<form class="validate">
<div>
<input type="text" minlength="3" maxlength="40" name="name" placeholder="Enter name" required>
</div>
<div>
<input type="url" name="url" pattern="^(?:(?:https?|HTTPS?|ftp|FTP):\/\/)(?:\S+(?::\S*)?#)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-zA-Z\u00a1-\uffff0-9]-*)*[a-zA-Z\u00a1-\uffff0-9]+)(?:\.(?:[a-zA-Z\u00a1-\uffff0-9]-*)*[a-zA-Z\u00a1-\uffff0-9]+)*(?:\.(?:[a-zA-Z\u00a1-\uffff]{2,}))\.?)(?::\d{2,5})?(?:[/?#]\S*)?$"
placeholder="Enter url" required>
</div>
<input type="submit" class="button" value="Submit">
</form>
<br>
<form class="validate">
<div>
<input type="user" minlength="4" maxlength="40" id="user" placeholder="Enter user" required>
</div>
<div>
<input type="position" minlength="5" maxlength="50" id="position" placeholder="Enter position" required>
</div>
<input type="submit" class="button" value="Submit">
</form>
The reason for it not to work as expected is that you remove the disabled state if there's no error by calling
event.target.parentElement.parentElement.lastElementChild.removeAttribute('disabled');
but don't add the Attribute disabled if there is an error.
So if you leave the name input blank but add e.g. http://example.com the button is enabled as the URL triggered the removeAttribute() function.
As mplungjan suggested it might be better to just use required attribute.
I want to be able to write the title of a book for now, then have it save to an array. When I click add book it should pop up right below the example text on the page, however im not sure why my functions are not working. Im new to this so its a bit confusing.
<title></title>
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<div class="container">
<h1>Library</h1>
<button type="button" onclick="addBookToLibrary();" id="addButton">Add
A Book</button>
<div class="formContainer">
<form id="info">
<input type="text" placeholder="Title" id="title">
<input type="text" placeholder="Author"
id="author">
<input type="number" placeholder="#Pages" min="0" max="5000"
id="number">
<label for="read">Read?</label>
<input type="checkbox" id="read">
</form>
</div>
</div>
<hr>
<div class="bookContainer">
<div class="wrapper">
<h2>Title</h2>
<h2>Author</h2>
<h2>Pages</h2>
<h2>Read</h2>
</div>
<div class="books">
<p class="item">The Hobbit</p>
<p class="item">by J.R.R. Tolkien</p>
<p class="item">295 pages</p>
<p class="item">Read</p>
</div>
</div>
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
#addButton {
margin-bottom: 1em;
}
.container {
text-align: center
}
.container h1 {
margin-top: .5em;
margin-bottom: .5em;
}
hr {
margin-top: 2em;
margin-bottom: 2em;
}
.bookContainer{
width: 90%;
border:2px solid black;
margin: auto;
}
.wrapper {
display: flex;
justify-content: space-around;
}
.books {
display: flex;
justify-content: space-around;
margin-top: 1em;
}
.item {
flex:1;
text-align:center;
}
#number {
width: 8%;
}
let myLibrary = [];
function book(title, author, pages, read) {
this.title = title
this.author = author
this.pages = pages
this.read = read
this.info = function() {
console.log(title, author, pages, read);
}
}
function addBookToLibrary() {
// get value from the input text
let title = document.querySelector('#title').value;
//append data to the array myLibrary
myLibrary.push(title);
let pval = '';
for (i = 0; i < myLibrary.length; i++) {
pval = pval + myLibrary[i] + '<br/>';
}
}
function render() {
//display array data
document.querySelector('.wrapper').innerHTML = pval;
}
I didnt add the html bc i didnt want it to be super long, but if you need it i can always add it.
Your pval is not in the same scope with function render. And try to keep your array storing only data instead of html string so that you can keep flexibility to do any thing.
let myLibrary = [
{
title: 'The Hobbit',
author: 'by J.R.R. Tolkien',
pages: '295 pages',
read: 'Read'
}
];
function book(title, author, pages, read) {
this.title = title
this.author = author
this.pages = pages
this.read = read
this.info = function () {
console.log(title, author, pages, read);
}
}
function addBookToLibrary() {
// get value from the input text
let title = document.querySelector('#title').value;
let author = document.querySelector('#author').value;
let pages = document.querySelector('#number').value;
let read = document.querySelector('#read').checked ? document.querySelector('#read').value : '';
// create a book
let b1 = new book(title, author, pages, read);
// append data to the array myLibrary
myLibrary.push(b1);
// invoke render function
render();
}
function render() {
//display array data
let pval = '';
for (i = 0; i < myLibrary.length; i++) {
pval += '<div class="book">';
pval += '<p class="item">' + myLibrary[i].title + '</p>';
pval += '<p class="item">' + myLibrary[i].author + '</p>';
pval += '<p class="item">' + myLibrary[i].pages + '</p>';
pval += '<p class="item">' + myLibrary[i].read + '</p>';
pval += '</div>'
}
document.querySelector('.books').innerHTML = pval;
}
You should provide a specific description instead of 'Doesnt work' only. Such as error message or the steps you put the code snippet in your program. It will always work if try to modify the code snippet above to fit your scenario. That's what we can't help you because we don't know what you want.
Anyway, you can check the link of solution which I've tried to understand your need below. And try to modify it to fit your need.
https://jsfiddle.net/chi1010860/Lc71w2f0/
take a look here : https://www.w3schools.com/js/js_arrays.asp
var myLibrary = new Array();
function book(title, author, pages, read) {
this.title = title
this.author = author
this.pages = pages
this.read = read
this.info = function() {
console.log(title, author, pages, read);
}
}
function addBookToLibrary() {
// get value from the input text
var title = document.querySelector('#title').value;
//append data to the array myLibrary
myLibrary.push(title);
var pval = '';
for (var i in myLibrary) {
if(myLibrary[i])pval = pval + myLibrary[i] + '<br/>';
}
}
function render() {
//display array data
document.querySelector('.wrapper').innerHTML = pval;
}
HTM like this :
<input type='file' multiple id="upload-file"/>
<?php
for($i=0;$i<5; $i++) {
?>
<div class="img-container" id="box<?php echo $i ?>">
<button style="display: none;" type="submit" class="btn btn-primary show-button">
<i class="glyphicon glyphicon-edit"></i>
</button>
</div>
<?php
}
?>
Javascript like this :
$(function () {
$(":file").change(function () {
var noOfFiles = this.files.length;
for(var i=0; i < noOfFiles; i++) {
var reader = new FileReader();
reader.onload = imageIsLoaded;
reader.readAsDataURL(this.files[i]);
}
});
});
function imageIsLoaded(e) {
var imgTmpl = '<img height="142" width="162" src='+e.target.result+'>';
var IsImgAdded=false;
$('.img-container').each(function(){
if($(this).find('img').length==0 && IsImgAdded==false){
$(this).append(imgTmpl);
IsImgAdded=true;
$(this).find('.show-button').show();
}
});
};
$(".show-button").click(function(){
$("#upload-file").click();
});
Demo and full code like this : http://phpfiddle.org/main/code/1g8t-h5kn
I try like that. But it does not fit
Should when I edit image on the box 2, it will change image on the box 2. Not box 3
How can I solve this issue?
You need to create a var to set the actual edited container than after the upload launch the function, check if the variable has a value different than null and append the img in the right place.
WORKING FIDDLE HERE: https://codepen.io/VLK_STUDIO/pen/wdQPRL
$(function() {
$(":file").change(function() {
var noOfFiles = this.files.length;
for (var i = 0; i < noOfFiles; i++) {
var reader = new FileReader();
reader.onload = imageIsLoaded;
reader.readAsDataURL(this.files[i]);
}
});
});
var actualEdited = "";
function imageIsLoaded(e) {
var imgTmpl = '<img height="142" width="162" src=' + e.target.result + ">";
var IsImgAdded = false;
$(".img-container").each(function() {
if ($(this).find("img").length == 0 && IsImgAdded == false) {
if (actualEdited == "") {
$(this).append(imgTmpl);
$(this).find(".show-button").show();
IsImgAdded = true;
return false;
} else {
$(actualEdited).find("img").remove();
$(actualEdited).append(imgTmpl);
$(actualEdited).find(".show-button").show();
actualEdited = "";
return false;
}
} else {
if (actualEdited != "") {
$(actualEdited).find("img").remove();
$(actualEdited).append(imgTmpl);
$(actualEdited).find(".show-button").show();
actualEdited = "";
return false;
}
}
});
}
$(".show-button").click(function() {
$("#upload-file").click();
actualEdited = $(this).parent();
});
.img-container {
width: 100px;
border: 1px solid black;
height: 100px;
margin: 20px;
display: inline-block;
}
img {
display: block;
width: 100%;
height: auto;
}
.show-button {
cursor: pointer;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input id="upload-file" type="file">
<div class="img-container">
<div class="show-button">click
</div>
</div>
<div class="img-container">
<div class="show-button">click
</div>
</div>
<div class="img-container">
<div class="show-button">click
</div>
</div>
<div class="img-container">
<div class="show-button">click
</div>
</div>
$(function () {
$(":file").change(function () {
var noOfFiles = this.files.length;
for(var i=0; i < noOfFiles; i++) {
var reader = new FileReader();
reader.onload = imageIsLoaded;
reader.readAsDataURL(this.files[i]);
}
});
});
function imageIsLoaded(e) {
var imgTmpl = '<img height="142" width="162" src='+e.target.result+'>';
var IsImgAdded=false;
$('.img-container').each(function(){
if($(this).find('img').length==0 && IsImgAdded==false){
$(this).append(imgTmpl);
IsImgAdded=true;
$(this).find('.show-button').show();
} else {
if ($(this).id == sessionStorage.getItem('blockid')){
$(this).append(imgTmpl);
IsImgAdded=true;
$(this).find('.show-button').show();
}
}
});
};
$(".show-button").click(function(){
sessionStorage.setItem('blockid', $(this).id);
$("#upload-file").click();
});
You're forggeting to identify the edited blocks. I warn you this is not the best approach, but the faster one. Bind one object action to another is confusing and poor practice, i advise you to use better js functions/closures to get what you want with proper form.