I'm using Ionic with a tabbed interface. Tab 1 has some basic data, Tab 2 has some more detailed data.
I'm using a db insert like this:
$scope.insert = function(date_in, basic_data1, basic_date2, detail1, detail2, detail3) {
db.transaction(function(tx){
tx.executeSql("INSERT OR REPLACE INTO 'stats' (date, age, gender, val1, val2, val3) VALUES (?,?,?,?,?,?)", [date_in, basic_data1, basic_date2, detail1, detail2, detail3);
},function(e){
$log.log("ERROR: " + e.message);
});
}
This works to insert the data from the tab you are on. Here is the ng-click:
ng-click="insert(currentDate, userage, usergender, user_detail1, user_detail2, user_detail3)"
And the form elements:
<label class="item item-input">
<i class="icon ion-clock"></i>
<span class="input-label smalltext"> Age</span>
<input type="number" ng-model="userage">
</label>
I'm looking at the webSQL DB in the Chrome dev console. The insert works, and it overwrites like it should. But it only saves the portion of the DB from the tab you are on.
E.g. If I save from Tab 1 the date, age and gender save. If I save from Tab 2 the date and detail1, 2 and 3 save. This overwrites the other part of the data with "undefined", even thought going back to the other tab shows that the data is still in the fields.
The reason the date works is there is a date picker on both tabs bound to the same model - currentDate.
How can I get the data from both tabs to be handled by my function every time? I never had this issue using jQM...
This is the code in the controller:
.controller('SettingsCtrl', function($scope, $rootScope, $localstorage, $log, $ionicPopup, $ionicActionSheet, $timeout, $cordovaDatePicker) {
$scope.insert = function(date_in, weight_in, heartrate_in, sysbp_in, diabp_in, neck_in, chest_in, biceps_in, waist_in, forearms_in, thighs_in, calves_in) {
console.log(date_in, weight_in, heartrate_in, sysbp_in, diabp_in, neck_in, chest_in, biceps_in, waist_in, forearms_in, thighs_in, calves_in);
db.transaction(function(tx){
tx.executeSql("INSERT OR REPLACE INTO 'stats' (date, weight, heartrate, sysbp, diabp, neck, chest, biceps, waist, forearms, thighs, calves) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)", [date_in, weight_in, heartrate_in, sysbp_in, diabp_in, neck_in, chest_in, biceps_in, waist_in, forearms_in, thighs_in, calves_in]);
console.log("The inserted weight is", weight_in);
console.log("The inserted date is", date_in);
console.log("The inserted neck is", neck_in);
console.log("The inserted chest is", chest_in);
},function(e){
$log.log("ERROR: " + e.message);
});
}
$scope.data = {};
$scope.select = function(date) {
db.transaction(function(tx){
tx.executeSql("SELECT date, weight, heartrate, sysbp, diabp, chest, biceps, waist, forearms, thighs, calves FROM stats where date =?", [date, weight, heartrate, sysbp, diabp, chest, biceps, waist, forearms, thighs, calves]);
console.log("The inserted weight is", y);
},function(e){
$log.log("ERROR: " + e.message);
});
}
$scope.usergender = $localstorage.get('storeGender');
$scope.userage = parseInt($localstorage.get('storeAge'));
$scope.userheight = parseInt($localstorage.get('storeHeight'));
$scope.userunits = $localstorage.get('storeUnits');
$scope.userdate = $localstorage.get('storeDate');
$scope.useranalytics = $localstorage.get('storeAnalytics');
$scope.changeVal = function(storeName, val){
$localstorage.set(storeName,val);
console.log(storeName + " is now " + val);
}
// Date converter UK or US //
Date.prototype.dateConvertUK = function() {
function two(n) {
return (n < 10 ? '0' : '') + n;
}
return two(this.getDate()) + '/' + two(this.getMonth() + 1) + '/' + this.getFullYear();
};
Date.prototype.dateConvertUS = function() {
function two(n) {
return (n < 10 ? '0' : '') + n;
}
return two(this.getMonth() + 1) + '/' + two(this.getDate()) + '/' + this.getFullYear();
};
var dateTypeUK = true;
// Set todays date
var d = new Date();
console.log("New Date is", d);
if(dateTypeUK===true){
today = d.dateConvertUK();
console.log("Today (coverted UK) is", today);
}else{
today = d.dateConvertUS();
console.log("Today (coverted US) is", today);
}
$scope.currentDate = today;
//Convert date to string
$scope.dateToString = function(date){
return date.toString();
}
var options = {
date: new Date(),
mode: 'date', // or 'time'
minDate: new Date() - 10000,
allowOldDates: true,
allowFutureDates: false,
doneButtonLabel: 'DONE',
doneButtonColor: '#F2F3F4',
cancelButtonLabel: 'CANCEL',
cancelButtonColor: '#000000'
};
$scope.popupDate = function(){
console.log("in the popup");
$cordovaDatePicker.show(options).then(function(date){
$scope.currentDate = date;
console.log("processed date is", date);
});
}
});
And the code in the html is:
<ion-view view-title="Vitals">
<ion-nav-buttons side="secondary">
<button class="button smalltext" ng-click="popupDate()">
<i class="icon ion-calendar"></i> {{currentDate}}
</button>
</ion-nav-buttons>
<ion-content class="padding" has-bouncing="false">
<div class="list">
<label class="item item-input">
<i class="icon ion-podium"></i>
<span class="input-label smalltext">Weight (<span id="weightunit">kg</span>)</span>
<input type="number" ng-model="data.userweight" placeholder="0">kg
</label>
<div class="item item-button-right smalltext">
Your BMI is: high
<button class="button button-stable button-clear">
<i class="icon ion-help-circled"></i>
</button>
</div>
</div>
<div class="list">
<label class="item item-input">
<i class="icon ion-heart"></i>
<span class="input-label smalltext">Heart Rate (bpm)</span>
<input type="number" ng-model="data.userheartrate" placeholder="0">
</label>
<div class="item item-button-right smalltext">
Your Heart Rate is:
<button class="button button-stable button-clear">
<i class="icon ion-help-circled"></i>
</button>
</div>
</div>
<div class="list">
<div class="item item-divider smalltext">
<i class="icon ion-stats-bars"></i> Blood Pressure
</div>
<div class="row">
<div class="col noPadding">
<label class="item item-input">
<i class="icon ion-arrow-up-b"></i>
<span class="input-label smalltext">Systolic</span>
<input type="number" placeholder="0" ng-model="data.usersysbp">
</label>
</div>
<div class="col noPadding">
<label class="item item-input">
<i class="icon ion-arrow-down-b"></i>
<span class="input-label smalltext">Diastolic</span>
<input type="number" placeholder="0" ng-model="data.userdiabp">
</label>
</div>
</div>
<div class="item item-button-right smalltext">
Your Blood Pressure is:
<button class="button button-stable button-clear">
<i class="icon ion-help-circled"></i>
</button>
</div>
</div>
<button class="button button-mygreen button-full" ng-click="insert(currentDate, data.userweight, data.userheartrate, data.usersysbp, data.userdiabp, data.userneck, data.userchest, data.userbiceps, data.userwaist, data.userforearms, data.userthighs, data.usercalves)">
<i class="icon ion-android-archive"></i> Save
</button>
</ion-content>
</ion-view>
Since each tab is included in an ng-if, each tab creates it's own scope. So each tab only has on it's scope the properties that it has input tags for. When you call the insert method (which I assume is created on the parent scope of the tabs, only the properties that are on the tab will be defined.
To fix, create an object on the same scope as your insert function that holds the data, and modify your inputs on your tabs to reference that data:
So, the controller for the parent of your tabs would be:
.controller('tabmanager', function($scope) {
$scope.insert = function(date_in, basic_data1, basic_date2, detail1, detail2, detail3) {
db.transaction(function(tx){
tx.executeSql("INSERT OR REPLACE INTO 'stats' (date, age, gender, val1, val2, val3) VALUES (?,?,?,?,?,?)", [date_in, basic_data1, basic_date2, detail1, detail2, detail3);
},function(e){
$log.log("ERROR: " + e.message);
});
}
$scope.data = {}; //an empty object to hold your model data in this scope
})
Your markup changes to:
<label class="item item-input">
<i class="icon ion-clock"></i>
<span class="input-label smalltext"> Age</span>
<input type="number" ng-model="data.userage">
</label>
....
....
<button ng-click="insert(data.currentDate, data.userage, data.usergender, data.user_detail1, data.user_detail2, data.user_detail3)">Save</button>
I think basically the issue is that each tab has its own data model, and the data that is defined in another tab is not available to this tab, so when you use gender
in tab 2 it is undefined.
I'm pretty new to angular and its conventions but is there anything stopping you from also passing the $rootScope
to the controller, like this:
.controller('TabxCtrl', function ($scope, $rootScope) {
and having both tab controllers share this $rootScope
?
Then you can just use $rootScope.data
to define values that are set in either tab, so that when you do the database INSERT none of the values are undefined.
Not sure how the rest of your application is structured, but you could leverage the
$rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams){ ... })
event to make sure you save the latest field values to the $rootScope.data
variable before you navigate away from the tab. Or you can just update the $rootScope.data
variable whenever any field value is changed or updated.
I have used jQM in the past so I understand how you feel. I'm in the same boat wondering if the advantages outweigh new learning curve lol.
Good luck!