call function synchronously inside an angular for each - angularjs

I'm using ngCordova File Transfer plugin in an ionic project to download set of images from urls. Here is the code i'm using for that.
// Save a image file in a given directory
$scope.saveImage = function(dir,imgUrl,imageName) {
var url = imgUrl;
var targetPath = cordova.file.dataDirectory+ dir+"/" + imageName;
var trustHosts = true;
var options = {};
// Download the image using cordovafiletransfer plugin
$cordovaFileTransfer.download(url, targetPath, options, trustHosts)
.then(function(result) {
$scope.loadedCount ++;
$ionicLoading.show({template : "<ion-spinner class='spinner-energized'></ion-spinner><p> Downloading pages : "+ $scope.loadedCount+" of "+ $scope.pages.length+ "</p><p>Please wait...</p><p><button class=\"button button-block button-positive\">continue in background</button></p>"});
if($scope.loadedCount == $scope.pages.length) {
$ionicLoading.hide();
$scope.showDownloadSuccessAlert = function() {
var alertPopup = $ionicPopup.alert({
title: 'Success!',
template: "Your magazine successfully downloaded. You can view it on Downloads!"
});
};
$scope.showDownloadSuccessAlert();
}
}, function(err) {
alert(JSON.stringify(err));
}, function (progress) {
if($scope.loadedCount > 80) {
}
});
};
// Download the current magazine
$scope.downloadMagazine = function() {
if($rootScope.user.user_id == undefined) {
$scope.showLoginAlert = function() {
var alertPopup = $ionicPopup.alert({
title: 'Oops!',
template: "Your must login to download magazines"
});
};
$scope.showLoginAlert();
return;
}
document.addEventListener('deviceready', function () {
var dirName = $rootScope.currentIssue.slug+'_VOL_'+$rootScope.currentIssue.vol+'_ISU_'+$rootScope.currentIssue.issue;
// First create the directory
$cordovaFile.createDir(cordova.file.dataDirectory, dirName, false)
.then(function (success) {
var count = 1;
$scope.loadedCount = 0;
angular.forEach($scope.pages, function(value, key) {
var imgName = count+".png";
$scope.saveImage(dirName,value.link,imgName); // Then save images one by one to the created directory.
count++;
});
}, function (error) {
// Directory already exists means that the magazine is already downloaded.
$scope.showDownloadedAlert = function() {
var alertPopup = $ionicPopup.alert({
title: 'Why worry!',
template: "Your have already downloaded this magazine. You can view it on downloads"
});
};
$scope.showDownloadedAlert();
});
}, false);
};
})
Problem here is that program try to download everything at once without waiting for previous one to finish. How to wait for one file to finish downloading and then start the other?
Thanks

If you want to do that automatically (you're not the first one : How can I execute array of promises in sequential order?), you could try reducing the list of address to a single Promise that will do the whole chain.
$scope.pages.reduce(function (curr,next) {
return curr.then(function(){
return $scope.saveImage(dirName, curr.link, imgName);
});
}, Promise.resolve()).then(function(result) {
$ionicLoading.show({template : "<ion-spinner class='spinner-energized'></ion-spinner><p> Downloading pages : "+ $scope.loadedCount+" of "+ $scope.pages.length+ "</p><p>Please wait...</p><p><button class=\"button button-block button-positive\">continue in background</button></p>"});
if($scope.loadedCount == $scope.pages.length) {
$ionicLoading.hide();
$scope.showDownloadSuccessAlert = function() {
var alertPopup = $ionicPopup.alert({
title: 'Success!',
template: "Your magazine successfully downloaded. You can view it on Downloads!"
});
};
$scope.showDownloadSuccessAlert();
}
});
And don't forget to make your saveImage async which returns a promise.
UPDATE:
You will need to remove the then logic from your save method and return the download method call:
return $cordovaFileTransfer.download(url, targetPath, options, trustHosts).promise;
Then you can put your download handler into Promise.resolve()).then. See above.

There's no other way other than chaining your promises. Here's an example:
angular.module('app', [])
.service('fakeDownloadService', function($timeout) {
this.download = (file) => $timeout(() => file, 100);
return this;
})
.run(function($rootScope, $q, fakeDownloadService) {
var listOfFiles = [];
for (var i = 0; i < 10; i++)
listOfFiles.push('file' + i);
$rootScope.log = [];
$rootScope.download = () => {
listOfFiles
.reduce((prev, curr) => {
return prev.then((result) => {
if(result)
$rootScope.log.push(result + ' downloaded');
return fakeDownloadService.download(curr);
});
}, $q.resolve())
.then(() => $rootScope.log.push('all done'));
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.9/angular.min.js"></script>
<div ng-app="app">
<button ng-click="download()">Start</button>
<div>Log:</div>
<ul>
<li ng-repeat="entry in log track by $index">
{{entry}}
</li>
</ul>
</div>

Related

Onsenui with Bluetooth plugin

I am trying to implement a bluetooth plugin using Onsenui in Monaca IDE. I keep getting an error message saying: bluetoothSerial not found.
I want to create a service that requires the Bluetooth Serial plugin. Then simply call this to do a .isenabled() call. Any help would be great.
app.service('iBeaconService', function() {
var bluetoothSerial = new cordova.plugins.bluetoothSerial;
return {
sendMessage: function(message) {
// interact with bluetoothSerial
}
};
});
app.controller('InfoPageCtrl', ['$scope', 'iBeaconService', function($scope, iBeaconService) {
bluetoothSerial.isEnabled(
function() {
console.log("Bluetooth is enabled");
},
function() {
console.log("Bluetooth is *not* enabled");
}
);
}]);
app.controller('AppController', function($scope) {
$scope.load = function(page) {
$scope.mySplitterContent.load(page)
}
$scope.open = function() {
$scope.mySplitterSide.open();
}
});
<ons-list ng-controller="InfoPageCtrl">
<ons-list-item class="list-item-container" >
<ons-row>
<ons-col width="110px">
<img src="{{beacon.icon}}" class="info-page-img">
</ons-col>
<ons-col>
<div class="info-page-description">
<p style="text-decoration: underline;">UUID</p>
{{beaconUuid}}
</div>
</ons-col>
</ons-row>
</ons-list-item>
</ons-list>
How to turn this code to use for Onsenui.
var app = {
initialize: function() {
this.bindEvents();
this.showMainPage();
},
bindEvents: function() {
var TOUCH_START = 'touchstart';
if (window.navigator.msPointerEnabled) { // windows phone
TOUCH_START = 'MSPointerDown';
}
document.addEventListener('deviceready', this.onDeviceReady, false);
refreshButton.addEventListener(TOUCH_START, this.refreshDeviceList, false);
sendButton.addEventListener(TOUCH_START, this.sendData, false);
disconnectButton.addEventListener(TOUCH_START, this.disconnect, false);
deviceList.addEventListener('touchstart', this.connect, false);
},
onDeviceReady: function() {
app.refreshDeviceList();
},
refreshDeviceList: function() {
bluetoothSerial.list(app.onDeviceList, app.onError);
},
onDeviceList: function(devices) {
var option;
// remove existing devices
deviceList.innerHTML = "";
app.setStatus("");
devices.forEach(function(device) {
var listItem = document.createElement('li'),
html = '<b>' + device.name + '</b><br/>' + device.id;
listItem.innerHTML = html;
if (cordova.platformId === 'windowsphone') {
// This is a temporary hack until I get the list tap working
var button = document.createElement('button');
button.innerHTML = "Connect";
button.addEventListener('click', app.connect, false);
button.dataset = {};
button.dataset.deviceId = device.id;
listItem.appendChild(button);
} else {
listItem.dataset.deviceId = device.id;
}
deviceList.appendChild(listItem);
});
if (devices.length === 0) {
option = document.createElement('option');
option.innerHTML = "No Bluetooth Devices";
deviceList.appendChild(option);
if (cordova.platformId === "ios") { // BLE
app.setStatus("No Bluetooth Peripherals Discovered.");
} else { // Android or Windows Phone
app.setStatus("Please Pair a Bluetooth Device.");
}
} else {
app.setStatus("Found " + devices.length + " device" + (devices.length === 1 ? "." : "s."));
}
},
connect: function(e) {
var onConnect = function() {
// subscribe for incoming data
bluetoothSerial.subscribe('\n', app.onData, app.onError);
resultDiv.innerHTML = "";
app.setStatus("Connected");
app.showDetailPage();
};
var deviceId = e.target.dataset.deviceId;
if (!deviceId) { // try the parent
deviceId = e.target.parentNode.dataset.deviceId;
}
bluetoothSerial.connect(deviceId, onConnect, app.onError);
},
onData: function(data) { // data received from Arduino
console.log(data);
resultDiv.innerHTML = resultDiv.innerHTML + "Received: " + data + "<br/>";
resultDiv.scrollTop = resultDiv.scrollHeight;
},
sendData: function(event) { // send data to Arduino
var success = function() {
console.log("success");
resultDiv.innerHTML = resultDiv.innerHTML + "Sent: " + messageInput.value + "<br/>";
resultDiv.scrollTop = resultDiv.scrollHeight;
};
var failure = function() {
alert("Failed writing data to Bluetooth peripheral");
};
var data = messageInput.value;
bluetoothSerial.write(data, success, failure);
},
disconnect: function(event) {
bluetoothSerial.disconnect(app.showMainPage, app.onError);
},
showMainPage: function() {
mainPage.style.display = "";
detailPage.style.display = "none";
},
showDetailPage: function() {
mainPage.style.display = "none";
detailPage.style.display = "";
},
setStatus: function(message) {
console.log(message);
window.clearTimeout(app.statusTimeout);
statusDiv.innerHTML = message;
statusDiv.className = 'fadein';
// automatically clear the status with a timer
app.statusTimeout = setTimeout(function () {
statusDiv.className = 'fadeout';
}, 5000);
},
onError: function(reason) {
alert("ERROR: " + reason); // real apps should use notification.alert
}
};
After installing the plugin with Monaca IDE and doing the custom Android build, I was able to get it to work using the following code:
ons.ready(function(){
bluetoothSerial.isConnected(
function() {
alert("Bluetooth is connected");
},
function() {
alert("Bluetooth is not connected");
}
);
});
The big thing to note, is you need to check for ons.ready, then access your variable.

How to lazy load Firebase data using ionic infinite scroll

I have a set of data within a users profile which holds the UID of all the posts they have liked. I am using a data snapshot to show these images within the users profile, but I would like to load it lazy or use ionic infinite scroll to load more items as the user scrolls.
I tried by making two duplicate snap shots. First one intialises the feed upon the page loading, and then I wanted the second one to load by using ionic infinite scroller.
But it doesn't work.
Does anyone know of a better way to do this?
service.js
getWardrobeFeed: function(){
var defered = $q.defer();
var user = User.getLoggedInUser();
var wardrobeKeyRef = new Firebase(FIREBASE_URL + '/userProfile/' + user.uid + '/wardrobe');
wardrobeKeyRef.orderByKey().limitToLast(2).on('child_added', function (snap) {
var imageId = snap.key();
userImageRef.child(imageId).on('value', function (snap) {
$timeout(function () {
if (snap.val() === null) {
delete userWardrobeFeed[imageId];
} else {
userWardrobeFeed[imageId] = snap.val();
}
});
});
});
wardrobeKeyRef.orderByKey().limitToLast(2).on('child_removed', function (snap) {
var imageId = snap.key();
$timeout(function () {
delete userWardrobeFeed[imageId];
});
});
defered.resolve(userWardrobeFeed);
return defered.promise;
},
getWardrobeItems: function(){
var defered = $q.defer();
var user = User.getLoggedInUser();
var wardrobeKeyRef = new Firebase(FIREBASE_URL + '/userProfile/' + user.uid + '/wardrobe');
wardrobeKeyRef.orderByKey().limitToFirst(2).on('child_added', function (snap) {
var imageId = snap.key();
userImageRef.child(imageId).on('value', function (snap) {
$timeout(function () {
if (snap.val() === null) {
delete userWardrobe[imageId];
} else {
userWardrobe[imageId] = snap.val();
}
});
});
});
wardrobeKeyRef.orderByKey().on('child_removed', function (snap) {
var imageId = snap.key();
$timeout(function () {
delete userWardrobe[imageId];
});
});
defered.resolve(userWardrobe);
return defered.promise;
},
controller.js
$scope.userWardrobe = [];
Service.getWardrobeFeed().then(function(items){
$scope.userWardrobe = items;
});
$scope.loadMore = function() {
Service.getWardrobeItems().then(function(items){
$scope.userWardrobe = $scope.userWardrobe.concat(items);
$scope.$broadcast('scroll.infiniteScrollComplete');
});
};

angular, try to display object in ng-repeat fails

i'm writing an mobile application in javascript with angularJS and ionicframework (last beta v.11), i create dinamically an object and want to display all objects inside in a ng-repeat. Why nr-repeat don't display anything?
This is screen from my object:
I use this code for put values in scope:
$scope.distanceSuppliers = myCar;
And this is the code in html:
<ion-item ng-repeat="(id, supplier) in distanceSuppliers">
<div class="items item-button-right" ng-click="openDetails(id)">
{{supplier.name}}<br />
{{supplier.address}}<br />
</div>
</ion-item>
This is my complete code for JS:
.controller('suppliers', function($scope, cw_db, $ionicPopup, $ionicActionSheet, appdelegate, $rootScope, $firebase, $location, $ionicLoading, cw_position) {
$ionicLoading.show({
template: 'Updating data..'
});
var geocoder;
var tot = 0;
var done = 0;
geocoder = new google.maps.Geocoder();
cw_db.getData(cw_db.getSuppliers(), "", function(suppliers) {
cw_position.getPosition(function (error, position) {
suppliers.on('value', function(supp) {
$scope.distanceSuppliers = {};
tot = 0;
done = 0;
supp.forEach(function(childSnapshot) {
tot++;
var childData = childSnapshot.val();
if (childData.address) {
calculateDistance(childData, position.coords.latitude, position.coords.longitude);
}
});
});
$ionicLoading.hide();
});
});
function calculateDistance(childData, usrLat, usrLon) {
var service = new google.maps.DistanceMatrixService();
service.getDistanceMatrix(
{
origins: [new google.maps.LatLng(usrLat, usrLon)],
destinations: [childData.address],
travelMode: google.maps.TravelMode.DRIVING,
unitSystem: google.maps.UnitSystem.METRIC,
avoidHighways: false,
avoidTolls: false
}, function(response, status) {
if (status != google.maps.DistanceMatrixStatus.OK) {
alert('Error was: ' + status);
} else {
done++;
var results = response.rows[0].elements;
childData.distance = results[0].distance.value;
$scope.distanceSuppliers.push(childData);
if (done == tot) {
console.log($scope.distanceSuppliers);
}
}
});
}
$scope.openDetails = function(index) {
//appdelegate.setCallId(index);
//$location.path("/app/supplierDetails");
}
})
what's wrong?
Not sure, but I believe you have a data binding update problem.
Try using the $timeout to force the render:
w_position.getPosition(function (error, position) {
$timeout(function() {
suppliers.on('value', function(supp) {
$scope.distanceSuppliers = {};
tot = 0;
done = 0;
supp.forEach(function(childSnapshot) {
tot++;
var childData = childSnapshot.val();
if (childData.address) {
calculateDistance(childData, position.coords.latitude, position.coords.longitude);
}
});
});
$ionicLoading.hide();
});
});
And don't forget to add the $timeout parameter to the controller:
.controller('suppliers', function($scope, ...<other parameters here>..., $timeout) {
I found the problem! Fix using $scope.$apply();
The problem was that i was writing in a different $scope using this code:
cw_position.getPosition(function (error, position) {
suppliers.on('value', function(supp) {
tot = 0;
done = 0;
supp.forEach(function(childSnapshot) {
tot++;
var childData = childSnapshot.val();
if (childData.address) {
calculateDistance(childData, position.coords.latitude, position.coords.longitude);
}
});
});
$ionicLoading.hide();
});
where cw_position.getPosition call a js with this code:
angular.module('cw_position', [])
.service('cw_position', function() {
this.getPosition = function(callback) {
navigator.geolocation.getCurrentPosition(
function (position) {
return callback(null, position);
},
function(error) {
return callback(error, null);
}
);
}
// Built google maps map option
//
this.getGoogleMapOptions = function (lat, lon, zoom, type) {
if (type == null) {
type = google.maps.MapTypeId.ROADMAP;
}
var mapOptions = {
center: new google.maps.LatLng(lat, lon),
zoom: zoom,
mapTypeId: type
};
return mapOptions;
}
});
navigator.geolocation.getCurrentPosition causes the 'problem'
Thx to all for your help

Uploading images to Firebase with AngularJS

I have a service that handles "episodes": creating, deleting and updating them. It looks like this:
app.service('Episode', ['$firebase', 'FIREBASE_URL', function($firebase, FIREBASE_URL) {
var ref = new Firebase(FIREBASE_URL);
var episodes = $firebase(ref);
return {
all: episodes,
create: function(episode) {
location.reload();
//Add to firebase db
return episodes.$add(episode);
},
delete: function(episodeId) {
location.reload();
return episodes.$remove(episodeId);
},
update: function(episode) {
location.reload();
return episodes.$save(episode);
}
};
}]);
Inside my controller:
app.controller('AdminCtrl', ['$scope', 'Episode', function ($scope, Episode) {
$scope.episodes = Episode.all;
$scope.createEpisode = function(){
Episode.create($scope.episode).then(function(data){
$scope.episode.name = '';
$scope.episode.title = '';
$scope.episode.description = '';
$scope.episode.time = '';
$scope.episode.img = '';
});
};
$scope.deleteEpisode = function(episodeId){
if(confirm('Are you sure you want to delete this episode?') === true) {
Episode.delete(episodeId).then(function(data){
console.log('Episode successfully deleted!');
});
}
};
$scope.updateEpisode = function(episode) {
Episode.update($scope.episode).then(function(data) {
console.log('Episode successfully updated.');
});
};
The only example of uploading images to Firebase from AngularJS I've seen online is this: https://github.com/firebase/firepano
How am I able to incorporate this into an object based addition/update instead of finding it's index/link?

How to sync update value in angularjs

I'm pretty new to angular so here goes. I have an upload avatar and a thumbnail to show the image of the uploaded avatar. My problem is how to update the thumbnail of the newly uploaded avatar. Coz as of right now I would have to refresh the page first before I can see the changes.
Here's some code in my controller:
//get avatar
$scope.userAvatar = function() {
Api.getAvatar($scope.security.currentUser.email)
.then(function(result) {
//success
$scope.avatarImage = result.config.url;
}, function(result) {
//errors
console.log(result);
});
}
//validate avatar then upload avatar
$scope.validateAvatar = function(files) {
var fd = new FormData();
if(files.length > 0)
{
$scope.filesize = files[0].size;
$scope.filemaxsize = 25 * 1024;
$scope.$apply();
//Take the first selected file
fd.append("avatarImage", files[0]);
$scope.uploadAvatar = function() {
Api.uploadAvatar($scope.security.currentUser.email, fd)
.then(function(result) {
console.log(result.data);
//$scope.avatarImage = result.config.url; ////doesn't update the $scope.avatarImage
//$scope.userAvatar(); //doesn't update the $scope.avatarImage
Api.getAvatar($scope.security.currentUser.email)
.then(function(result) {
//success
console.log('uploaded Image');
$scope.avatarImage = result.config.url;
});
}, function(result) {
console.log(result);
})
};
}
};
and on my partials
<div id="show-avatar" class="col-sm-3 col-sm-offset-3">
<img id="avatarbox" ng-src="{{avatarImage}}" >
</div>
On my controller code above I validate the chosen image first if its not more than 25kb I show the upload button. I included the code on how I upload my avatar maybe it may help on my problem.
okay, i solved my own problem with the below code.
$scope.validateAvatar = function(files) {
var fd = new FormData();
if(files.length > 0)
{
$scope.filesize = files[0].size;
$scope.filemaxsize = 25 * 1024;
$scope.avatarImage = '';
//Take the first selected file
fd.append("avatarImage", files[0]);
$scope.uploadAvatar = function() {
Api.uploadAvatar($scope.security.currentUser.email, fd)
.then(function(result) {
$scope.userAvatar();
}, function(result) {
console.log(result);
})
};
$scope.$apply();
}
};
I reset the $scope.avatarImage then assign it again using the $scope.userAvatar();.

Resources