comparison website/issues/html/issue.item.html @ 6454:83b67f65b8ff

Implement file upload by drag and drop and paste. Table formatting Move file description from below the file upload into the table on same line as file upload. Implement https://wiki.roundup-tracker.org/FileUploadViaDragDropAndPaste for table structure. Added creation of description field along with file input field. On tables align header fields that do not span rows (i.e. column headers) on the left. Table titles/grouping span multiple columns. Also do not align column headers left on the index page which uses the .list class. Also add some padding to th to increase space and improve readability. For message and files tables make them 95% of the width of their container. It makes the headers for messages and files more readable by adding space.
author John Rouillard <rouilj@ieee.org>
date Mon, 05 Jul 2021 01:24:37 -0400
parents 6ad3b2d46852
children a2c340261290
comparison
equal deleted inserted replaced
6453:3e7fc096fe5b 6454:83b67f65b8ff
155 </td> 155 </td>
156 </tr> 156 </tr>
157 157
158 <tr tal:condition="context/is_edit_ok"> 158 <tr tal:condition="context/is_edit_ok">
159 <th><label for="file-1@content" i18n:translate="">File</label>:</th> 159 <th><label for="file-1@content" i18n:translate="">File</label>:</th>
160 <td colspan=3> 160 <td>
161 <input type="hidden" name="@link@files" value="file-1"> 161 <input type="hidden" name="@link@files" value="file-1">
162 <input type="file" id="file-1@content" name="file-1@content" size="40"> 162 <input type="file" id="file-1@content" name="file-1@content" size="40">
163 </td> 163 </td>
164 <th><label for="file-1@description" i18n:translate="">File Description</label>:</th>
165 <td colspan=3><input type="edit" id="file-1@description" name="file-1@description" size="40"></td>
164 </tr> 166 </tr>
165 <tr tal:condition="context/is_edit_ok"> 167 <tr tal:condition="context/is_edit_ok">
166 <th><label for="file-1@description" i18n:translate="">File Description</label>:</th> 168 <td colspan=4>
167 <td colspan=3><input type="edit" id="file-1@description" name="file-1@description" size="40"></td> 169 <textarea readonly id="DropZone">
170 paste images or drag and drop files here....
171 </textarea>
172 </td>
168 </tr> 173 </tr>
169 </table> 174 </table>
170 </fieldset> 175 </fieldset>
171 <table class="form"> 176 <table class="form">
172 <tr tal:condition="context/is_edit_ok"> 177 <tr tal:condition="context/is_edit_ok">
181 i18n:translate="">Make a copy</a> 186 i18n:translate="">Make a copy</a>
182 </td> 187 </td>
183 </tr> 188 </tr>
184 </table> 189 </table>
185 </form> 190 </form>
191
192 <!-- drag and drop code -->
193 <script tal:attributes="nonce request/client/client_nonce">
194 /* multiple file drops cause issues with redefined
195 file-X@content issues. input multiple assumes
196 it can start numbering from 1 for each of the
197 multiple files. However numbering here does the
198 same leading to duplicate file-2@content.
199
200 layout needs some work, alignnment of new file
201 input's isn't great.
202
203 Need a way to delete or reset file inputs so file
204 assigned to them isn't uploaded. Clicking on button
205 in chrome and then canceling unsets the file. But this
206 sequence does nothing in firefox.
207
208 Pasting always uses image.<type> can't name file.
209 Need to query user during paste for name/description.
210 */
211
212 let newInput=null;
213 let NextInputNum = 100; /* file input 1 is hardcoded in form.
214 It is a multiple file input control. To
215 prevent collision, we start dynamic file controls at
216 file-100@content. 100 is larger than we expect
217 the number of files uploaded using file input 1.*/
218
219 let target = document.getElementById('DropZone');
220 target.style.display = "block";
221 let body = document.body;
222 let fileInput = document.getElementById('file-1@content');
223 let fileDesc = document.getElementById('file-1@description');
224
225 function add_file_input () {
226
227 // Only allow one change listener on newest input.
228 fileInput.removeEventListener('change',
229 add_file_input,
230 false);
231
232 /* create new file input to get next dragged file */
233 /* <input type="file" name="file-2@content"> for 2,
234 3, 4, ... */
235 newInput=document.createElement('input');
236 newInput.type="file";
237 newInput.id="file-" + NextInputNum +"@content";
238 newInput.name=newInput.id;
239 fileInput = fileInput.insertAdjacentElement('afterend',
240 newInput);
241 // add change hander to newest file input
242 fileInput.addEventListener('change',
243 add_file_input, // create new input for more files
244 false);
245
246 /* link file-N to list of files on issue.
247 also link to msg-1 */
248 addLink=document.createElement('input');
249 addLink.type="hidden";
250 addLink.id="@link@file=file-" + NextInputNum;
251 addLink.name="@link@files"
252 addLink.value="file-" + NextInputNum;
253 fileInput.insertAdjacentElement('afterend', addLink);
254
255 addLink=document.createElement('input');
256 addLink.type="hidden";
257 addLink.id="msg-1@link@files=file-" + NextInputNum;
258 addLink.name="msg-1@link@files"
259 addLink.value="file-" + NextInputNum
260 fileInput.insertAdjacentElement('afterend', addLink);
261
262 addLink=document.createElement('input');
263 addLink.type="edit";
264 addLink.id="file-" + NextInputNum + "@description";
265 addLink.name=addLink.id
266 addLink.size = 40
267 fileDesc=fileDesc.insertAdjacentElement('afterend', addLink);
268
269 NextInputNum = NextInputNum+1;
270
271 }
272
273 function MarkDropZone(e, active) {
274 active == true ? e.style.backgroundColor = "goldenrod" :
275 e.style.backgroundColor = "";
276 }
277 fileInput.addEventListener('change',
278 add_file_input, // create new input for more files
279 false);
280
281 target.addEventListener('dragover', (e) => {
282 e.preventDefault();
283 body.classList.add('dragging');
284 });
285
286 target.addEventListener('dragenter', (e) => {
287 e.preventDefault();
288 MarkDropZone(target, true);
289 });
290
291
292 target.addEventListener('dragleave', (e) => {
293 e.preventDefault();
294 MarkDropZone(target, false);
295 });
296
297 target.addEventListener('dragleave', () => {
298 body.classList.remove('dragging');
299 });
300
301 target.addEventListener('drop', (e) => {
302 body.classList.remove('dragging');
303 MarkDropZone(target, false);
304
305 // Only allow single file drop unless
306 // fileInput name is @file that can support
307 // multiple file drop and file drop is multiple.
308 if (( fileInput.name != "@file" ||
309 ! fileInput.hasAttribute('multiple')) &&
310 e.dataTransfer.files.length != 1 ) {
311 alert("File input can only accept one file.")
312 e.preventDefault();
313 return
314 }
315 // set file input files to the dragged files
316 fileInput.files = e.dataTransfer.files;
317
318 add_file_input(); // create new input for more files
319 // run last otherwise firefox empties e.dataTransfer
320 e.preventDefault();
321 });
322
323 target.addEventListener('mouseover', (e) => {
324 e.preventDefault();
325 MarkDropZone(target, true);
326 });
327
328 target.addEventListener('mouseout', (e) => {
329 e.preventDefault();
330 MarkDropZone(target, false);
331 });
332
333 target.addEventListener('paste', (event) => {
334 // https://mobiarch.wordpress.com/2013/09/25/upload-image-by-copy-and-paste/
335 // add paste event listener to the page
336
337 // https://stackoverflow.com/questions/50427513/
338 // html-paste-clipboard-image-to-file-input
339 if ( event.clipboardData.files.length == 0) {
340 // if not file data alert
341 alert("No image found for pasting");
342 event.preventDefault();
343 return;
344 }
345 fileInput.files = event.clipboardData.files;
346
347 /* Possible enhancement if file check fails.
348 iterate over all items 0 ...:
349 event.clipboardData.items.length
350 look at all items[i].kind for 'string' and
351 items[i].type looking for a text/plain item. If
352 found,
353 event.clipboardData.items[1].getAsString(
354 callback_fcn(s))
355
356 where callback function that creates a new
357 dataTransfer object with a file and insert the
358 content s and assigns it to the input.
359
360 https://gist.github.com/guest271314/7eac2c21911f5e40f489\33ac78e518bd
361 */
362 add_file_input(); // create new input for more files
363 // do not paste contents to dropzone
364 event.preventDefault();
365 }, false);
366 </script>
367 <style tal:attributes="nonce request/client/client_nonce">
368 #FileArea input[type=file] ~ input[type=file] {display:block;}
369 #DropZone { /* don't display dropzone by default.
370 Displayed as block by javascript. */
371 display:none;
372 width: 100%;
373 /* override textarea inset */
374 border-style: dashed;
375 padding: 3ex 0; /* larger dropzone */
376 /* add space below inputs */
377 margin-block-start: 1em;
378 /* lighter color */
379 background: rgba(255,255,255,0.4);
380 }
381 </style>
186 382
187 <p tal:condition="context/id" i18n:translate=""> 383 <p tal:condition="context/id" i18n:translate="">
188 Created on <b><tal:x replace="python:context.creation.pretty('%Y-%m-%d %H:%M')" i18n:name="creation" /></b> 384 Created on <b><tal:x replace="python:context.creation.pretty('%Y-%m-%d %H:%M')" i18n:name="creation" /></b>
189 by <b><tal:x replace="context/creator" i18n:name="creator" /></b>, 385 by <b><tal:x replace="context/creator" i18n:name="creator" /></b>,
190 last changed <b><tal:x replace="python:context.activity.pretty('%Y-%m-%d %H:%M')" i18n:name="activity" /></b> 386 last changed <b><tal:x replace="python:context.activity.pretty('%Y-%m-%d %H:%M')" i18n:name="activity" /></b>
257 </div> 453 </div>
258 454
259 </td> 455 </td>
260 456
261 </tal:block> 457 </tal:block>
458 <!-- SHA: ad841842c0da5f9d1a7f69a1e0c847a549b75bf2 -->

Roundup Issue Tracker: http://roundup-tracker.org/