@@ 1118-1371 (lines=254) @@ | ||
1115 | return upload; |
|
1116 | }]); |
|
1117 | ||
1118 | ngFileUpload.directive('ngfSelect', ['$parse', '$timeout', '$compile', 'Upload', function ($parse, $timeout, $compile, Upload) { |
|
1119 | var generatedElems = []; |
|
1120 | ||
1121 | function isDelayedClickSupported(ua) { |
|
1122 | // fix for android native browser < 4.4 and safari windows |
|
1123 | var m = ua.match(/Android[^\d]*(\d+)\.(\d+)/); |
|
1124 | if (m && m.length > 2) { |
|
1125 | var v = Upload.defaults.androidFixMinorVersion || 4; |
|
1126 | return parseInt(m[1]) < 4 || (parseInt(m[1]) === v && parseInt(m[2]) < v); |
|
1127 | } |
|
1128 | ||
1129 | // safari on windows |
|
1130 | return ua.indexOf('Chrome') === -1 && /.*Windows.*Safari.*/.test(ua); |
|
1131 | } |
|
1132 | ||
1133 | function linkFileSelect(scope, elem, attr, ngModel, $parse, $timeout, $compile, upload) { |
|
1134 | /** @namespace attr.ngfSelect */ |
|
1135 | /** @namespace attr.ngfChange */ |
|
1136 | /** @namespace attr.ngModel */ |
|
1137 | /** @namespace attr.ngfModelOptions */ |
|
1138 | /** @namespace attr.ngfMultiple */ |
|
1139 | /** @namespace attr.ngfCapture */ |
|
1140 | /** @namespace attr.ngfValidate */ |
|
1141 | /** @namespace attr.ngfKeep */ |
|
1142 | var attrGetter = function (name, scope) { |
|
1143 | return upload.attrGetter(name, attr, scope); |
|
1144 | }; |
|
1145 | ||
1146 | function isInputTypeFile() { |
|
1147 | return elem[0].tagName.toLowerCase() === 'input' && attr.type && attr.type.toLowerCase() === 'file'; |
|
1148 | } |
|
1149 | ||
1150 | function fileChangeAttr() { |
|
1151 | return attrGetter('ngfChange') || attrGetter('ngfSelect'); |
|
1152 | } |
|
1153 | ||
1154 | function changeFn(evt) { |
|
1155 | if (upload.shouldUpdateOn('change', attr, scope)) { |
|
1156 | var fileList = evt.__files_ || (evt.target && evt.target.files), files = []; |
|
1157 | /* Handle duplicate call in IE11 */ |
|
1158 | if (!fileList) return; |
|
1159 | for (var i = 0; i < fileList.length; i++) { |
|
1160 | files.push(fileList[i]); |
|
1161 | } |
|
1162 | upload.updateModel(ngModel, attr, scope, fileChangeAttr(), |
|
1163 | files.length ? files : null, evt); |
|
1164 | } |
|
1165 | } |
|
1166 | ||
1167 | upload.registerModelChangeValidator(ngModel, attr, scope); |
|
1168 | ||
1169 | var unwatches = []; |
|
1170 | if (attrGetter('ngfMultiple')) { |
|
1171 | unwatches.push(scope.$watch(attrGetter('ngfMultiple'), function () { |
|
1172 | fileElem.attr('multiple', attrGetter('ngfMultiple', scope)); |
|
1173 | })); |
|
1174 | } |
|
1175 | if (attrGetter('ngfCapture')) { |
|
1176 | unwatches.push(scope.$watch(attrGetter('ngfCapture'), function () { |
|
1177 | fileElem.attr('capture', attrGetter('ngfCapture', scope)); |
|
1178 | })); |
|
1179 | } |
|
1180 | if (attrGetter('ngfAccept')) { |
|
1181 | unwatches.push(scope.$watch(attrGetter('ngfAccept'), function () { |
|
1182 | fileElem.attr('accept', attrGetter('ngfAccept', scope)); |
|
1183 | })); |
|
1184 | } |
|
1185 | unwatches.push(attr.$observe('accept', function () { |
|
1186 | fileElem.attr('accept', attrGetter('accept')); |
|
1187 | })); |
|
1188 | function bindAttrToFileInput(fileElem, label) { |
|
1189 | function updateId(val) { |
|
1190 | fileElem.attr('id', 'ngf-' + val); |
|
1191 | label.attr('id', 'ngf-label-' + val); |
|
1192 | } |
|
1193 | ||
1194 | for (var i = 0; i < elem[0].attributes.length; i++) { |
|
1195 | var attribute = elem[0].attributes[i]; |
|
1196 | if (attribute.name !== 'type' && attribute.name !== 'class' && attribute.name !== 'style') { |
|
1197 | if (attribute.name === 'id') { |
|
1198 | updateId(attribute.value); |
|
1199 | unwatches.push(attr.$observe('id', updateId)); |
|
1200 | } else { |
|
1201 | fileElem.attr(attribute.name, (!attribute.value && (attribute.name === 'required' || |
|
1202 | attribute.name === 'multiple')) ? attribute.name : attribute.value); |
|
1203 | } |
|
1204 | } |
|
1205 | } |
|
1206 | } |
|
1207 | ||
1208 | function createFileInput() { |
|
1209 | if (isInputTypeFile()) { |
|
1210 | return elem; |
|
1211 | } |
|
1212 | ||
1213 | var fileElem = angular.element('<input type="file">'); |
|
1214 | ||
1215 | var label = angular.element('<label>upload</label>'); |
|
1216 | label.css('visibility', 'hidden').css('position', 'absolute').css('overflow', 'hidden') |
|
1217 | .css('width', '0px').css('height', '0px').css('border', 'none') |
|
1218 | .css('margin', '0px').css('padding', '0px').attr('tabindex', '-1'); |
|
1219 | bindAttrToFileInput(fileElem, label); |
|
1220 | ||
1221 | generatedElems.push({el: elem, ref: label}); |
|
1222 | ||
1223 | document.body.appendChild(label.append(fileElem)[0]); |
|
1224 | ||
1225 | return fileElem; |
|
1226 | } |
|
1227 | ||
1228 | function clickHandler(evt) { |
|
1229 | if (elem.attr('disabled')) return false; |
|
1230 | if (attrGetter('ngfSelectDisabled', scope)) return; |
|
1231 | ||
1232 | var r = detectSwipe(evt); |
|
1233 | // prevent the click if it is a swipe |
|
1234 | if (r != null) return r; |
|
1235 | ||
1236 | resetModel(evt); |
|
1237 | ||
1238 | // fix for md when the element is removed from the DOM and added back #460 |
|
1239 | try { |
|
1240 | if (!isInputTypeFile() && !document.body.contains(fileElem[0])) { |
|
1241 | generatedElems.push({el: elem, ref: fileElem.parent()}); |
|
1242 | document.body.appendChild(fileElem.parent()[0]); |
|
1243 | fileElem.bind('change', changeFn); |
|
1244 | } |
|
1245 | } catch (e) {/*ignore*/ |
|
1246 | } |
|
1247 | ||
1248 | if (isDelayedClickSupported(navigator.userAgent)) { |
|
1249 | setTimeout(function () { |
|
1250 | fileElem[0].click(); |
|
1251 | }, 0); |
|
1252 | } else { |
|
1253 | fileElem[0].click(); |
|
1254 | } |
|
1255 | ||
1256 | return false; |
|
1257 | } |
|
1258 | ||
1259 | ||
1260 | var initialTouchStartY = 0; |
|
1261 | var initialTouchStartX = 0; |
|
1262 | ||
1263 | function detectSwipe(evt) { |
|
1264 | var touches = evt.changedTouches || (evt.originalEvent && evt.originalEvent.changedTouches); |
|
1265 | if (touches) { |
|
1266 | if (evt.type === 'touchstart') { |
|
1267 | initialTouchStartX = touches[0].clientX; |
|
1268 | initialTouchStartY = touches[0].clientY; |
|
1269 | return true; // don't block event default |
|
1270 | } else { |
|
1271 | // prevent scroll from triggering event |
|
1272 | if (evt.type === 'touchend') { |
|
1273 | var currentX = touches[0].clientX; |
|
1274 | var currentY = touches[0].clientY; |
|
1275 | if ((Math.abs(currentX - initialTouchStartX) > 20) || |
|
1276 | (Math.abs(currentY - initialTouchStartY) > 20)) { |
|
1277 | evt.stopPropagation(); |
|
1278 | evt.preventDefault(); |
|
1279 | return false; |
|
1280 | } |
|
1281 | } |
|
1282 | return true; |
|
1283 | } |
|
1284 | } |
|
1285 | } |
|
1286 | ||
1287 | var fileElem = elem; |
|
1288 | ||
1289 | function resetModel(evt) { |
|
1290 | if (upload.shouldUpdateOn('click', attr, scope) && fileElem.val()) { |
|
1291 | fileElem.val(null); |
|
1292 | upload.updateModel(ngModel, attr, scope, fileChangeAttr(), null, evt, true); |
|
1293 | } |
|
1294 | } |
|
1295 | ||
1296 | if (!isInputTypeFile()) { |
|
1297 | fileElem = createFileInput(); |
|
1298 | } |
|
1299 | fileElem.bind('change', changeFn); |
|
1300 | ||
1301 | if (!isInputTypeFile()) { |
|
1302 | elem.bind('click touchstart touchend', clickHandler); |
|
1303 | } else { |
|
1304 | elem.bind('click', resetModel); |
|
1305 | } |
|
1306 | ||
1307 | function ie10SameFileSelectFix(evt) { |
|
1308 | if (fileElem && !fileElem.attr('__ngf_ie10_Fix_')) { |
|
1309 | if (!fileElem[0].parentNode) { |
|
1310 | fileElem = null; |
|
1311 | return; |
|
1312 | } |
|
1313 | evt.preventDefault(); |
|
1314 | evt.stopPropagation(); |
|
1315 | fileElem.unbind('click'); |
|
1316 | var clone = fileElem.clone(); |
|
1317 | fileElem.replaceWith(clone); |
|
1318 | fileElem = clone; |
|
1319 | fileElem.attr('__ngf_ie10_Fix_', 'true'); |
|
1320 | fileElem.bind('change', changeFn); |
|
1321 | fileElem.bind('click', ie10SameFileSelectFix); |
|
1322 | fileElem[0].click(); |
|
1323 | return false; |
|
1324 | } else { |
|
1325 | fileElem.removeAttr('__ngf_ie10_Fix_'); |
|
1326 | } |
|
1327 | } |
|
1328 | ||
1329 | if (navigator.appVersion.indexOf('MSIE 10') !== -1) { |
|
1330 | fileElem.bind('click', ie10SameFileSelectFix); |
|
1331 | } |
|
1332 | ||
1333 | if (ngModel) ngModel.$formatters.push(function (val) { |
|
1334 | if (val == null || val.length === 0) { |
|
1335 | if (fileElem.val()) { |
|
1336 | fileElem.val(null); |
|
1337 | } |
|
1338 | } |
|
1339 | return val; |
|
1340 | }); |
|
1341 | ||
1342 | scope.$on('$destroy', function () { |
|
1343 | if (!isInputTypeFile()) fileElem.parent().remove(); |
|
1344 | angular.forEach(unwatches, function (unwatch) { |
|
1345 | unwatch(); |
|
1346 | }); |
|
1347 | }); |
|
1348 | ||
1349 | $timeout(function () { |
|
1350 | for (var i = 0; i < generatedElems.length; i++) { |
|
1351 | var g = generatedElems[i]; |
|
1352 | if (!document.body.contains(g.el[0])) { |
|
1353 | generatedElems.splice(i, 1); |
|
1354 | g.ref.remove(); |
|
1355 | } |
|
1356 | } |
|
1357 | }); |
|
1358 | ||
1359 | if (window.FileAPI && window.FileAPI.ngfFixIE) { |
|
1360 | window.FileAPI.ngfFixIE(elem, fileElem, changeFn); |
|
1361 | } |
|
1362 | } |
|
1363 | ||
1364 | return { |
|
1365 | restrict: 'AEC', |
|
1366 | require: '?ngModel', |
|
1367 | link: function (scope, elem, attr, ngModel) { |
|
1368 | linkFileSelect(scope, elem, attr, ngModel, $parse, $timeout, $compile, Upload); |
|
1369 | } |
|
1370 | }; |
|
1371 | }]); |
|
1372 | ||
1373 | (function () { |
|
1374 |
@@ 696-949 (lines=254) @@ | ||
693 | return upload; |
|
694 | }]); |
|
695 | ||
696 | ngFileUpload.directive('ngfSelect', ['$parse', '$timeout', '$compile', 'Upload', function ($parse, $timeout, $compile, Upload) { |
|
697 | var generatedElems = []; |
|
698 | ||
699 | function isDelayedClickSupported(ua) { |
|
700 | // fix for android native browser < 4.4 and safari windows |
|
701 | var m = ua.match(/Android[^\d]*(\d+)\.(\d+)/); |
|
702 | if (m && m.length > 2) { |
|
703 | var v = Upload.defaults.androidFixMinorVersion || 4; |
|
704 | return parseInt(m[1]) < 4 || (parseInt(m[1]) === v && parseInt(m[2]) < v); |
|
705 | } |
|
706 | ||
707 | // safari on windows |
|
708 | return ua.indexOf('Chrome') === -1 && /.*Windows.*Safari.*/.test(ua); |
|
709 | } |
|
710 | ||
711 | function linkFileSelect(scope, elem, attr, ngModel, $parse, $timeout, $compile, upload) { |
|
712 | /** @namespace attr.ngfSelect */ |
|
713 | /** @namespace attr.ngfChange */ |
|
714 | /** @namespace attr.ngModel */ |
|
715 | /** @namespace attr.ngfModelOptions */ |
|
716 | /** @namespace attr.ngfMultiple */ |
|
717 | /** @namespace attr.ngfCapture */ |
|
718 | /** @namespace attr.ngfValidate */ |
|
719 | /** @namespace attr.ngfKeep */ |
|
720 | var attrGetter = function (name, scope) { |
|
721 | return upload.attrGetter(name, attr, scope); |
|
722 | }; |
|
723 | ||
724 | function isInputTypeFile() { |
|
725 | return elem[0].tagName.toLowerCase() === 'input' && attr.type && attr.type.toLowerCase() === 'file'; |
|
726 | } |
|
727 | ||
728 | function fileChangeAttr() { |
|
729 | return attrGetter('ngfChange') || attrGetter('ngfSelect'); |
|
730 | } |
|
731 | ||
732 | function changeFn(evt) { |
|
733 | if (upload.shouldUpdateOn('change', attr, scope)) { |
|
734 | var fileList = evt.__files_ || (evt.target && evt.target.files), files = []; |
|
735 | /* Handle duplicate call in IE11 */ |
|
736 | if (!fileList) return; |
|
737 | for (var i = 0; i < fileList.length; i++) { |
|
738 | files.push(fileList[i]); |
|
739 | } |
|
740 | upload.updateModel(ngModel, attr, scope, fileChangeAttr(), |
|
741 | files.length ? files : null, evt); |
|
742 | } |
|
743 | } |
|
744 | ||
745 | upload.registerModelChangeValidator(ngModel, attr, scope); |
|
746 | ||
747 | var unwatches = []; |
|
748 | if (attrGetter('ngfMultiple')) { |
|
749 | unwatches.push(scope.$watch(attrGetter('ngfMultiple'), function () { |
|
750 | fileElem.attr('multiple', attrGetter('ngfMultiple', scope)); |
|
751 | })); |
|
752 | } |
|
753 | if (attrGetter('ngfCapture')) { |
|
754 | unwatches.push(scope.$watch(attrGetter('ngfCapture'), function () { |
|
755 | fileElem.attr('capture', attrGetter('ngfCapture', scope)); |
|
756 | })); |
|
757 | } |
|
758 | if (attrGetter('ngfAccept')) { |
|
759 | unwatches.push(scope.$watch(attrGetter('ngfAccept'), function () { |
|
760 | fileElem.attr('accept', attrGetter('ngfAccept', scope)); |
|
761 | })); |
|
762 | } |
|
763 | unwatches.push(attr.$observe('accept', function () { |
|
764 | fileElem.attr('accept', attrGetter('accept')); |
|
765 | })); |
|
766 | function bindAttrToFileInput(fileElem, label) { |
|
767 | function updateId(val) { |
|
768 | fileElem.attr('id', 'ngf-' + val); |
|
769 | label.attr('id', 'ngf-label-' + val); |
|
770 | } |
|
771 | ||
772 | for (var i = 0; i < elem[0].attributes.length; i++) { |
|
773 | var attribute = elem[0].attributes[i]; |
|
774 | if (attribute.name !== 'type' && attribute.name !== 'class' && attribute.name !== 'style') { |
|
775 | if (attribute.name === 'id') { |
|
776 | updateId(attribute.value); |
|
777 | unwatches.push(attr.$observe('id', updateId)); |
|
778 | } else { |
|
779 | fileElem.attr(attribute.name, (!attribute.value && (attribute.name === 'required' || |
|
780 | attribute.name === 'multiple')) ? attribute.name : attribute.value); |
|
781 | } |
|
782 | } |
|
783 | } |
|
784 | } |
|
785 | ||
786 | function createFileInput() { |
|
787 | if (isInputTypeFile()) { |
|
788 | return elem; |
|
789 | } |
|
790 | ||
791 | var fileElem = angular.element('<input type="file">'); |
|
792 | ||
793 | var label = angular.element('<label>upload</label>'); |
|
794 | label.css('visibility', 'hidden').css('position', 'absolute').css('overflow', 'hidden') |
|
795 | .css('width', '0px').css('height', '0px').css('border', 'none') |
|
796 | .css('margin', '0px').css('padding', '0px').attr('tabindex', '-1'); |
|
797 | bindAttrToFileInput(fileElem, label); |
|
798 | ||
799 | generatedElems.push({el: elem, ref: label}); |
|
800 | ||
801 | document.body.appendChild(label.append(fileElem)[0]); |
|
802 | ||
803 | return fileElem; |
|
804 | } |
|
805 | ||
806 | function clickHandler(evt) { |
|
807 | if (elem.attr('disabled')) return false; |
|
808 | if (attrGetter('ngfSelectDisabled', scope)) return; |
|
809 | ||
810 | var r = detectSwipe(evt); |
|
811 | // prevent the click if it is a swipe |
|
812 | if (r != null) return r; |
|
813 | ||
814 | resetModel(evt); |
|
815 | ||
816 | // fix for md when the element is removed from the DOM and added back #460 |
|
817 | try { |
|
818 | if (!isInputTypeFile() && !document.body.contains(fileElem[0])) { |
|
819 | generatedElems.push({el: elem, ref: fileElem.parent()}); |
|
820 | document.body.appendChild(fileElem.parent()[0]); |
|
821 | fileElem.bind('change', changeFn); |
|
822 | } |
|
823 | } catch (e) {/*ignore*/ |
|
824 | } |
|
825 | ||
826 | if (isDelayedClickSupported(navigator.userAgent)) { |
|
827 | setTimeout(function () { |
|
828 | fileElem[0].click(); |
|
829 | }, 0); |
|
830 | } else { |
|
831 | fileElem[0].click(); |
|
832 | } |
|
833 | ||
834 | return false; |
|
835 | } |
|
836 | ||
837 | ||
838 | var initialTouchStartY = 0; |
|
839 | var initialTouchStartX = 0; |
|
840 | ||
841 | function detectSwipe(evt) { |
|
842 | var touches = evt.changedTouches || (evt.originalEvent && evt.originalEvent.changedTouches); |
|
843 | if (touches) { |
|
844 | if (evt.type === 'touchstart') { |
|
845 | initialTouchStartX = touches[0].clientX; |
|
846 | initialTouchStartY = touches[0].clientY; |
|
847 | return true; // don't block event default |
|
848 | } else { |
|
849 | // prevent scroll from triggering event |
|
850 | if (evt.type === 'touchend') { |
|
851 | var currentX = touches[0].clientX; |
|
852 | var currentY = touches[0].clientY; |
|
853 | if ((Math.abs(currentX - initialTouchStartX) > 20) || |
|
854 | (Math.abs(currentY - initialTouchStartY) > 20)) { |
|
855 | evt.stopPropagation(); |
|
856 | evt.preventDefault(); |
|
857 | return false; |
|
858 | } |
|
859 | } |
|
860 | return true; |
|
861 | } |
|
862 | } |
|
863 | } |
|
864 | ||
865 | var fileElem = elem; |
|
866 | ||
867 | function resetModel(evt) { |
|
868 | if (upload.shouldUpdateOn('click', attr, scope) && fileElem.val()) { |
|
869 | fileElem.val(null); |
|
870 | upload.updateModel(ngModel, attr, scope, fileChangeAttr(), null, evt, true); |
|
871 | } |
|
872 | } |
|
873 | ||
874 | if (!isInputTypeFile()) { |
|
875 | fileElem = createFileInput(); |
|
876 | } |
|
877 | fileElem.bind('change', changeFn); |
|
878 | ||
879 | if (!isInputTypeFile()) { |
|
880 | elem.bind('click touchstart touchend', clickHandler); |
|
881 | } else { |
|
882 | elem.bind('click', resetModel); |
|
883 | } |
|
884 | ||
885 | function ie10SameFileSelectFix(evt) { |
|
886 | if (fileElem && !fileElem.attr('__ngf_ie10_Fix_')) { |
|
887 | if (!fileElem[0].parentNode) { |
|
888 | fileElem = null; |
|
889 | return; |
|
890 | } |
|
891 | evt.preventDefault(); |
|
892 | evt.stopPropagation(); |
|
893 | fileElem.unbind('click'); |
|
894 | var clone = fileElem.clone(); |
|
895 | fileElem.replaceWith(clone); |
|
896 | fileElem = clone; |
|
897 | fileElem.attr('__ngf_ie10_Fix_', 'true'); |
|
898 | fileElem.bind('change', changeFn); |
|
899 | fileElem.bind('click', ie10SameFileSelectFix); |
|
900 | fileElem[0].click(); |
|
901 | return false; |
|
902 | } else { |
|
903 | fileElem.removeAttr('__ngf_ie10_Fix_'); |
|
904 | } |
|
905 | } |
|
906 | ||
907 | if (navigator.appVersion.indexOf('MSIE 10') !== -1) { |
|
908 | fileElem.bind('click', ie10SameFileSelectFix); |
|
909 | } |
|
910 | ||
911 | if (ngModel) ngModel.$formatters.push(function (val) { |
|
912 | if (val == null || val.length === 0) { |
|
913 | if (fileElem.val()) { |
|
914 | fileElem.val(null); |
|
915 | } |
|
916 | } |
|
917 | return val; |
|
918 | }); |
|
919 | ||
920 | scope.$on('$destroy', function () { |
|
921 | if (!isInputTypeFile()) fileElem.parent().remove(); |
|
922 | angular.forEach(unwatches, function (unwatch) { |
|
923 | unwatch(); |
|
924 | }); |
|
925 | }); |
|
926 | ||
927 | $timeout(function () { |
|
928 | for (var i = 0; i < generatedElems.length; i++) { |
|
929 | var g = generatedElems[i]; |
|
930 | if (!document.body.contains(g.el[0])) { |
|
931 | generatedElems.splice(i, 1); |
|
932 | g.ref.remove(); |
|
933 | } |
|
934 | } |
|
935 | }); |
|
936 | ||
937 | if (window.FileAPI && window.FileAPI.ngfFixIE) { |
|
938 | window.FileAPI.ngfFixIE(elem, fileElem, changeFn); |
|
939 | } |
|
940 | } |
|
941 | ||
942 | return { |
|
943 | restrict: 'AEC', |
|
944 | require: '?ngModel', |
|
945 | link: function (scope, elem, attr, ngModel) { |
|
946 | linkFileSelect(scope, elem, attr, ngModel, $parse, $timeout, $compile, Upload); |
|
947 | } |
|
948 | }; |
|
949 | }]); |
|
950 | ||
951 | (function () { |
|
952 |
@@ 1-254 (lines=254) @@ | ||
1 | ngFileUpload.directive('ngfSelect', ['$parse', '$timeout', '$compile', 'Upload', function ($parse, $timeout, $compile, Upload) { |
|
2 | var generatedElems = []; |
|
3 | ||
4 | function isDelayedClickSupported(ua) { |
|
5 | // fix for android native browser < 4.4 and safari windows |
|
6 | var m = ua.match(/Android[^\d]*(\d+)\.(\d+)/); |
|
7 | if (m && m.length > 2) { |
|
8 | var v = Upload.defaults.androidFixMinorVersion || 4; |
|
9 | return parseInt(m[1]) < 4 || (parseInt(m[1]) === v && parseInt(m[2]) < v); |
|
10 | } |
|
11 | ||
12 | // safari on windows |
|
13 | return ua.indexOf('Chrome') === -1 && /.*Windows.*Safari.*/.test(ua); |
|
14 | } |
|
15 | ||
16 | function linkFileSelect(scope, elem, attr, ngModel, $parse, $timeout, $compile, upload) { |
|
17 | /** @namespace attr.ngfSelect */ |
|
18 | /** @namespace attr.ngfChange */ |
|
19 | /** @namespace attr.ngModel */ |
|
20 | /** @namespace attr.ngfModelOptions */ |
|
21 | /** @namespace attr.ngfMultiple */ |
|
22 | /** @namespace attr.ngfCapture */ |
|
23 | /** @namespace attr.ngfValidate */ |
|
24 | /** @namespace attr.ngfKeep */ |
|
25 | var attrGetter = function (name, scope) { |
|
26 | return upload.attrGetter(name, attr, scope); |
|
27 | }; |
|
28 | ||
29 | function isInputTypeFile() { |
|
30 | return elem[0].tagName.toLowerCase() === 'input' && attr.type && attr.type.toLowerCase() === 'file'; |
|
31 | } |
|
32 | ||
33 | function fileChangeAttr() { |
|
34 | return attrGetter('ngfChange') || attrGetter('ngfSelect'); |
|
35 | } |
|
36 | ||
37 | function changeFn(evt) { |
|
38 | if (upload.shouldUpdateOn('change', attr, scope)) { |
|
39 | var fileList = evt.__files_ || (evt.target && evt.target.files), files = []; |
|
40 | /* Handle duplicate call in IE11 */ |
|
41 | if (!fileList) return; |
|
42 | for (var i = 0; i < fileList.length; i++) { |
|
43 | files.push(fileList[i]); |
|
44 | } |
|
45 | upload.updateModel(ngModel, attr, scope, fileChangeAttr(), |
|
46 | files.length ? files : null, evt); |
|
47 | } |
|
48 | } |
|
49 | ||
50 | upload.registerModelChangeValidator(ngModel, attr, scope); |
|
51 | ||
52 | var unwatches = []; |
|
53 | if (attrGetter('ngfMultiple')) { |
|
54 | unwatches.push(scope.$watch(attrGetter('ngfMultiple'), function () { |
|
55 | fileElem.attr('multiple', attrGetter('ngfMultiple', scope)); |
|
56 | })); |
|
57 | } |
|
58 | if (attrGetter('ngfCapture')) { |
|
59 | unwatches.push(scope.$watch(attrGetter('ngfCapture'), function () { |
|
60 | fileElem.attr('capture', attrGetter('ngfCapture', scope)); |
|
61 | })); |
|
62 | } |
|
63 | if (attrGetter('ngfAccept')) { |
|
64 | unwatches.push(scope.$watch(attrGetter('ngfAccept'), function () { |
|
65 | fileElem.attr('accept', attrGetter('ngfAccept', scope)); |
|
66 | })); |
|
67 | } |
|
68 | unwatches.push(attr.$observe('accept', function () { |
|
69 | fileElem.attr('accept', attrGetter('accept')); |
|
70 | })); |
|
71 | function bindAttrToFileInput(fileElem, label) { |
|
72 | function updateId(val) { |
|
73 | fileElem.attr('id', 'ngf-' + val); |
|
74 | label.attr('id', 'ngf-label-' + val); |
|
75 | } |
|
76 | ||
77 | for (var i = 0; i < elem[0].attributes.length; i++) { |
|
78 | var attribute = elem[0].attributes[i]; |
|
79 | if (attribute.name !== 'type' && attribute.name !== 'class' && attribute.name !== 'style') { |
|
80 | if (attribute.name === 'id') { |
|
81 | updateId(attribute.value); |
|
82 | unwatches.push(attr.$observe('id', updateId)); |
|
83 | } else { |
|
84 | fileElem.attr(attribute.name, (!attribute.value && (attribute.name === 'required' || |
|
85 | attribute.name === 'multiple')) ? attribute.name : attribute.value); |
|
86 | } |
|
87 | } |
|
88 | } |
|
89 | } |
|
90 | ||
91 | function createFileInput() { |
|
92 | if (isInputTypeFile()) { |
|
93 | return elem; |
|
94 | } |
|
95 | ||
96 | var fileElem = angular.element('<input type="file">'); |
|
97 | ||
98 | var label = angular.element('<label>upload</label>'); |
|
99 | label.css('visibility', 'hidden').css('position', 'absolute').css('overflow', 'hidden') |
|
100 | .css('width', '0px').css('height', '0px').css('border', 'none') |
|
101 | .css('margin', '0px').css('padding', '0px').attr('tabindex', '-1'); |
|
102 | bindAttrToFileInput(fileElem, label); |
|
103 | ||
104 | generatedElems.push({el: elem, ref: label}); |
|
105 | ||
106 | document.body.appendChild(label.append(fileElem)[0]); |
|
107 | ||
108 | return fileElem; |
|
109 | } |
|
110 | ||
111 | function clickHandler(evt) { |
|
112 | if (elem.attr('disabled')) return false; |
|
113 | if (attrGetter('ngfSelectDisabled', scope)) return; |
|
114 | ||
115 | var r = detectSwipe(evt); |
|
116 | // prevent the click if it is a swipe |
|
117 | if (r != null) return r; |
|
118 | ||
119 | resetModel(evt); |
|
120 | ||
121 | // fix for md when the element is removed from the DOM and added back #460 |
|
122 | try { |
|
123 | if (!isInputTypeFile() && !document.body.contains(fileElem[0])) { |
|
124 | generatedElems.push({el: elem, ref: fileElem.parent()}); |
|
125 | document.body.appendChild(fileElem.parent()[0]); |
|
126 | fileElem.bind('change', changeFn); |
|
127 | } |
|
128 | } catch (e) {/*ignore*/ |
|
129 | } |
|
130 | ||
131 | if (isDelayedClickSupported(navigator.userAgent)) { |
|
132 | setTimeout(function () { |
|
133 | fileElem[0].click(); |
|
134 | }, 0); |
|
135 | } else { |
|
136 | fileElem[0].click(); |
|
137 | } |
|
138 | ||
139 | return false; |
|
140 | } |
|
141 | ||
142 | ||
143 | var initialTouchStartY = 0; |
|
144 | var initialTouchStartX = 0; |
|
145 | ||
146 | function detectSwipe(evt) { |
|
147 | var touches = evt.changedTouches || (evt.originalEvent && evt.originalEvent.changedTouches); |
|
148 | if (touches) { |
|
149 | if (evt.type === 'touchstart') { |
|
150 | initialTouchStartX = touches[0].clientX; |
|
151 | initialTouchStartY = touches[0].clientY; |
|
152 | return true; // don't block event default |
|
153 | } else { |
|
154 | // prevent scroll from triggering event |
|
155 | if (evt.type === 'touchend') { |
|
156 | var currentX = touches[0].clientX; |
|
157 | var currentY = touches[0].clientY; |
|
158 | if ((Math.abs(currentX - initialTouchStartX) > 20) || |
|
159 | (Math.abs(currentY - initialTouchStartY) > 20)) { |
|
160 | evt.stopPropagation(); |
|
161 | evt.preventDefault(); |
|
162 | return false; |
|
163 | } |
|
164 | } |
|
165 | return true; |
|
166 | } |
|
167 | } |
|
168 | } |
|
169 | ||
170 | var fileElem = elem; |
|
171 | ||
172 | function resetModel(evt) { |
|
173 | if (upload.shouldUpdateOn('click', attr, scope) && fileElem.val()) { |
|
174 | fileElem.val(null); |
|
175 | upload.updateModel(ngModel, attr, scope, fileChangeAttr(), null, evt, true); |
|
176 | } |
|
177 | } |
|
178 | ||
179 | if (!isInputTypeFile()) { |
|
180 | fileElem = createFileInput(); |
|
181 | } |
|
182 | fileElem.bind('change', changeFn); |
|
183 | ||
184 | if (!isInputTypeFile()) { |
|
185 | elem.bind('click touchstart touchend', clickHandler); |
|
186 | } else { |
|
187 | elem.bind('click', resetModel); |
|
188 | } |
|
189 | ||
190 | function ie10SameFileSelectFix(evt) { |
|
191 | if (fileElem && !fileElem.attr('__ngf_ie10_Fix_')) { |
|
192 | if (!fileElem[0].parentNode) { |
|
193 | fileElem = null; |
|
194 | return; |
|
195 | } |
|
196 | evt.preventDefault(); |
|
197 | evt.stopPropagation(); |
|
198 | fileElem.unbind('click'); |
|
199 | var clone = fileElem.clone(); |
|
200 | fileElem.replaceWith(clone); |
|
201 | fileElem = clone; |
|
202 | fileElem.attr('__ngf_ie10_Fix_', 'true'); |
|
203 | fileElem.bind('change', changeFn); |
|
204 | fileElem.bind('click', ie10SameFileSelectFix); |
|
205 | fileElem[0].click(); |
|
206 | return false; |
|
207 | } else { |
|
208 | fileElem.removeAttr('__ngf_ie10_Fix_'); |
|
209 | } |
|
210 | } |
|
211 | ||
212 | if (navigator.appVersion.indexOf('MSIE 10') !== -1) { |
|
213 | fileElem.bind('click', ie10SameFileSelectFix); |
|
214 | } |
|
215 | ||
216 | if (ngModel) ngModel.$formatters.push(function (val) { |
|
217 | if (val == null || val.length === 0) { |
|
218 | if (fileElem.val()) { |
|
219 | fileElem.val(null); |
|
220 | } |
|
221 | } |
|
222 | return val; |
|
223 | }); |
|
224 | ||
225 | scope.$on('$destroy', function () { |
|
226 | if (!isInputTypeFile()) fileElem.parent().remove(); |
|
227 | angular.forEach(unwatches, function (unwatch) { |
|
228 | unwatch(); |
|
229 | }); |
|
230 | }); |
|
231 | ||
232 | $timeout(function () { |
|
233 | for (var i = 0; i < generatedElems.length; i++) { |
|
234 | var g = generatedElems[i]; |
|
235 | if (!document.body.contains(g.el[0])) { |
|
236 | generatedElems.splice(i, 1); |
|
237 | g.ref.remove(); |
|
238 | } |
|
239 | } |
|
240 | }); |
|
241 | ||
242 | if (window.FileAPI && window.FileAPI.ngfFixIE) { |
|
243 | window.FileAPI.ngfFixIE(elem, fileElem, changeFn); |
|
244 | } |
|
245 | } |
|
246 | ||
247 | return { |
|
248 | restrict: 'AEC', |
|
249 | require: '?ngModel', |
|
250 | link: function (scope, elem, attr, ngModel) { |
|
251 | linkFileSelect(scope, elem, attr, ngModel, $parse, $timeout, $compile, Upload); |
|
252 | } |
|
253 | }; |
|
254 | }]); |
|
255 |