1/** 2 * Dépôt des pièces jointes du formulaire de contact. 3 * 4 * Volontairement distinct de multipleFileForm.js, que chargent aussi les avis produits et les 5 * contenus : les plafonds posés ici ne doivent contraindre que ce formulaire. 6 * 7 * Plafonds et messages sont lus sur les data-* de l'élément, alimentés par faq_contactForm : 8 * une seule source, sinon le garde-fou navigateur et le contrôle serveur divergent et le client 9 * se voit refuser un envoi que le formulaire lui présentait comme valide. 10 * 11 * Requiert imageResize.js (resizeInputFile, formatFileSize), à charger avant. 12 */ 13(function($) 14{ 15 $.fn.contactPictures = function() 16 { 17 return this.each(function() 18 { 19 var zone = $(this), 20 settings = { 21 accept : (zone.data('accept') || '').split(',').map(function(t){ return $.trim(t).toLowerCase(); }), 22 maxSize : parseInt(zone.data('max-size'),10) || 0, 23 maxFiles : parseInt(zone.data('max-files'),10) || 0, 24 maxTotal : parseInt(zone.data('max-total'),10) || 0, 25 msgMaxFiles : zone.data('msg-max-files') || '', 26 msgMaxSize : zone.data('msg-max-size') || '', 27 msgMaxTotal : zone.data('msg-max-total') || '', 28 msgAccept : zone.data('msg-accept') || '', 29 units : (zone.data('units') || 'Ko,Mo').split(','), 30 }; 31 32 // Zone de message : le client doit voir pourquoi son fichier est refusé sans quitter le formulaire 33 var message = $('<div class="fileFormMessage" role="alert" aria-live="polite"></div>').hide(); 34 zone.after(message); 35 36 zone.css({'display': 'flex','flex-wrap': 'wrap'}); 37 addSlot(zone,settings); 38 39 zone.on('change','.imageUploader input',function() 40 { 41 var input = $(this), 42 label = input.closest('.imageUploader').find('label'); 43 44 if (!input[0].files[0]) 45 return; 46 47 // Réduction avant contrôle : une photo de téléphone trop lourde passe une fois réduite 48 resizeInputFile(input[0]).then(function(files) 49 { 50 var file = files[0], 51 error = checkFile(zone,settings,file); 52 53 if (error) 54 { 55 // L'espace est remis à zéro : sans ça le client croit son fichier joint alors qu'il est écarté 56 input.val(''); 57 resetLabel(label); 58 message.text(error).show(); 59 return; 60 } 61 62 message.text('').hide(); 63 showPreview(zone,settings,label,file); 64 }); 65 }); 66 67 zone.on('click','.removeFile',function(ev) 68 { 69 ev.preventDefault(); 70 71 var label = $(this).closest('label'); 72 73 $('#'+label.attr('for')).val(''); 74 resetLabel(label); 75 message.text('').hide(); 76 compactSlots(zone,settings); 77 }); 78 }); 79 }; 80 81 /** 82 * Confronte un fichier aux plafonds du formulaire. 83 * Rend le message à afficher, ou une chaîne vide si le fichier passe. 84 */ 85 function checkFile(zone,settings,file) 86 { 87 // Le type réel, pas celui qu'annonce l'attribut accept : un glisser-déposer le contourne 88 if (settings.accept.length && !isAcceptedType(file.type,settings.accept)) 89 return settings.msgAccept; 90 91 if (settings.maxSize > 0 && file.size > settings.maxSize) 92 return settings.msgMaxSize; 93 94 var attached = countFiles(zone); 95 96 if (settings.maxFiles > 0 && attached.count > settings.maxFiles) 97 return settings.msgMaxFiles; 98 99 // Poids cumulé : c'est lui qui fait refuser l'envoi par le routeur de mails, pas le poids unitaire 100 if (settings.maxTotal > 0 && attached.size > settings.maxTotal) 101 return settings.msgMaxTotal; 102 103 return ''; 104 } 105 106 function isAcceptedType(type,formats) 107 { 108 type = (type || '').toLowerCase(); 109 110 for (var i=0; i<formats.length; i++) 111 if (formats[i] === type || (formats[i].slice(-2) === '/*' && type.indexOf(formats[i].slice(0,-1)) === 0)) 112 return true; 113 114 return false; 115 } 116 117 /** Nombre de fichiers joints et poids cumulé. */ 118 function countFiles(zone) 119 { 120 var attached = { count: 0, size: 0 }; 121 122 zone.find('.imageUploader input').each(function(){ 123 if (!this.files) 124 return; 125 126 attached.count += this.files.length; 127 128 for (var i=0; i<this.files.length; i++) 129 attached.size += this.files[i].size; 130 }); 131 132 return attached; 133 } 134 135 /** Affiche la vignette d'un fichier accepté, puis ouvre l'espace suivant s'il reste de la place. */ 136 function showPreview(zone,settings,label,file) 137 { 138 var reader = new FileReader(), 139 pdfIcon = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M384 160L224 0V128c0 17.7 14.3 32 32 32H384zM176 352c-8.8 0-16 7.2-16 16v128c0 8.8 7.2 16 16 16s16-
1397.2 16-16V464h16c30.9 0 56-25.1 56-56s-25.1-56-56-56H176zm32 80H192V384h16c13.3 0 24 10.7 24 24s-10.7 24-24 24zm96-80c-8.8 0-16 7.2-16 16V496c0 8.8 7.2 16 16 16h32c26.5 0 48-21.5 48-48V400c0-26.5-21.5-48-48-48H304zm32 128H320V384h16c8.8 0 16 7.2 16 16v64c0 8.8-7.2 16-16 16zm80-112v128c0 8.8 7.2 16 16 16s16-7.2 16-16V448h32c8.8 0 16-7.2 16-16s-7.2-16-16-16H448V384h32c8.8 0 16-7.2 16-16s-7.2-16-16-16H432c-8.8 0-16 7.2-16 16z"/><path opacity=".4" d="M0 64C0 28.7 28.7 0 64 0H224V128c0 17.7 14.3 32 32 32H384V304H176c-35.3 0-64 28.7-64 64V512H64c-35.3 0-64-28.7-64-64V64z"/></svg>'; 140 141 reader.onload = function(ev) 142 { 143 if (/^image/.test(file.type)) 144 label.css({'background-image': 'url("'+ev.target.result+'")'}); 145 else 146 label.css({'background-image': "url('data:image/svg+xml;charset=utf-8,"+encodeURIComponent(pdfIcon)+"')"}); 147 148 label.html('<i class="far fa-pen-circle"></i><i class="far fa-trash-circle removeFile"></i>'); 149 label.find('i').css({'font-size':'200%'}); 150 label.append($('<span class="fileSize"></span>').text(formatFileSize(file.size,settings.units))); 151 152 addSlot(zone,settings); 153 154 // Le plafond se signale au moment où il est atteint : une fois le dernier espace rempli, 155 // il n'y a plus de bouton d'ajout, donc plus rien qui puisse expliquer le blocage. 156 if (settings.maxFiles > 0 && countFiles(zone).count >= settings.maxFiles) 157 zone.next('.fileFormMessage').text(settings.msgMaxFiles).show(); 158 }; 159 160 reader.readAsDataURL(file); 161 } 162 163 function addSlot(zone,settings) 164 { 165 // Plus d'espace vierge une fois le plafond atteint : le client ne peut plus dépasser sans le savoir 166 if (settings.maxFiles > 0 && countFiles(zone).count >= settings.maxFiles) 167 { 168 applyStyles(zone); 169 return; 170 } 171 172 var rank = zone.find('.imageUploader').length + 1, 173 slot = $('<div class="imageUploader"></div>'); 174 175 slot.append('<input type="file" id="pjs_'+rank+'" name="pjs[]" class="inputfile" accept="'+zone.data('accept')+'">'); 176 slot.append('<label for="pjs_'+rank+'" class="file-label"><i class="fad fa-plus-circle"></i></label>'); 177 178 zone.append(slot); 179 applyStyles(zone); 180 } 181 182 function compactSlots(zone,settings) 183 { 184 if (zone.find('.imageUploader').length > 1) 185 zone.find('.imageUploader input').each(function(){ 186 if ($(this).val() === '') 187 $(this).closest('.imageUploader').remove(); 188 }); 189 190 addSlot(zone,settings); 191 } 192 193 function resetLabel(label) 194 { 195 label.css({'background-image': ''}); 196 label.html('<i class="fad fa-plus-circle"></i>'); 197 label.find('i').css({'font-size':'200%'}); 198 } 199 200 function applyStyles(zone) 201 { 202 zone.find('.imageUploader input').hide(); 203 zone.find('.imageUploader label').css({ 204 'border-radius' : '5px', 205 'margin' : '2px', 206 'color' : '#232323', 207 'border' : '2px dashed #9b9b9b', 208 'position' : 'relative', 209 'display' : 'flex', 210 'justify-content' : 'space-around', 211 'align-items' : 'center', 212 'width' : '120px', 213 'height' : '120px', 214 'cursor' : 'pointer', 215 'background-repeat' : 'no-repeat', 216 'transition' : 'border-color 150ms linear,color 150ms linear', 217 'background-size' : 'contain', 218 'background-position': 'center center', 219 }); 220 zone.find('.imageUploader').css({'margin':'0'}); 221 zone.find('.imageUploader label i').css({'font-size':'200%'}); 222 } 223})(jQuery);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.