diff --git a/.jshintrc b/.jshintrc index 8fe58ca6..952163d3 100644 --- a/.jshintrc +++ b/.jshintrc @@ -14,7 +14,7 @@ "regexp": true, "undef": true, "unused": true, - "strict": true, + "strict": false, "trailing": true, "smarttabs": true, "globals": { diff --git a/Makefile b/Makefile index fdde9c5e..e61596cc 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,9 @@ install: ./node_modules/protractor/bin/webdriver-manager update run: + @cp node_modules/angular/angular.min.js examples/blog/build/angular.min.js @cp node_modules/sinon/pkg/sinon-server-1.14.1.js examples/blog/build/sinon-server.js + @cp node_modules/angular/angular.js examples/blog/build/angular.js @./node_modules/webpack-dev-server/bin/webpack-dev-server.js --colors --devtool cheap-module-inline-source-map --content-base examples/blog --port 8000 build: diff --git a/examples/blog/config.js b/examples/blog/config.js index ecfb8325..b7a48996 100644 --- a/examples/blog/config.js +++ b/examples/blog/config.js @@ -281,8 +281,10 @@ nga.field('name'), nga.field('published', 'boolean').validation({ required: true // as this boolean is required, ng-admin will use a checkbox instead of a dropdown - }) - ]); + }), + nga.field('image', 'file') + .uploadInformation({ url: 'http://localhost:3333/upload' }) + ]) tag.showView() .fields([ diff --git a/examples/blog/fakerest-init.js b/examples/blog/fakerest-init.js index 570456b3..e9a403bb 100644 --- a/examples/blog/fakerest-init.js +++ b/examples/blog/fakerest-init.js @@ -7,14 +7,6 @@ restServer.init(apiData); restServer.toggleLogging(); // logging is off by default, enable it - // use sinon.js to monkey-patch XmlHttpRequest - sinon.FakeXMLHttpRequest.useFilters = true; - sinon.FakeXMLHttpRequest.addFilter(function (method, url) { - // Do not catch webpack sync, config.js transformation but catch /upload in test env - return url.indexOf('/socket.io/') !== -1 || url.indexOf('config.js') !== -1 - || (!testEnv && url.indexOf('/upload') !== -1); - }); - var server = sinon.fakeServer.create(); server.autoRespond = true; server.autoRespondAfter = 0; // answer immediately @@ -28,4 +20,11 @@ } }); } + + // use sinon.js to monkey-patch XmlHttpRequest + sinon.FakeXMLHttpRequest.useFilters = true; + sinon.FakeXMLHttpRequest.addFilter(function (method, url) { + // do not fake other urls than http://localhost:3000/* + return url.indexOf(restServer.baseUrl) === -1; + }); }()); diff --git a/examples/blog/index.html b/examples/blog/index.html index 53fb2193..09b1d9be 100644 --- a/examples/blog/index.html +++ b/examples/blog/index.html @@ -8,11 +8,14 @@
+ + - + + diff --git a/package.json b/package.json index f4526ec3..6d7fdc2d 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "mocha": "^2.1.0", "ng-annotate-loader": "0.0.2", "ng-annotate-webpack-plugin": "^0.1.2", - "ng-file-upload": "^7.0.12", + "ng-file-upload": "^9.0.8", "nginflection": "^1.1.10", "ngtemplate-loader": "^1.3.0", "node-libs-browser": "^0.5.0", diff --git a/src/javascripts/ng-admin/Crud/field/maFileField.js b/src/javascripts/ng-admin/Crud/field/maFileField.js index 67a8ab1f..2665a7e9 100644 --- a/src/javascripts/ng-admin/Crud/field/maFileField.js +++ b/src/javascripts/ng-admin/Crud/field/maFileField.js @@ -12,7 +12,8 @@ define(function (require) { return { scope: { 'field': '&', - 'value': '=' + 'value': '=', + 'files': '=', }, restrict: 'E', link: { @@ -34,7 +35,10 @@ define(function (require) { for (var file in files) { scope.files[files[file]] = { "name": files[file], - "progress": 0 + "progress": 0, + "success": null, + "error": null, + "new": false, }; } }, @@ -45,12 +49,15 @@ define(function (require) { if (scope.value) { scope.v.required = false; } + scope.buttonLabel = field.buttonLabel(); var input = element.find('input')[0]; var attributes = field.attributes(); for (var name in attributes) { input.setAttribute(name, attributes[name]); } + scope.uploadInProgress = false; + scope.fileSelected = function(selectedFiles) { if (!selectedFiles || !selectedFiles.length) { return; @@ -58,36 +65,54 @@ define(function (require) { var uploadParams; + scope.uploadInProgress = true; + var fileUploaded = 0; + scope.files = {}; for (var file in selectedFiles) { uploadParams = angular.copy(scope.field().uploadInformation()); - uploadParams.file = selectedFiles[file]; + uploadParams.data = { file: selectedFiles[file] }; Upload .upload(uploadParams) - .progress(function(evt) { - scope.files[evt.config.file.name] = { - "name": evt.config.file.name, - "progress": Math.min(100, parseInt(100.0 * evt.loaded / evt.total)) + .then((response) => { + const fileName = response.config.data.file.name; + scope.files[fileName] = { + "name": scope.apifilename && response.data[scope.apifilename] ? response.data[scope.apifilename] : fileName, + "progress": 0, + "success": response.data, + "new": true, }; - }) - .success(function(data, status, headers, config) { - scope.files[config.file.name] = { - "name": scope.apifilename ? data[scope.apifilename] : config.file.name, - "progress": 0 + var names = Object.keys(scope.files).map(function(fileindex) { + return scope.files[fileindex].name; + }); + console.log(scope.files); + scope.value = names.join(','); + ++fileUploaded; + if (fileUploaded === selectedFiles.length) { + scope.uploadInProgress = false; + } + }, (response) => { + const fileName = response.config.data.file.name; + scope.files[fileName] = { + "name": fileName, + "progress": 0, + "error": response.data, + "new": true, }; - if (scope.apifilename) { - var apiNames = Object.keys(scope.files).map(function(fileindex) { - return scope.files[fileindex].name; - }); - scope.value = apiNames.join(','); - } else { - scope.value = Object.keys(scope.files).join(','); + console.log(scope.files); + ++fileUploaded; + if (fileUploaded === selectedFiles.length) { + scope.uploadInProgress = false; } - }) - .error(function(data, status, headers, config) { - delete scope.files[config.file.name]; - - scope.value = Object.keys(scope.files).join(','); + }, (evt) => { + const name = evt.config.data.file.name; + const progress = Math.min(100, parseInt(100.0 * evt.loaded / evt.total)); + scope.files[name] = { + name, + progress, + "new": true, + }; + console.log(scope.files); }); } }; @@ -100,8 +125,8 @@ define(function (require) { template: '
' + '
' + - '' + - 'Browse' + + '' + + '{{ buttonLabel }}' + '' + '
' + '
' + @@ -113,7 +138,12 @@ define(function (require) { '
' + '
' + '' + - '
{{ file.name }}
' + + '
' + + '{{ file.name }} successfully uploaded' + + '{{ file.name }} upload error: "{{ file.error }}"' + + '{{ file.name }}' + + '{{ file.name }}' + + '
' + '' + '' + '' + diff --git a/src/javascripts/ng-admin/Crud/fieldView/FileFieldView.js b/src/javascripts/ng-admin/Crud/fieldView/FileFieldView.js index 55bdf518..8e89f1ab 100644 --- a/src/javascripts/ng-admin/Crud/fieldView/FileFieldView.js +++ b/src/javascripts/ng-admin/Crud/fieldView/FileFieldView.js @@ -2,5 +2,5 @@ module.exports = { getReadWidget: () => 'error: cannot display file field as readable', getLinkWidget: () => 'error: cannot display file field as linkable', getFilterWidget: () => 'error: cannot display file field as filter', - getWriteWidget: () => '' + getWriteWidget: () => '' }; diff --git a/webpack.config.js b/webpack.config.js index 8ed2d874..fe988f81 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -10,10 +10,11 @@ function getEntrySources(sources) { var ngAdminSources = [ './src/javascripts/ng-admin.js', - './src/sass/ng-admin.scss' + './src/sass/ng-admin.scss', ]; var ngAdminAndVendorSources = [ + 'angular/angular.js', './src/javascripts/ng-admin.js', './src/javascripts/vendors.js', 'font-awesome/scss/font-awesome.scss', @@ -24,18 +25,55 @@ var ngAdminAndVendorSources = [ 'codemirror/lib/codemirror.css', 'codemirror/addon/lint/lint.css', 'ui-select/dist/select.css', - './src/sass/ng-admin.scss' + './src/sass/ng-admin.scss', +]; + +var vendorsJsSources = [ + './src/javascripts/vendors.js', +]; + +var vendorsCssSources = [ + 'font-awesome/scss/font-awesome.scss', + 'bootstrap-sass/assets/stylesheets/_bootstrap.scss', + 'nprogress/nprogress.css', + 'humane-js/themes/flatty.css', + 'textangular/src/textAngular.css', + 'codemirror/lib/codemirror.css', + 'codemirror/addon/lint/lint.css', + 'ui-select/dist/select.css', + './src/sass/ng-admin.scss', +]; + +var vendorsJsSources = [ + './src/javascripts/vendors.js' ]; +var vendorsCssSources = [ + 'font-awesome/scss/font-awesome.scss', + 'bootstrap-sass/assets/stylesheets/_bootstrap.scss', + 'nprogress/nprogress.css', + 'humane-js/themes/flatty.css', + 'textangular/src/textAngular.css', + 'codemirror/lib/codemirror.css', + 'codemirror/addon/lint/lint.css', + 'ui-select/dist/select.css', + './src/sass/ng-admin.scss' + ]; + module.exports = { entry: { 'ng-admin': getEntrySources(ngAdminAndVendorSources), - 'ng-admin-only': getEntrySources(ngAdminSources) + 'ng-admin-only': getEntrySources(ngAdminSources), + 'ng-admin-vendors-js': getEntrySources(vendorsJsSources), + 'ng-admin-vendors-css': getEntrySources(vendorsCssSources) }, output: { publicPath: "http://localhost:8000/", filename: "build/[name].min.js" }, + externals: { + 'angular': 'angular' + }, module: { loaders: [ { test: /\.js/, loaders: ['babel'], exclude: /node_modules\/(?!admin-config)/ },