diff --git a/app/partials/common.jade b/app/partials/common.jade
index 8c65256be8..636624496f 100644
--- a/app/partials/common.jade
+++ b/app/partials/common.jade
@@ -1,4 +1,3 @@
.left-nav(ui-view="left", ng-class="{toggled: menu.isCollapsed}")
.top-view(ui-view="top")
.main-container(ui-view="right", scroll-to-top, in-view-container, ng-click="hideMenu()")
-downloader
diff --git a/app/partials/export-history.jade b/app/partials/export-history.jade
index dce506da1f..a6aeec2665 100644
--- a/app/partials/export-history.jade
+++ b/app/partials/export-history.jade
@@ -5,10 +5,10 @@ form(role="form" name="exportForm" ng-submit="submit()" autocomplete="off" noval
helper-button(content="EXPORT_HISTORY_EXPLAIN")
.modal-body
- .ph-form
- .flex-row.pal
- .flex-1
- .flex-4.form-group(ng-show="activeCount > 1")
+ .ph-form.flex-row
+ .flex-1
+ .flex-4
+ .form-group.pal(ng-show="activeCount > 1")
ui-select.send-from-dropdown(
name="active"
ng-model="$parent.active"
@@ -18,10 +18,7 @@ form(role="form" name="exportForm" ng-submit="submit()" autocomplete="off" noval
label-origin(origin="$select.selected")
ui-select-choices(repeat="t in targets | filter:{label:$select.search} | limitTo:limit")
label-origin(origin="::t" in-view="$last && isLast(t) && incLimit()" highlight="$select.search")
- .flex-1
-
- .flex-row
- .flex-3.flex-column.flex-center.flex-around.pam(ng-class="{'has-error':exportForm.$invalid || start.date > end.date}")
+ .flex-row.flex-center.flex-between.pal(ng-class="{'has-error':exportForm.$invalid || start.date > end.date}")
p.input-group.flex-center
input.form-control(
type="text"
@@ -33,7 +30,7 @@ form(role="form" name="exportForm" ng-submit="submit()" autocomplete="off" noval
placeholder="{{'START_DATE'|translate}}"
required)
span.ti-calendar.pointer(ng-click="start.open=true")
- i.ti-arrow-down.blue
+ i.ti-arrow-right.blue
p.input-group.flex-center
input.form-control(
type="text"
@@ -45,14 +42,8 @@ form(role="form" name="exportForm" ng-submit="submit()" autocomplete="off" noval
placeholder="{{'END_DATE'|translate}}"
required)
span.ti-calendar.pointer(ng-click="end.open=true")
+ .flex-1
- span.line-split.flex-center.mrl.mll
-
- .flex-3.flex-row.flex-center.flex-around.pam
- label.mll.btn.button-default(ng-model="exportFormat" uib-btn-radio="'csv'") CSV
- span.upper(translate="OR")
- label.mrl.btn.button-default(ng-model="exportFormat" uib-btn-radio="'xls'") XLS
-
.modal-footer
button.button-muted.mrm(
type="button"
@@ -63,4 +54,9 @@ form(role="form" name="exportForm" ng-submit="submit()" autocomplete="off" noval
ui-ladda="busy"
ladda-translate="EXPORT"
data-style="expand-left"
+ ng-show="history == null || canTriggerDownload"
ng-disabled="exportForm.$invalid || start.date > end.date")
+ download-button.btn.button-success(
+ ng-hide="history == null || canTriggerDownload"
+ filename="history.csv"
+ content="history")
diff --git a/assets/js/controllers/exportHistory.controller.js b/assets/js/controllers/exportHistory.controller.js
index b3c02e6bf4..79e5db494a 100644
--- a/assets/js/controllers/exportHistory.controller.js
+++ b/assets/js/controllers/exportHistory.controller.js
@@ -6,6 +6,9 @@ function ExportHistoryController ($scope, $sce, $translate, $filter, format, Wal
$scope.limit = 50;
$scope.incLimit = () => $scope.limit += 50;
+ $scope.ableBrowsers = ['chrome', 'firefox'];
+ $scope.canTriggerDownload = $scope.ableBrowsers.indexOf(browserDetection().browser) > -1;
+
let accounts = Wallet.accounts().filter(a => !a.archived && a.index != null);
let addresses = Wallet.legacyAddresses().filter(a => !a.archived).map(a => a.address);
@@ -37,7 +40,6 @@ function ExportHistoryController ($scope, $sce, $translate, $filter, format, Wal
$scope.format = 'dd/MM/yyyy';
$scope.options = { minDate: new Date(1231024500000), maxDate: new Date() };
- $scope.exportFormat = 'csv';
$scope.start = { open: false, date: Date.now() - 604800000 };
$scope.end = { open: false, date: Date.now() };
@@ -48,6 +50,11 @@ function ExportHistoryController ($scope, $sce, $translate, $filter, format, Wal
let start = $scope.formatDate($scope.start.date);
let end = $scope.formatDate($scope.end.date);
let active = $scope.active.address || $scope.active.xpub;
- Wallet.exportHistory(start, end, active).finally(() => $scope.busy = false);
+ Wallet.exportHistory(start, end, active)
+ .then((data) => {
+ $scope.history = data;
+ $scope.canTriggerDownload && $scope.$broadcast('download');
+ })
+ .finally(() => $scope.busy = false);
};
}
diff --git a/assets/js/directives/download-button.directive.js b/assets/js/directives/download-button.directive.js
new file mode 100644
index 0000000000..055aee4224
--- /dev/null
+++ b/assets/js/directives/download-button.directive.js
@@ -0,0 +1,28 @@
+angular
+ .module('walletApp')
+ .directive('downloadButton', downloadButton);
+
+function downloadButton ($window, $timeout) {
+ const directive = {
+ restrict: 'E',
+ replace: true,
+ scope: {
+ filename: '@',
+ content: '='
+ },
+ template: '{{::"DOWNLOAD"|translate}}',
+ link: link
+ };
+ return directive;
+
+ function link (scope, attr, elem) {
+ scope.$watch('content', (content) => {
+ let blob = new $window.Blob([content], {type: 'text/csv'});
+ scope.dataRef = $window.URL.createObjectURL(blob);
+ });
+ scope.$on('download', (event) => {
+ if (!scope.dataRef) return;
+ $timeout(() => elem.$$element[0].click());
+ });
+ }
+}
diff --git a/assets/js/directives/downloader.directive.js b/assets/js/directives/downloader.directive.js
deleted file mode 100644
index d10038ae4f..0000000000
--- a/assets/js/directives/downloader.directive.js
+++ /dev/null
@@ -1,23 +0,0 @@
-angular
- .module('walletApp')
- .directive('downloader', downloader);
-
-function downloader ($window, $timeout) {
- const directive = {
- restrict: 'E',
- replace: true,
- template: '',
- link: link
- };
- return directive;
-
- function link (scope, attr, elem) {
- scope.$on('download', (event, data) => {
- if (!data.contents || !data.filename) return;
- scope.blob = new $window.Blob([data.contents]);
- scope.dataRef = $window.URL.createObjectURL(scope.blob);
- scope.filename = data.filename;
- $timeout(() => elem.$$element[0].click());
- });
- }
-}
diff --git a/assets/js/services/wallet.service.js b/assets/js/services/wallet.service.js
index c73ca092a0..6c0e6a6814 100644
--- a/assets/js/services/wallet.service.js
+++ b/assets/js/services/wallet.service.js
@@ -1131,15 +1131,15 @@ function Wallet ($http, $window, $timeout, $location, Alerts, MyWallet, MyBlockc
return $q.resolve(p)
.then(history => {
if (!history.length) return $q.reject('NO_HISTORY');
- let contents = json2csv(history.map(addTxNote));
- $rootScope.$broadcast('download', { contents, filename: 'history.csv' });
+ return json2csv(history.map(addTxNote));
})
.catch(e => {
let error = e.message || e;
- if (error && error.indexOf('Too many transactions') > -1) {
+ if (typeof error === 'string' && error.indexOf('Too many transactions') > -1) {
error = 'TOO_MANY_TXS';
}
Alerts.displayError(error || 'UNKNOWN_ERROR');
+ return $q.reject(error);
});
};
diff --git a/locales/en-human.json b/locales/en-human.json
index bcb586af9c..6f4eef306d 100644
--- a/locales/en-human.json
+++ b/locales/en-human.json
@@ -270,6 +270,7 @@
"EXPORTING_FOR" : "Exporting transactions for: {{ name }}",
"NO_HISTORY" : "No transactions found in that range",
"TOO_MANY_TXS" : "Too many transactions to export, maximum of 10,000 allowed",
+ "DOWNLOAD": "Download File",
"SPENDABLE_ADDRESSES" : "Spendable Addresses",
"NEXT_ADDRESSES_FOR_ACCOUNT" : "Unused Addresses in {{account}}",
"PAST_ADDRESSES_FOR_ACCOUNT" : "Used Addresses in {{account}}",
diff --git a/tests/directives/download-button_spec.coffee b/tests/directives/download-button_spec.coffee
new file mode 100644
index 0000000000..052473cbfa
--- /dev/null
+++ b/tests/directives/download-button_spec.coffee
@@ -0,0 +1,39 @@
+describe "downloadButton", ->
+ scope = undefined
+ element = undefined
+ isoScope = undefined
+ $timeout = undefined
+
+ beforeEach module("walletApp")
+
+ beforeEach inject(($compile, $rootScope, $window, _$timeout_) ->
+ $timeout = _$timeout_
+
+ spyOn($window, 'Blob').and.callFake((data) -> toString: -> "data[#{data.join()}]")
+ spyOn($window.URL, 'createObjectURL').and.callFake((obj) -> "blob://#{obj}")
+
+ scope = $rootScope.$new()
+ scope.content = 'asdf'
+ scope.$digest()
+
+ element = $compile("")(scope)
+ isoScope = element.isolateScope()
+ isoScope.$digest()
+ )
+
+ it "should create a data ref for content", ->
+ expect(isoScope.dataRef).toEqual('blob://data[asdf]')
+
+ it "should create a data ref when content is updated", ->
+ isoScope.content = 'abc'
+ isoScope.$digest()
+ expect(isoScope.dataRef).toEqual('blob://data[abc]')
+
+ it "should use the correct filename", ->
+ expect(isoScope.filename).toEqual('test.txt')
+
+ it "should click the anchor tag to trigger download", ->
+ spyOn(element[0], 'click')
+ scope.$broadcast("download")
+ $timeout.flush()
+ expect(element[0].click).toHaveBeenCalled()
diff --git a/tests/directives/downloader_spec.coffee b/tests/directives/downloader_spec.coffee
deleted file mode 100644
index 74e4e31a02..0000000000
--- a/tests/directives/downloader_spec.coffee
+++ /dev/null
@@ -1,33 +0,0 @@
-describe "downloader", ->
- element = undefined
- scope = undefined
- $rootScope = undefined
- $window = undefined
- $timeout = undefined
-
- beforeEach module("walletApp")
-
- beforeEach inject(($compile, _$rootScope_, _$window_, _$timeout_) ->
- $rootScope = _$rootScope_
- $window = _$window_
- $timeout = _$timeout_
-
- scope = $rootScope.$new()
- element = $compile("")(scope)
- )
-
- beforeEach ->
- spyOn($window, 'Blob').and.callFake((data) -> toString: -> "blob[#{data.join()}]")
- spyOn($window.URL, 'createObjectURL').and.callFake((obj) -> "ref://#{obj}")
- $rootScope.$broadcast('download', { contents: 'asdf', filename: 'test.txt' })
-
- it "should create a data ref on receiving download event", ->
- expect(scope.dataRef).toEqual('ref://blob[asdf]')
-
- it "should use the correct filename", ->
- expect(scope.filename).toEqual('test.txt')
-
- it "should click the anchor tag to trigger download", ->
- spyOn(element[0], 'click')
- $timeout.flush()
- expect(element[0].click).toHaveBeenCalled()
diff --git a/tests/services/wallet_service_spec.coffee b/tests/services/wallet_service_spec.coffee
index 7fe19e5e52..c909c7b839 100644
--- a/tests/services/wallet_service_spec.coffee
+++ b/tests/services/wallet_service_spec.coffee
@@ -529,10 +529,8 @@ describe "walletServices", () ->
it "should convert to csv with notes and broadcast broadcast download event", (done) ->
spyOn(Wallet, 'getNote').and.callFake((hash) -> hash == 'asdf' && 'test_note')
spyOn($rootScope, '$broadcast')
- Wallet.exportHistory().then ->
- expect($rootScope.$broadcast).toHaveBeenCalledWith 'download',
- contents: 'sent,receive,tx,note\n1,0,asdf,test_note\n0,2,qwer,'
- filename: 'history.csv'
+ Wallet.exportHistory().then (data) ->
+ expect(data).toEqual('sent,receive,tx,note\n1,0,asdf,test_note\n0,2,qwer,')
done()
$rootScope.$digest()
@@ -541,7 +539,7 @@ describe "walletServices", () ->
spyOn(MyBlockchainApi, 'exportHistory').and.returnValue([])
it "should show an error", (done) ->
- Wallet.exportHistory().then ->
+ Wallet.exportHistory().finally ->
expect(Alerts.displayError).toHaveBeenCalledWith('NO_HISTORY')
done()
$rootScope.$digest()