I've been working with Dexie JS to manage an IndexDB data store and now want to sync the data store with a remote database. The issue that I am having is that I want to nest all relational/child records under their respective parent records in a collection and then send the whole group/list off to the remote server, using some AJAX.
In practice, what I am seeing is that the child records are not present at the time they are pushed to the remote server. However, I do see them in console.log(). I know that this is because console.log() gets the actual data at a much later time than when the data is pushed remotely. I also know that this is a common issue with promise chains, but am somehow unable to solve it.
Here's what I have so far.
function PushRemote(items, modelName) {
console.log(items);
$.ajax({
type: 'POST',
url: '/' + modelName + '/AsyncSave',
contentType: 'application/json; charset=utf-8',
dataType: 'json',
data: JSON.stringify(DataTransferItemList),
success: function (response) {
iDB.open().then(function () {
return iDB.table(modelName).update(response, { isNotSynced: "false" });
});
},
error: function (response) {
console.error(response);
}
});
}
function PushTableToRemote(modelName) {
iDB.transaction('rw',
iDB.Comments,
iDB.CommentRatings,
iDB.Posts,
iDB.PostRatings,
iDB.Reviews,
() => {
iDB.table(modelName).where({ isNotSynced: "true" }).toArray(items => {
items.forEach(item => {
if (modelName == 'Comments') {
iDB.CommentRatings.where({ CommentId: item.CommentId }).toArray(c => item.CommentRatings = c);
}
if (modelName == 'Posts') {
iDB.PostRatings.where({ PostId: item.PostId }).toArray(p => item.PostRatings = p);
}
})
return items;
})
.then(items => {
if (items && items.length > 0) {
PushRemote(item, modelName);
}
});
});
}
pushTableToRemote('Comments');
Alright, so it turns out if you can't go through the promise chain, then you go around the promise chain. ;)
Ended up implementing spawn() and yield with a generator function, instead of a promise chain (https://dexie.org/docs/Simplify-with-yield.html). This was way more straight forward, for me at least. And worked like a charm. Your mileage may vary.
function PushTableToRemote(modelName) {
spawn(function* () {
var items = yield iDB.table(modelName).where({ isNotSynced: "true" }).toArray();
if (items && items.length > 0) {
if (modelName == 'Comments') {
for (var i = 0; i < items.length; i++) {
var CommentRatings = yield iDB.CommentRatings.where({ CommentId: items[i].CommentId }).toArray();
items[i].CommentRatings = CommentRatings;
}
}
if (modelName == 'Posts') {
for (var i = 0; i < items.length; i++) {
var PostRatings = yield iDB.PostRatings.where({ PostId: items[i].PostId }).toArray();
items[i].PostRatings = PostRatings;
}
}
PushRemote(items, modelName);
}
});
}
Related
I'm looping through a nested object to get a some data. This is working, but I can't seem to return the data and use it elsewhere.
I've tried putting the loop in a promise and couldn't get anywhere either. What am I doing wrong?
data: any = {
'1234': {
url: 'https://example1.com/',
path: 'uploads',
link: 'https://example1.com/uploads',
},
'5678': {
url: 'https://example2.com/',
path: 'uploads',
link: 'https://example2.com/uploads',
}
}
onSubmit(formData) {
this.formdata = formData;
Object.keys(this.data).forEach(key => {
if (key == this.formdata.pin) {
const url = this.data[key].url;
// have also tried this.url to no avail
}
});
// says undefined
console.log(url);
// set up headers, etc...
// I need to use here
this.http.post(url, body, head)
...
}
onSubmit(formData) {
this.formdata = formData;
let url; // Define here so that its accessible
Object.keys(this.data).forEach(key => {
if (key === this.formdata.pin) {
url = this.data[key].url;
// have also tried this.url to no avail
}
});
// Now url is in scope
console.log(url);
...
}
Switching your forEach to a map can simplify this; map return values, whereas forEach does not.
Old:
Object.keys(this.data).forEach(key => {
if (key == this.formdata.pin) {
const url = this.data[key].url;
}
});
// says undefined
console.log(url);
New: (I've also added a === in here based on the comment below)
const urls = Object.keys(this.data).map(key => {
if (key === this.formdata.pin) {
return this.data[key].url;
// have also tried this.url to no avail
}
});
console.log(urls);
map docs and forEach docs
How to ensure, in JavaScript (jquery) that some actions are performed one after other, in an order.
Say, I need to load schools collection BEFORE loading teachers, in order to assing the myTeacher.SchoolName = schools[myTeacher.SchoolId].name;
The pseudo code bellow:
const studentsUrl='api/students', teachersUrl='api/teachers', schoolsUrl='api/schools';
let students = null, teachers = null, schools = null;
$(document).ready(function () {
getSchools();
getTeachers();
getStudents();
});
function getSchools() {
$.get(schoolsUrl, function (data) {
window.schools = data;
});
}
function getTeachers() {
$.get(teachersUrl, function (data) {
window.teachers = data;
// >>> SHOULD BE SURE, SCHOOLS already loaded!!!
$.each(teachers, function (key, item) {
item.school = schools[item.schoolId].name;
});
});
}
function getStudents() {
$.get(studentsUrl, function (data) {
window.students = data;
// >>> SHOULD BE SURE, TEACEHRS already loaded!!!
$.each(students, function (key, item) {
item.teacher = teachers[item.teacherId].name;
});
});
}
PS.
Is there another way to assure order but the encapsulation of one function at the end of another?
As others already suggested you can chain requests.
I made few changes to your code.
Added Strict Mode it helps to prevent bugs
The code wrapped in IFFE in order to prevent global pollution
If all apis belong to the same server you can process all this data on server side
and return one filled json.
in this way your server will do a little extra work on constructing this json but in other hand you will make only one ajax request instead of 3.
This will work faster and you can cache this json for some time
Code for the first solution
(function () {
'use strict';
const studentsUrl = 'api/students';
const teachersUrl = 'api/teachers';
const schoolsUrl = 'api/schools';
let students = null;
let teachers = null;
let schools = null;
var scoolData = {
schools: null,
teachers: null,
students: null
};
$(document).ready(function () {
getSchools().then(function (schools) {
scoolData.schools = schools;
getTeachers().then(function (teachers) {
scoolData.teachers = teachers;
$.each(scoolData.teachers, function (key, item) {
item.school = scoolData.schools[item.schoolId].name;
});
});
});
});
function getSchools() {
return $.get(schoolsUrl);
}
function getTeachers() {
return $.get(teachersUrl,
function (result) {
scoolData.teachers = result;
// >>> SHOULD BE SURE, SCHOOLS already loaded!!!
$.each(teachers, function (key, item) {
item.school = scoolData.schools[item.schoolId].name;
});
});
}
})();
Since you only need all the results available and each request does not depend on the previous you can use jQuery.when
let students = null;
let teachers = null;
let schools = null;
$(document).ready(function() {
$.when(
getSchools(),
getTeachers()
).done(function(shoolResults, teacherResults) {
window.schools = shoolResults;
window.teachers = teacherResults;
handleTeachers();
getStudents();
});
function getSchools() {
return $.ajax({
type: 'GET',
url: schoolsUrl
});
}
function getTeachers() {
return $.ajax({
type: 'GET',
url: teachersUrl
});
}
function handleTeachers() {
$.each(teachers, function (key, item) {
item.school = schools[item.schoolId].name;
});
}
});
If you want them in order (though I'm not sure I understand why, since you retrieve all schools/teachers/students anyway), you can simply do this.
Note: get* functions are dummies in the following sample. Instead, just return the result of $.get calls from them:
function getSchools() {
return Promise.resolve({1: {name: 'school1'}});
}
function getTeachers() {
return Promise.resolve({1: {name: 'teacher1', schoolId: 1}});
}
function getStudents() {
return Promise.resolve({1: {name: 'student1', teacherId: 1}});
}
(async () => {
const schools = await getSchools();
const teachers = await getTeachers();
const students = await getStudents();
// Alternative for the $.each code
Object.values(teachers).forEach(teacher => teacher.school = schools[teacher.schoolId].name);
Object.values(students).forEach(student => student.teacher = teachers[student.teacherId].name);
console.log(schools, teachers, students);
})();
Another note: this is ES8 code, I'll post a non async/await version if you need to support older browsers and can't use a transpiler like Babel.
Non ES8-dependent code:
function getSchools() {
return Promise.resolve({1: {name: 'school1'}});
}
function getTeachers() {
return Promise.resolve({1: {name: 'teacher1', schoolId: 1}});
}
function getStudents() {
return Promise.resolve({1: {name: 'student1', teacherId: 1}});
}
let schools = null, teachers = null, students = null;
getSchools().then(_schools => {
schools = _schools;
return getTeachers();
}).then(_teachers => {
teachers = _teachers;
return getStudents();
}).then(_students => {
students = _students;
for (var _ in teachers) {
teachers[_].school = schools[teachers[_].schoolId].name;
}
for (var _ in students) {
students[_].teacher = teachers[students[_].teacherId].name
}
console.log(schools, teachers, students);
});
Call getTeachers(); when getSchools(); return success or complete, success preferred since complete runs if there's an error..
I think you are looking for this one.
getSchools().done(function(data){
var someId = data.findThatId;
getTeachers(someId);
});
You will need to return data from ajax call to get data in done.
You may load them asynchronously but you have to wait until both calls are finished.
To achieve this, add return before your ajax calls and combine the results in your ready function (not in the success handler of the teachers call):
let schoolsPromise = getSchools();
let teachersPromise = getTeachers();
$.when(schoolsPromise, teachersPromise)
.then((schools, teachers) => {
$.each(teachers, (key, item) => {
item.school = schools[item.schoolId].name;
});
});
I was trying to run AngularJS forEach loop inside a $http request where as promise is not waiting to complete the loop and before that its returning.
Please find my code below:
return $http({
method: 'GET',
url: $rootScope.baseUrl + 'test/test/test/test',
headers: {
"token": token
}
})
.then(function(responce) {
var dashboardCheck = function() {
var defer = $q.defer();
angular.forEach($rootScope.getDashboardDetails.dashboardList, function(value, key) {
if (value.name == "Dashboard" && value.contentDashboard == true) {
//return value;
defer.resolve(value);
}
})
return defer.promise;
}
var availableDashboard = function() {
return $rootScope.getDashboardDetails.dashboardList[0];
}
var defaultDash = function(value) {
method goes here
}
if (dashboardCheck()) {
defaultDash(dashboardCheck());
} else {
defaultDash(availableDashboard())
}
})
You seem to make everything way more complicated than it should be, you want to find a certain dashboard and if not find just return the first.
There is no async code after making the request and you don't do anything with the request so I'm not sure why you're making it in the first place.
The much simpler version of what you're trying to do would be this:
return $http({
method: 'GET',
url: $rootScope.baseUrl + 'test/test/test/test',
headers: {
"token": token
}
})
.then(function(responce) {
var scopeDashboard = $rootScope.getDashboardDetails.dashboardList;
var dashboard =
//not sure why need to return a defer here, no async code provided
scopeDashboard.filter(
dashboard=>
dashboard.name == "Dashboard" && dashboard.contentDashboard == true
)[0] || scopeDashboard[0];
// if scopeDashboard is an object try this
// scopeDashboard[
// Object.keys(scopeDashboard)
// .filter(
// key=>
// scopeDashboard[key].name == "Dashboard" &&
// scopeDashboard[key].contentDashboard == true
// )[0] || 0
// ];
return [dashboard,response];
})
.then(
([dashboard,response])=>{
//you now have the response and dashboard, what would you like to do with it?
}
)
.catch(
err=>console.error("something went wrong",err)
)
I have the following:
transformResult: function(response) {
if (response && response.buckets && response.buckets[0] && response.buckets[0].documents) {
return {
suggestions: $.map(response.buckets[0].documents, function(dataItem) {
return { value: dataItem._id, data: {
key: response.buckets[0].key,
url: dataItem.url
}
};
})
};
}
I'm using response.buckets[0] to ensure at least one bucket exists in the array. There can be 0 or 1+ buckets. The problem is, now the suggestions are just returning for the first bucket w [0] in response.buckets[0].documents
How can I get the suggestions to return for 0 or more $.map(response.buckets[0].documents?
Update
transformResult: function(response) {
var suggestions = {
suggestions: {}
};
if(!response || !response.buckets) {
return suggestions;
}
for(var i=0;i<response.buckets.length;i++) {
var bucket = response.buckets[i];
if(!!bucket.documents) {
suggestions.concat($.map(bucket.documents, function(item) {
return {
value: item._id,
data: {
key: bucket.key,
url: item.url
}
}
}));
};
}
return suggestions;
},
This is now erroring with: Uncaught TypeError: suggestions.concat is not a function
If you run a for loop on the buckets-array and inside the for run the map function on each element you should achieve what you are after.
var suggestions = [];
if(!response || !response.buckets) {
return { suggestions: suggestions };
}
for(var i=0;i<response.buckets.length;i++) {
var bucket = response.buckets[i];
if(!!bucket.documents) {
suggestions.concat($.map(bucket.documents, function(item) {
return {
value: item._id,
data: {
key: bucket.key,
url: item.url
}
};
}));
}
}
return { suggestions: suggestions };
If there are 0 buckets, the for-loop will not loop at all and the suggestions array will be of 0 length.
I'm not entirely sure wether I got your intention and data-structure right, but I think you're looking for this:
transformResult: function(response) {
//seems that $.map() doesn't handle null-values :(
//so I have to take care of this
var emptyArray = [];
return {
suggestions: $.map(response && response.buckets || emptyArray, function(bucket){
//jQuerys map-implementation is actually more like a fmap.
//so this doesn't return an Array of Arrays, but one flat Array instead
return $.map(bucket.documents || emptyArray, function(document){
return {
value: document._id,
data: {
key: bucket.key,
url: document.url
}
}
});
})
}
}
I have a list of models I want to search through and pull the url for the correct one. I won't always have the full key, and never the full value, but will always have at least a unique part of it.
Right now the code is just in test mode, with a set number that matches a key, print a success or failure.
The console keeps telling me that models[i].indexOf isn't a function. I know it's an object, but when I do a toString on it, I get "object Object". What am I not understanding?
I'm happy with a solution that is either vanilla JavaScript or uses jQuery.
The code:
if ($('.mobile_tutorial').length) {
var device = /*$device.model*/ "NTZEZ717VLU", model_code = device.substr(2).substr(0,device.length-3);
$.ajax({
url: "/scripts/phone_models.json",
type: 'get',
dataType: 'json',
success: function (data) {
var models = data.Manufacturer;
for (var i = models.length - 1; i >= 0; i--) {
if (models[i].indexOf(model_code) > -1) {
console.log(models[i])
} else {
console.log('no match')
}
}
}
});
}
The JSON (partial):
{
"Manufacturer": [{
"ZEZ955L": "http://x.com/mobile/home.seam?custId=ZEZ955L"
}, {
"ZEZ990G": "http://x.com/mobile/home.seam?custId=ZEZ990G"
}, {
"ZEZ828TL": "http://x.com/mobile/home.seam?custId=ZEZ828TL"
}, {
"ZEZ716BL": "http://x.com/mobile/home.seam?custId=ZEZ716BL"
}, {
"ZEZ717VL": "http://x.com/mobile/home.seam?custId=ZEZ717VL"
}, {
"ZEZ962BL": "http://x.com/mobile/home.seam?custId=ZEZ962BL"
}, {
"ZEZ963VL": "http://x.com/mobile/home.seam?custId=ZEZ963VL"
}]
}
models[i] is not a string so you are getting error. If you want to check key then use .each() function on models[i]. In that each loop compare the key using indexOf function.
if ($('.mobile_tutorial').length) {
var device = /*$device.model*/ "NTZEZ717VLU", model_code = device.substr(2).substr(0,device.length-3);
$.ajax({
url: "/scripts/phone_models.json",
type: 'get',
dataType: 'json',
success: function (data) {
var models = data.Manufacturer;
for (var i = models.length - 1; i >= 0; i--) {
$.each(models[i], function( key, value ) {
if (key.indexOf(model_code) > -1) {
console.log(models[i])
} else {
console.log('no match')
}
}
}});
});
}
You would need to grab the value of the key changing models[i].indexOf(model_code) to Object.keys(models[i])[0].indexOf(partial_model_code). Here's it in action:
var partial_model_code = '3VL'
function ajax(data) {
var models = data.Manufacturer;
for (var i = models.length - 1; i >= 0; i--) {
// grab the keys in the object
// since there will only be one object grab the first one
// check if the key partially matches
if (Object.keys(models[i])[0].indexOf(partial_model_code) > -1) {
console.log(models[i])
} else {
console.log('no match')
}
}
}
var data = JSON.parse(`{
"Manufacturer": [{
"ZEZ955L": "http://x.com/mobile/home.seam?custId=ZEZ955L"
}, {
"ZEZ990G": "http://x.com/mobile/home.seam?custId=ZEZ990G"
}, {
"ZEZ828TL": "http://x.com/mobile/home.seam?custId=ZEZ828TL"
}, {
"ZEZ716BL": "http://x.com/mobile/home.seam?custId=ZEZ716BL"
}, {
"ZEZ717VL": "http://x.com/mobile/home.seam?custId=ZEZ717VL"
}, {
"ZEZ962BL": "http://x.com/mobile/home.seam?custId=ZEZ962BL"
}, {
"ZEZ963VL": "http://x.com/mobile/home.seam?custId=ZEZ963VL"
}]
}`)
ajax(data)
I hope that helps!