So I have this JS indexedDB library:
window.db = {
name:"indexedDBname",
varsion:0.7,
_settings:"indexedDBobject",
h:null, // Handler to the db
open:function(callback,callback1) {
var r = indexedDB.open( db.name );
r.onupgradeneeded = function(e){ console.log(".onupgradeneeded is not yet supported by webkit"); };
r.onsuccess = function(e){
db.h = e.target.result;
if( db.version !== db.h.version ) {
var v = db.h.setVersion( db.version );
v.onsuccess = function(e) {
if(db.h.objectStoreNames.contains( db._settings )) db.h.deleteObjectStore( db._settings );
db.h.createObjectStore(db._settings, { keyPath:"name" });
};
v.onfailure = db.onerror;
v.onerror = db.onerror;
v.onblocked = db.onerror;
}
// CALLBACKS
if(typeof callback=="function" && typeof callback1=="function") callback.call(window,callback1);
else if(typeof callback=="function" && typeof callback1!="function") callback.call(window);
};
r.onfailure = db.onerror;
},
getSettings:function(callback){ // retrieve user custom settings
var t = db.h.transaction( [db._settings], IDBTransaction.READ_ONLY ),
s = t.objectStore(db._settings),
keyRange = IDBKeyRange.lowerBound(0),
cursorRequest = s.openCursor(keyRange),
tmp = {};
cursorRequest.onsuccess = function(e) {
var result = e.target.result;
if(!!result==false) {
// CALLBACKS
if(typeof callback=="function") callback.call(window);
return;
}
tmp[result.value.name] = result.value.value;
result.continue();
}
cursorRequest.onerror = db.onerror;
},
onerror:function(e){ console.log("Handle and print error here:"+e); }
};
// actual run
db.open( db.getSettings, user.applySettings);
Which I tend to use pretty often, but as you can see, those callback doesn't look too well... And when I want to do series of tasks or even call any of those functions with set of their own parameters, my code starts to look really choppy, ex.
db.open('forWhichUser',newSettingsToApplyObject, callback1, argumentForCallback1, secondOptionalArgument, callback2, etc);
So in the old days I'll just do:
db.open('userName', settingsMap);
var opts = db.getSettings();
user.downloadInfoBasedOn( opts );
user.renderInfoTo('userDataHolderId');
but now, since everything can start/finish in unpredictable moments (depending on computer performance, db size, etc, etc...) how do I handle all of that asynchronicity keeping code graceful and readable?
You can use the JavaScript promises/deferreds pattern:
http://wiki.commonjs.org/wiki/Promises/A
http://blogs.msdn.com/b/ie/archive/2011/09/11/asynchronous-programming-in-javascript-with-promises.aspx
Promises/deferreds can help you create a much easier and readable async code. You can use jQuery deffered object to achieve that (http://api.jquery.com/category/deferred-object/)
Another option is to use story.js which wrap the IndexedDB API and exposes it in a much simpler way (http://blogs.microsoft.co.il/blogs/gilf/archive/2012/04/21/the-story-begins.aspx)
I hope you will find this answer helpful.
Gil
Related
I want to be able to change the value of a global variable when it is being used by a function as a parameter.
My javascript:
function playAudio(audioFile, canPlay) {
if (canPlay < 2 && audioFile.paused) {
canPlay = canPlay + 1;
audioFile.play();
} else {
if (canPlay >= 2) {
alert("This audio has already been played twice.");
} else {
alert("Please wait for the audio to finish playing.");
};
};
};
const btnPitch01 = document.getElementById("btnPitch01");
const audioFilePitch01 = new Audio("../aud/Pitch01.wav");
var canPlayPitch01 = 0;
btnPitch01.addEventListener("click", function() {
playAudio(audioFilePitch01, canPlayPitch01);
});
My HTML:
<body>
<button id="btnPitch01">Play Pitch01</button>
<button id="btnPitch02">Play Pitch02</button>
<script src="js/js-master.js"></script>
</body>
My scenario:
I'm building a Musical Aptitude Test for personal use that won't be hosted online. There are going to be hundreds of buttons each corresponding to their own audio files. Each audio file may only be played twice and no more than that. Buttons may not be pressed while their corresponding audio files are already playing.
All of that was working completely fine, until I optimised the function to use parameters. I know this would be good to avoid copy-pasting the same function hundreds of times, but it has broken the solution I used to prevent the audio from being played more than once. The "canPlayPitch01" variable, when it is being used as a parameter, no longer gets incremented, and therefore makes the [if (canPlay < 2)] useless.
How would I go about solving this? Even if it is bad coding practise, I would prefer to keep using the method I'm currently using, because I think it is a very logical one.
I'm a beginner and know very little, so please forgive any mistakes or poor coding practises. I welcome corrections and tips.
Thank you very much!
It's not possible, since variables are passed by value, not by reference. You should return the new value, and the caller should assign it to the variable.
function playAudio(audioFile, canPlay) {
if (canPlay < 2 && audioFile.paused) {
canPlay = canPlay + 1;
audioFile.play();
} else {
if (canPlay >= 2) {
alert("This audio has already been played twice.");
} else {
alert("Please wait for the audio to finish playing.");
};
};
return canPlay;
};
const btnPitch01 = document.getElementById("btnPitch01");
const audioFilePitch01 = new Audio("../aud/Pitch01.wav");
var canPlayPitch01 = 0;
btnPitch01.addEventListener("click", function() {
canPlayPitch01 = playAudio(audioFilePitch01, canPlayPitch01);
});
A little improvement of the data will fix the stated problem and probably have quite a few side benefits elsewhere in the code.
Your data looks like this:
const btnPitch01 = document.getElementById("btnPitch01");
const audioFilePitch01 = new Audio("../aud/Pitch01.wav");
var canPlayPitch01 = 0;
// and, judging by the naming used, there's probably more like this:
const btnPitch02 = document.getElementById("btnPitch02");
const audioFilePitch02 = new Audio("../aud/Pitch02.wav");
var canPlayPitch02 = 0;
// and so on
Now consider that global data looking like this:
const model = {
btnPitch01: {
canPlay: 0,
el: document.getElementById("btnPitch01"),
audioFile: new Audio("../aud/Pitch01.wav")
},
btnPitch02: { /* and so on */ }
}
Your event listener(s) can say:
btnPitch01.addEventListener("click", function(event) {
// notice how (if this is all that's done here) we can shrink this even further later
playAudio(event);
});
And your playAudio function can have a side-effect on the data:
function playAudio(event) {
// here's how we get from the button to the model item
const item = model[event.target.id];
if (item.canPlay < 2 && item.audioFile.paused) {
item.canPlay++;
item.audioFile.play();
} else {
if (item.canPlay >= 2) {
alert("This audio has already been played twice.");
} else {
alert("Please wait for the audio to finish playing.");
};
};
};
Side note: the model can probably be built in code...
// you can automate this even more using String padStart() on 1,2,3...
const baseIds = [ '01', '02', ... ];
const model = Object.fromEntries(
baseIds.map(baseId => {
const id = `btnPitch${baseId}`;
const value = {
canPlay: 0,
el: document.getElementById(id),
audioFile: new Audio(`../aud/Pitch${baseId}.wav`)
}
return [id, value];
})
);
// you can build the event listeners in a loop, too
// (or in the loop above)
Object.values(model).forEach(value => {
value.el.addEventListener("click", playAudio)
})
below is an example of the function.
btnPitch01.addEventListener("click", function() {
if ( this.dataset.numberOfPlays >= this.dataset.allowedNumberOfPlays ) return;
playAudio(audioFilePitch01, canPlayPitch01);
this.dataset.numberOfPlays++;
});
you would want to select all of your buttons and assign this to them after your html is loaded.
https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementsByClassName
const listOfButtons = document.getElementsByClassName('pitchButton');
listOfButtons.forEach( item => {
item.addEventListener("click", () => {
if ( this.dataset.numberOfPlays >= this.dataset.allowedNumberOfPlays ) return;
playAudio("audioFilePitch" + this.id);
this.dataset.numberOfPlays++;
});
I want to execute this query
select * from properties where propertyCode IN ("field1", "field2", "field3")
How can I achieve this in IndexedDB
I tried this thing
getData : function (indexName, params, objectStoreName) {
var defer = $q.defer(),
db, transaction, index, cursorRequest, request, objectStore, resultSet, dataList = [];
request = indexedDB.open('test');
request.onsuccess = function (event) {
db = request.result;
transaction = db.transaction(objectStoreName);
objectStore = transaction.objectStore(objectStoreName);
index = objectStore.index(indexName);
cursorRequest = index.openCursor(IDBKeyRange.only(params));
cursorRequest.onsuccess = function () {
resultSet = cursorRequest.result;
if(resultSet){
dataList.push(resultSet.value);
resultSet.continue();
}
else{
console.log(dataList);
defer.resolve(dataList);
}
};
cursorRequest.onerror = function (event) {
console.log('Error while opening cursor');
}
}
request.onerror = function (event) {
console.log('Not able to get access to DB in executeQuery');
}
return defer.promise;
But didn't worked. I tried google but couldn't find exact answer.
If you consider that IN is essentially equivalent to field1 == propertyCode OR field2 == propertyCode, then you could say that IN is just another way of using OR.
IndexedDB cannot do OR (unions) from a single request.
Generally, your only recourse is to do separate requests, then merge them in memory. Generally, this will not have great performance. If you are dealing with a lot of objects, you might want to consider giving up altogether on this approach and thinking of how to avoid such an approach.
Another approach is to iterate over all objects in memory, and then filter those that don't meet your conditions. Again, terrible performance.
Here is a gimmicky hack that might give you decent performance, but it requires some extra work and a tiny bit of storage overhead:
Store an extra field in your objects. For example, plan to use a property named hasPropertyCodeX.
Whenever any of the 3 properties are true (has the right code), set the field (as in, just make it a property of the object, its value is irrelevant).
When none of the 3 properties are true, delete the property from the object.
Whenever the object is modified, update the derived property (set or unset it as appropriate).
Create an index on this derived property in indexedDB.
Open a cursor over the index. Only objects with a property present will appear in the cursor results.
Example for 3rd approach
var request = indexedDB.open(...);
request.onupgradeneeded = upgrade;
function upgrade(event) {
var db = event.target.result;
var store = db.createObjectStore('store', ...);
// Create another index for the special property
var index = store.createIndex('hasPropCodeX', 'hasPropCodeX');
}
function putThing(db, thing) {
// Before storing the thing, secretly update the hasPropCodeX value
// which is derived from the thing's other properties
if(thing.field1 === 'propCode' || thing.field2 === 'propCode' ||
thing.field3 === 'propCode') {
thing.hasPropCodeX = 1;
} else {
delete thing.hasPropCodeX;
}
var tx = db.transaction('store', 'readwrite');
var store = tx.objectStore('store');
store.put(thing);
}
function getThingsWherePropCodeXInAnyof3Fields(db, callback) {
var things = [];
var tx = db.transaction('store');
var store = tx.objectStore('store');
var index = store.index('hasPropCodeX');
var request = index.openCursor();
request.onsuccess = function(event) {
var cursor = event.target.result;
if(cursor) {
var thing = cursor.value;
things.push(thing);
cursor.continue();
} else {
callback(things);
}
};
request.onerror = function(event) {
console.error(event.target.error);
callback(things);
};
}
// Now that you have an api, here is some example calling code
// Not bothering to promisify it
function getData() {
var request = indexedDB.open(...);
request.onsuccess = function(event) {
var db = event.target.result;
getThingsWherePropCodeXInAnyof3Fields(db, function(things) {
console.log('Got %s things', things.length);
for(let thing of things) {
console.log('Thing', thing);
}
});
};
}
I need to create web browser using CefSharp.Wpf with ability to give fake data to site for example CPU cores, browser plugins, platform name etc.
There are site that can retrieve all this info: https://www.deviceinfo.me/
My quesiton is: How to hide GPU info from this site? Using javascript or CefSharp functionality
I have tried to redefine WebGLRenderingContext.getParameter method, which gives an info about GPU renderer and vendor:
var canvas = document.createElement('canvas');
var gl;
try {
gl = canvas.getContext("webgl2") || canvas.getContext("webgl") || canvas.getContext("experimental-webgl2") || canvas.getContext("experimental-webgl");
} catch (e) {
}
var oldParam = WebGLRenderingContext.prototype.getParameter;
WebGLRenderingContext.prototype.getParameter = function(parameter){
console.log("we have guests");
if(parameter == debugInfo.UNMASKED_RENDERER_WEBGL){
return "GTX 1080";
}
if(parameter == gl.getExtension("WEBGL_debug_renderer_info").UNMASKED_RENDERER_WEBGL){
return "GTX 1080";
}
if(parameter == debugInfo.UNMASKED_RENDERER_WEBGL){
return "NVidia";
}
if(parameter == gl.VERSION){
return "GTX 1080";
}
return oldParam(parameter);
};
I expected to completely redefine this method and return some fake info, but when i called gl.getParameter(param) again, it still gave me an old gpu info
If you still want Canvas2D and WebGL to still work then you can't hide since they can finger print by actually rendering.
You could disable them with
HTMLCanvasElement.prototype.getContext = function() {
return null;
};
Though the fact they don't exist is also a data point.
Otherwise your wrapper appears to have some issues.
First you really should set the function before creating the context.
Second your last line should be
oldParam.call(this, parameter);
Also you didn't show debugInfo but you can use WebGLRenderingContext instead or you can just hard code the numbers
As for http://www.deviceinfo.me you need to make sure your patch runs in all iframes and workers before any other JavaScript.
WebGLRenderingContext.prototype.getParameter = function(origFn) {
const paramMap = {};
paramMap[0x9245] = "Foo"; // UNMASKED_VENDOR_WEBGL
paramMap[0x9246] = "Bar"; // UNMASKED_RENDERER_WEBGL
paramMap[0x1F00] = "Nobody"; // VENDOR
paramMap[0x1F01] = "Jim"; // RENDERER
paramMap[0x1F02] = "Version 1.0"; // VERSION
return function(parameter) {
return paramMap[parameter] || origFn.call(this, parameter);
};
}(WebGLRenderingContext.prototype.getParameter);
// --- test
const gl = document.createElement('canvas').getContext('webgl');
const ext = gl.getExtension('WEBGL_debug_renderer_info');
show(gl, gl, [
'VENDOR',
'RENDERER',
'VERSION',
]);
if (ext) {
show(gl, ext, [
'UNMASKED_VENDOR_WEBGL',
'UNMASKED_RENDERER_WEBGL',
]);
}
function show(gl, base, params) {
for (const param of params) {
console.log(param, ':', gl.getParameter(base[param]));
}
}
There is WebGLRenderingContext and WebGL2RenderingContext
I have a similar question here, but I thought I'd ask it a different way to cast a wider net. I haven't come across a workable solution yet (that I know of).
I'd like for XCode to issue a JavaScript command and get a return value back from an executeSql callback.
From the research that I've been reading, I can't issue a synchronous executeSql command. The closest I came was trying to Spin Lock until I got the callback. But that hasn't worked yet either. Maybe my spinning isn't giving the callback chance to come back (See code below).
Q: How can jQuery have an async=false argument when it comes to Ajax? Is there something different about XHR than there is about the executeSql command?
Here is my proof-of-concept so far: (Please don't laugh)
// First define any dom elements that are referenced more than once.
var dom = {};
dom.TestID = $('#TestID'); // <input id="TestID">
dom.msg = $('#msg'); // <div id="msg"></div>
window.dbo = openDatabase('POC','1.0','Proof-Of-Concept', 1024*1024); // 1MB
!function($, window, undefined) {
var Variables = {}; // Variables that are to be passed from one function to another.
Variables.Ready = new $.Deferred();
Variables.DropTableDeferred = new $.Deferred();
Variables.CreateTableDeferred = new $.Deferred();
window.dbo.transaction(function(myTrans) {
myTrans.executeSql(
'drop table Test;',
[],
Variables.DropTableDeferred.resolve()
// ,WebSqlError
);
});
$.when(Variables.DropTableDeferred).done(function() {
window.dbo.transaction(function(myTrans) {
myTrans.executeSql(
'CREATE TABLE IF NOT EXISTS Test'
+ '(TestID Integer NOT NULL PRIMARY KEY'
+ ',TestSort Int'
+ ');',
[],
Variables.CreateTableDeferred.resolve(),
WebSqlError
);
});
});
$.when(Variables.CreateTableDeferred).done(function() {
for (var i=0;i < 10;i++) {
myFunction(i);
};
Variables.Ready.resolve();
function myFunction(i) {
window.dbo.transaction(function(myTrans) {
myTrans.executeSql(
'INSERT INTO Test(TestID,TestSort) VALUES(?,?)',
[
i
,i+100000
]
,function() {}
,WebSqlError
)
});
};
});
$.when(Variables.Ready).done(function() {
$('#Save').removeAttr('disabled');
});
}(jQuery, window);
!function($, window, undefined) {
var Variables = {};
$(document).on('click','#Save',function() {
var local = {};
local.result = barcode.Scan(dom.TestID.val());
console.log(local.result);
});
var mySuccess = function(transaction, argument) {
var local = {};
for (local.i=0; local.i < argument.rows.length; local.i++) {
local.qry = argument.rows.item(local.i);
Variables.result = local.qry.TestSort;
}
Variables.Return = true;
};
var myError = function(transaction, argument) {
dom.msg.text(argument.message);
Variables.result = '';
Variables.Return = true;
}
var barcode = {};
barcode.Scan = function(argument) {
var local = {};
Variables.result = '';
Variables.Return = false;
window.dbo.transaction(function(myTrans) {
myTrans.executeSql(
'SELECT * FROM Test WHERE TestID=?'
,[argument]
,mySuccess
,myError
)
});
for (local.I = 0;local.I < 3; local.I++) { // Try a bunch of times.
if (Variables.Return) break; // Gets set in mySuccess and myError
SpinLock(250);
}
return Variables.result;
}
var SpinLock = function(milliseconds) {
var local = {};
local.StartTime = Date.now();
do {
} while (Date.now() < local.StartTime + milliseconds);
}
function WebSqlError(tx,result) {
if (dom.msg.text()) {
dom.msg.append('<br>');
}
dom.msg.append(result.message);
}
}(jQuery, window);
Is there something different about XHR than there is about the executeSql command?
Kind of.
How can jQuery have an async=false argument when it comes to Ajax?
Ajax, or rather XMLHttpRequest, isn't strictly limited to being asynchronous -- though, as the original acronym suggested, it is preferred.
jQuery.ajax()'s async option is tied to the boolean async argument of xhr.open():
void open(
DOMString method,
DOMString url,
optional boolean async, // <---
optional DOMString user,
optional DOMString password
);
The Web SQL Database spec does also define a Synchronous database API. However, it's only available to implementations of the WorkerUtils interface, defined primarily for Web Workers:
window.dbo = openDatabaseSync('POC','1.0','Proof-Of-Concept', 1024*1024);
var results;
window.dbo.transaction(function (trans) {
results = trans.executeSql('...');
});
If the environment running the script hasn't implemented this interface, then you're stuck with the asynchronous API and returning the result will not be feasible. You can't force blocking/waiting of asynchronous tasks for the reason you suspected:
Maybe my spinning isn't giving the callback chance to come back (See code below).
I'm using a proxy class as the data I have is a reference to a Firebase location that stores my object but I want to act as if I have the object itself. I've got something that works fine but I would like to improve it, the key criteria being to reduce repetition. I suspect something is possible by inspecting the Map class and using apply() but I don't know quite how to do that (or if there is a better solution).
I think it would also be useful if the solution could be generalised to support any class, not just the Map class.
var Map = function() {
...
};
var MapProxy = function(mapRef) {
this.mapRef = mapRef;
};
Map.prototype.addToken = function(portrait, newLocation) {
...
};
Map.prototype.removeToken = function(token) {
...
};
Map.prototype.moveToken = function(token, newLocation) {
...
};
MapProxy.prototype.addToken = function(portrait, newLocation) {
var mapRef = this.mapRef;
mapRef.once('value', function(data) {
var map = new Map();
map.init(mapRef, data.val());
map.addToken(portrait, newLocation);
});
};
MapProxy.prototype.removeToken = function(token) {
var mapRef = this.mapRef;
mapRef.once('value', function(data) {
var map = new Map();
map.init(mapRef, data.val());
map.removeToken(token);
});
};
MapProxy.prototype.moveToken = function(token, newLocation) {
var mapRef = this.mapRef;
mapRef.once('value', function(data) {
var map = new Map();
map.init(mapRef, data.val());
map.moveToken(token, newLocation);
});
};
var mapProxy = new MapProxy(mapRef);
Think I solved it myself in the end.
var FirebaseProxy = function(classToProxy, firebaseRef) {
var key,
self = this;
self.proxy = classToProxy;
self.firebaseRef = firebaseRef;
for (key in self.proxy.prototype) {
if (typeof self.proxy.prototype[key] === 'function') {
(function(inner_key) {
self[inner_key] = function ()
{
var args = arguments;
self.firebaseRef.once('value', function(data) {
var proxiedInstance = new self.proxy();
if (typeof proxiedInstance.init === 'function') {
proxiedInstance.init(self.firebaseRef, data.val());
}
proxiedInstance[inner_key].apply(proxiedInstance, args);
});
}
})(key);
}
}
}
I don't think I completely follow what you're trying to accomplish. Could you forego the proxy and just use something like this?
var Map = function(mapRef) {
mapRef.on('value', function(snap) {
this.init(snap.val());
});
};
Map.prototype.init = function(data) {
// update internal state with new data from Firebase ...
};
...
Since 'value' will fire every time the data at mapRef changes, your map object will always have the latest data.
It's worth noting that if you're going to be needing the latest map data on a regular basis, you should probably use .on(), not .once(). .once() will go and retrieve the data from the servers every time you ask for it, while .on() will always have the latest data cached (since it subscribes to updates). So it'll be faster and use less bandwidth.