Create custom event, like ionicPlatform.ready() or document.ready() - javascript

I have an Ionic app in which I use a database. I want to fill this database with the contents of a file.
This part I got working. I want to create a DB.ready() event, much like the $ionicPlatform.ready() or document.ready(), as I need to wait until the database is loaded until I query it.
I am fairly new to Ionic, and to the concept of Promises, so it might be something simple.
I've gotten it to work in Android, but iOS keeps returning an error in the query with "someTablename does not exist". I've placed multiple console.log(), and according to those everything is fine.
Could anyone tell me which part I did incorrect, or another method if that is more common in this situation (again, I'm new, so don't know what is common)?
I expected to get "query" logged every query, but it doesn't do that, is that significant?
// L35_DB - Databaseclass for apps
.factory('L35_DB', ['$ionicPlatform','$cordovaFile','$cordovaSQLite', function($ionicPlatform, $cordovaFile,$cordovaSQLite) {
var L35_DB = {db_start : false};
//-------------------------------------
DB_READY = new Promise(function(resolve,reject){
console.log("query");
if( L35_DB.db_start ){console.log("b"); resolve("Stuff worked!"); }
else{
var filename='fileWithDB.db';
$ionicPlatform.ready(function() {
if( window.cordova ){
return window.plugins.sqlDB.copy(filename, 0,
function(info){ loadDB(filename).then( function(){ console.log("First load", info); resolve("DB loaded?"); }) },
function(info){ loadDB(filename).then( function(){ console.log("Other loads", info); resolve("DB loaded?"); }) }
);
}
});
}
});
//-------------------------------------
// Load the file
function loadDB(filename){
var loading = new Promise(function(resolve,reject){
db = window.sqlitePlugin.openDatabase(
{name: filename, location: 'default'},
function(){
console.log("loadDB success"); // <--- fired
L35_DB.db_start = true; // true, so next call we don't do all this
resolve("DB ready loading");
},
function(err){ reject(err);}
);
});
return loading;
}
//-------------------------------------
// Query -
var _query = function(query,values){
if( !L35_DB.db_start ){
console.error("DB not init");
return false;
}
else if( window.cordova ){
values = values || [];
var actualQuery = new Promise(function(resolve,reject){
db.executeSql(query, values, resolve, reject);
})
return actualQuery;
}
}
//-------------------------------------
return {
query : _query
};
}])
Throughout my app I do:
DB_READY.then(function () {
L35_DB.query("SELECT * FROM systems").then(function (result) {
// Something something something darkside
})
})

After a lot of testing and digging, turns out window.plugins.sqlDB.copy() was the culprit.
The 2nd value, location, can be changed. It defaults to 0, but for iOS it has to be 2. After this change, everything work exactly as expected.
This function should preload the database for Android and iOS, assumed a bit too early it actually did.

Related

array of callbacks as Promise?

I am working through some old legacy code dealing with network requests using RPC/YUI library. It essentially creates tags to handle network requests. There are no promises for these.Also, because of IE11 support, we cannot use the native Promise object. Our build process does not utilize any NPM dependencies, so we cannot use any babel related polyfills.
There is a bug I am working on to fix that the argument ignoreError gets overwritten each time another function calls the same function....obviously! We have multiple functions calling this network request function library. Sometimes we want to ignore an error, sometimes we do not.
What is the ideal way to store the multiple requests made and their respective error callbacks so the appropriate item is called?
example:
var rpcUrl,
rpcRetries,
rpcIgnoreError;
// main function that sets some globals:
rpc: function(url, retries, ignoreError) {
rpcUrl = url;
rpcRetries = retries;
rpcIgnoreError = ignoreError;
this.doRpc();
},
// calls the YUI library to initialize network script:
doRpc: function() {
YAHOO.util.Get.script(rpcUrl, {
onFailure: function() {
this.callbackError(true);
},
timeout: 55000
});
},
// YUI callback
callbackError: function(retry) {
if (retry && rpcRetries > 0) {
rpcRetries = rpcRetries - 1;
this.doRpc();
} else {
// ** how do i know this error handling is for the script which failed?
if (!rpcIgnoreError) {
this.populateFormStatus(6);
}
}
},
now, we have multiple functions calling rpc() such as:
sendConfig: function() {
this.rpc(urlForEndpoint, 3, true);
},
sendUser: function() {
this.rpc(urlForEndpoint, 3, false);
},
sendWidget: function() {
this.rpc(urlForEndpoint, 3, false);
},
I am concerned making an array of callbacks will not guarantee that each item is handled with its respective handler.
I could do something like create a map constant:
var RPC_ERR_CB = {
sendConfig: false,
sendUser: true,
sendWidget: true
};
// and then in the onFailure callback, I can read the src of the script tag:
...
doRpc: function() {
YAHOO.util.Get.script(rpcUrl, {
onFailure: function() {
var hasCB = Object.keys(RPC_ERR_CB).some(function(item) {
return arguments[0].src.indexOf(RPC_ERR_CB[item]) <= 0;
});
if (hasCB) {
this.callbackError(true);
}
},
timeout: 55000
});
},
Hope this makes sense...THANKS!
You could pass the values into doRpc, then you can pass it to callbackError or handle it in doRpc (like your example code at the end). This will prevent the global variable from changing on you.
If you're not able to use Promises or ES6 Classes, your options become somewhat limited. If at all possible, I would recommend biting the bullet on getting a Babel transpilation process so you can take advantage of newer features without needing to drop IE11 support.
As it is now though, ideally you don't want to track every request in a global variable somewhere. You can handle each transaction independently by creating each request as a self-contained object:
function RpcRequest (url, retries, ignoreError) {
this.url = url
this.retries = retries
this.ignoreError = ignoreError
}
RpcRequest.prototype.send = function() {
YAHOO.util.Get.script(this.url, {
onFailure: function() {
this.callbackError(true);
},
timeout: 55000
});
}
RpcRequest.prototype.callbackError = function(retry) {
if (retry && this.retries > 0) {
this.retries = this.retries - 1;
this.send();
} else {
if (!this.ignoreError) {
// ...
}
}
}
// Somewhere else, initiate a request
var requestOne = new RpcRequest("http://blah", 3, false)
requestOne.send()
Something I noted when looking over your code: the code that's creating the request has no idea whether the request succeeded or not. And when you have an error, the calling context doesn't know anything about that error. I took a look at the library you mentioned, and it does appear to have some context that you can pass along.
If I were to rewrite this a little bit, I'd do something like this to bubble the error up to your calling context:
RpcRequest.prototype.send = function(callback) {
YAHOO.util.Get.script(this.url, {
onFailure: function(context) {
if( this.ignoreError ) {
context.ignoredError = true
callback(null, context);
return;
}
var retError = new Error('Failure doing something!');
retError.context = context;
callback(retError);
},
onSuccess: function(context) {
callback(null, context);
},
timeout: 55000
});
}
// Somewhere else in the code...
sendWidget: function() {
var request = new RpcRequest(urlForEndpoint, 3, false)
request.send(function(err, result) {
if( err ) {
console.error('Failed at doing a widget thing:', err.context);
// maybe even:
// throw err;
return;
}
if( result.ignoredError ) {
console.warn('Ignored an error on the widget thing:', result);
return;
}
console.log('Success on the widget thing!', result);
})
}

Struggling with Q.js promises

I am sure I am missing something obvious but I can't seem to make heads or tails of this problem. I have a web page that is being driven by javascript. The bindings are being provided by Knockout.js, the data is coming down from the server using Breeze.js, I am using modules tied together with Require.js. My goal is to load the html, load the info from Breeze.js, and then apply the bindings to show the data to the user. All of these things appear to be happening correctly, just not in the correct order which is leading to weird binding errors. Now on to the code.
I have a function that gets called after the page loads
function applyViewModel() {
var vm = viewModel();
vm.activate()
.then(
applyBindings(vm)
);
}
This should call activate, wait for activate to finish, then apply bindings....but it appears to be calling activate, not waiting for it to finish and then runs applybindings.
activate -
function activate() {
logger.log('Frames Admin View Activated', null, 'frames', false);
return datacontext.getAllManufacturers(manufacturers)
.then(function () {
manufacturer(manufacturers()[0]);
}).then(function () {
datacontext.getModelsWithSizes(modelsWithSizes, manufacturers()[0].manufacturerID())
.then(datacontext.getTypes(types));
});
}
datacontext.getAllManufacturers -
var getAllManufacturers = function (manufacturerObservable) {
var query = entityQuery.from('Manufacturers')
.orderBy('name');
return manager.executeQuery(query)
.then(querySucceeded)
.fail(queryFailed);
function querySucceeded(data) {
if (manufacturerObservable) {
manufacturerObservable(data.results);
}
log('Retrieved [All Manufacturer] from remote data source',
data, true);
}
};
datacontext.getModelsWithSizes -
var getModelsWithSizes = function (modelsObservable, manufacturerId) {
var query = entityQuery.from('Models').where('manufactuerID', '==', manufacturerId)
.orderBy('name');
return manager.executeQuery(query)
.then(querySucceeded)
.fail(queryFailed);
function querySucceeded(data) {
if (modelsObservable) {
for (var i = 0; i < data.results.length; i++) {
datacontext.getSizes(data.results[i].sizes, data.results[i].modelID());
// add new size function
data.results[i].addNewSize = function () {
var newValue = createNewSize(this.modelID());
this.sizes.valueHasMutated();
return newValue;
};
}
modelsObservable(data.results);
}
log('Retrieved [Models With Sizes] from remote data source',
data, false);
}
};
Any help on why this promise isn't working would be appreciated, as would any process to figure it out so I can help myself the next time I run into this.
A common mistake when working with promises is instead of specifying a callback, you specify the value returned from a callback:
function applyViewModel() {
var vm = viewModel();
vm.activate()
.then( applyBindings(vm) );
}
Note that when the callback returns a regular truthy value (number, object, string), this should cause an exception. However, if the callback doesn't return anything or it returns a function, this can be tricky to locate.
To correct code should look like this:
function applyViewModel() {
var vm = viewModel();
vm.activate()
.then(function() {
applyBindings(vm);
});
}

Meteor observe changes added callback on server fires on all item

Tracker.autorun(function() {
DATA.find().observeChanges({
added: function(id, doc) {
console.log(doc);
}
});
});
This code is being called on the server. Every time the meteor server starts, the added function fires for every single item in the database. Is there a way to have the added callback fire only when new items are added?
added will be called for every document in the result set when observeChanges is first run. The trick is to ignore the callback during this initialization period. I have an expanded example in my answer to this question, but this code should work for you:
(function() {
var initializing = true;
DATA.find().observeChanges({
added: function(id, doc) {
if (!initializing) {
console.log(doc);
}
}
});
initializing = false;
})();
Note that Tracker.autorun is a client-only function. On the server I think it only ever executes once.
I struggled with this for a long time. For some reason, David's answer did not work for me - it was firing after the initializing variable was set to false.
This pattern from Avi was successful for me:
var usersLoaded = false;
Meteor.subscribe("profiles", function () {
// at this point all new users sent down are legitimately new ones
usersLoaded = true;
});
Meteor.users.find().observe({
added: function(user) {
if (usersLoaded) {
console.log("New user created: ", user);
}
}
});
Since it is initialization issue, you can do this.
var observerOfMessages = Messages.find({}).observe({
added: function(doc){
if(!observerOfMessages) return;
console.log(doc)
}
});
This is more elegant actually.
Provide a selector for the query which does not match old items. If using mongo ObjectID as _id you could query for items that have _id greater than the latest item's:
const latest = DATA.findOne({}, {sort: {_id: -1}})
DATA.find({_id: {$gt: latest._id}}).observeChanges({
added: function() { ... }
})
Or with createdAt timestamp:
const currentTime = new Date()
DATA.find({createdAt: {$gt: currentTime}}).observeChanges({
added: function() { ... }
})
Here's another way to solve this:
Meteor.subscribe('messages', function() {
var messages = Messages.find();
var msgCount = messages.count();
messages.observe({
addedAt: function(doc, atIndex) {
if(atIndex > (msgCount - 1)) console.log('added');
}
});
});
Should only fire for docs added after the existing amount is delivered. It's important that this goes in an onReady callback for Meteor.subscribe so that the msgCount changes as your subscription does... if for example, you're paginating your subscriptions.
cursor.observe() documentation

Can this old-style ajax call be easily done with jquery?

I'm working with some pretty old code and the following is being used to monitor session status. If the user is inactive for X minutes (determined by check_session.php), they are logged out.
The server side stuff works fine. Actually, the existing javascript appears to work OK as well, but looks like it needs cleaning up.
Here's the existing javascript:
function checkSessionStatus()
{
session_http.open('GET', '/check_session.php', true);
session_http.onreadystatechange = handleSessionHttpResponse;
session_http.send(null);
}
function handleSessionHttpResponse()
{
if (session_http.readyState == 4)
{
results = session_http.responseText;
if (results == 'inactive')
{
window.location='/logout.php';
document.getElementById('session_divbox').innerHTML = results;
}
}
}
function get_session_HTTPObject()
{
var xml_session_http;
if (!xml_session_http && typeof XMLHttpRequest != 'undefined')
{
try
{
xml_session_http = new XMLHttpRequest();
}
catch (e)
{
xml_session_http = false;
}
}
return xml_session_http;
}
var session_http = get_session_HTTPObject();
function init_page_header()
{
window.setInterval( 'checkSessionStatus();', 30000);
}
This seems incredibly long for what it is doing.
I am still learning jquery and am able to do some basic ajax calls like this one, which places a returned value in a div:
$(document).ready(function()
{
$('#users_online').load('/show_users_online.php');
var refreshId = setInterval(function()
{
$('#users_online').load('/show_users_online.php');
}, 2000);
$.ajaxSetup({ cache: false });
});
The issue with the first bit of code is that it returns a value of 'inactive', which is then acted on by the client (window redirect).
Is it possible to do this in Jquery without winding up with dozens of lines of code? I may already know how to do this and am not seeing the forest for the trees -- some guidance here is appreciated.
Even if its very vampiric question style, should look like
$.get('/check_session.php', function( data ) {
if( data === 'inactive' ) {
window.location='/logout.php';
document.getElementById('session_divbox').innerHTML = data;
}
});

After a clear, localStorage remembers previous values

So, this is a bit strange. I'm using Backbone and Backbone.localStorage to save remote data to local storage for caching. Pretty standard stuff.
When I run a localStorage.clear() on the entire store, all values are cleared out permanently except for the key that has a string array of values. It gets cleared out on first inspection, but then when storage saves again with Backbone.LocalStorage.sync('create', model); the previous values are back in there.
Of course, if I manually delete the key within Chrome Developer Tools, then it stays gone and isn't repopulated. It's as if the localStorage.clear() call still caches keys with a string array. I've confirmed it is initially cleared out on app start.
I'll post some code and screenshots here on edit, but really it's pretty standard except for the fact those values remain after the key is repopulated. Any ideas here?
EDIT: Lots of fun code to look at:
Collection:
app.DiagnosisCollection = Backbone.Collection.extend({
model: DiagnosisModel,
localStorage: new Backbone.LocalStorage('diagnosis'),
save: function(model) {
Backbone.LocalStorage.sync('create', model);
}
});
Model:
app.DiagnosisModel = Backbone.Model.extend({
urlRoot: app.ApiRoot,
url: app.DiagnosisApi,
initialize: function(options) {
if(!options.query) {
throw 'query must be passed to diagnosisModel';
}
this.local = false;
this._query = options.query;
},
sync: function(method, model, options) {
var diagnosisKey = localStorage.getItem('diagnosis');
var index, diagnosisStore;
if(diagnosisKey) {
diagnosisKey = diagnosisKey.split(',');
}
index = _.indexOf(diagnosisKey, this._query);
if(index !== -1) {
diagnosisStore = localStorage.getItem('diagnosis' + '-' + diagnosisKey[index]);
}
if(diagnosisStore) {
this.local = true;
options.success(JSON.parse(diagnosisStore));
}
else {
this.local = false;
var fetchUrl = this.urlRoot + this.url + this._query;
$.getJSON(fetchUrl, function(data, textStatus) {
if(textStatus !== 'success') {
options.error();
}
options.success(data);
});
}
}
});
return app.DiagnosisModel;
Controller function that does the work:
var self = this;
// Create a new collection if we don't already have one
// The save function puts it in local storage
if(!this.diagnosisCollection) {
this.diagnosisCollection = new DiagnosisCollection();
this.diagnosisCollection.on('add', function(diagModel) {
this.save(diagModel);
});
}
var diagModel = new DiagnosisModel({
query: diagId
});
diagModel.fetch({
success: function() {
var diagView = new DiagnosisView({
model: diagModel
});
if(!diagModel.local) {
self.diagnosisCollection.add(diagModel);
}
self._switchPage(diagView);
},
error: function() {
console.error("Diag Model Fetch Failed");
Backbone.history.navigate('503', { trigger: true });
}
});
By the way, localStorage.clear() call is in app start. It does an API call to see if the version on the server has changed. If the version has changed, then we nuke localStorage.
Instead of localStorage.clear(), use :
localStorage.removeItem('userInfo');
I have faced the same issue in my backbone application and i used this way to clear out the values.
P.s : Yes you need to specify all of your localstorage variables one by one..:(.. But this works well.

Categories