Safari Extension SQL transaction fails unless I refer to a non-existant variable - safari-extension

I'm "trying" to create a Safari Extension that adds links to a local sqlite database.
In the global.html file, I can create the database, but I can't create a table unless I refer to a non-existant variable, which makes the rest of the script fail.
// Global Variable to hold my database
var my_DB;
// Make A Database
init_DB();
// Make A Table
createTable(my_DB);
// Rest of the code
alert("Database and Table have been created!");
// Initialise the Database
function init_DB() {
try {
// Check for Database Support
if (!window.openDatabase) {
alert("Database functionality is NOT Supported!");
} else {
// Setup the database
var shortName = "imp_DB";
var version = '1.0';
var displayName = "My Important Database";
var maxSize = 65536; // in bytes
var theDB = openDatabase(shortName, version, displayName, maxSize);
}
} catch(e) {
// Error Handling
if (e == "INVALID_STATE_ERR") {
// We have a version number mismatch
alert("Invalid database version");
} else {
// Unknown error
alert("Unknown error: " + e);
}
return;
}
// Assign the database to the global variable
my_DB = theDB;
}
// Create The Table
function createTable(thisDB) {
thisDB.transaction(function(txn) {
txn.executeSql('CREATE TABLE IF NOT EXISTS people (id unique, name, age)');
});
// The following line is the problem
someVar = txn;
}
Calling init_DB() works fine, and the database is created. However, calling createTable(my_DB) just fails silently, (No Errors, or Warnings) and the rest of the code completes, UNLESS I refer to "txn" somehow.
So adding the "SomeVar = txn;" line allows the Table to be created, but unfortunately, it causes "ReferenceError: Can't find variable: txn" to appear in the console, and stops the rest of the code from running.
Does anyone have any ideas? I've been trying to get this to work for over a week, and I'm at my wit's end.
Thanks in advance for any suggestions ;-)
N.B. Declaring "txn" before attempting to create a Table also causes a silent fail.

Related

Update Document with external object

i have a database containing Song objects. The song class has > 30 properties.
My Music Tagging application is doing changes on a song on the file system.
It then does a lookup in the database using the filename.
Now i have a Song object, which i created in my Tagging application by reading the physical file and i have a Song object, which i have just retrieved from the database and which i want to update.
I thought i just could grab the ID from the database object, replace the database object with my local song object, set the saved id and store it.
But Raven claims that i am replacing the object with a different object.
Do i really need to copy every single property over, like this?
dbSong.Artist = songfromFilesystem.Artist;
dbSong.Album = songfromFileSystem.Album;
Or are there other possibilities.
thanks,
Helmut
Edit:
I was a bit too positive. The suggestion below works only in a test program.
When doing it in my original code i get following exception:
Attempted to associate a different object with id 'TrackDatas/3452'
This is produced by following code:
try
{
originalFileName = Util.EscapeDatabaseQuery(originalFileName);
// Lookup the track in the database
var dbTracks = _session.Advanced.DocumentQuery<TrackData, DefaultSearchIndex>().WhereEquals("Query", originalFileName).ToList();
if (dbTracks.Count > 0)
{
track.Id = dbTracks[0].Id;
_session.Store(track);
_session.SaveChanges();
}
}
catch (Exception ex)
{
log.Error("UpdateTrack: Error updating track in database {0}: {1}", ex.Message, ex.InnerException);
}
I am first looking up a song in the database and get a TrackData object in dbTracks.
The track object is also of type TrackData and i just put the ID from the object just retrieved and try to store it, which gives the above error.
I would think that the above message tells me that the objects are of different types, which they aren't.
The same error happens, if i use AutoMapper.
any idea?
You can do what you're trying: replace an existing object using just the ID. If it's not working, you might be doing something else wrong. (In which case, please show us your code.)
When it comes to updating existing objects in Raven, there are a few options:
Option 1: Just save the object using the same ID as an existing object:
var song = ... // load it from the file system or whatever
song.Id = "Songs/5"; // Set it to an existing song ID
DbSession.Store(song); // Overwrites the existing song
Option 2: Manually update the properties of the existing object.
var song = ...;
var existingSong = DbSession.Load<Song>("Songs/5");
existingSong.Artist = song.Artist;
existingSong.Album = song.Album;
Option 3: Dynamically update the existing object:
var song = ...;
var existingSong = DbSession.Load<Song>("Songs/5");
existingSong.CopyFrom(song);
Where you've got some code like this:
// Inside Song.cs
public virtual void CopyFrom(Song other)
{
var props = typeof(Song)
.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance)
.Where(p => p.CanWrite);
foreach (var prop in props)
{
var source = prop.GetValue(other);
prop.SetValue(this, source);
}
}
If you find yourself having to do this often, use a library like AutoMapper.
Automapper can automatically copy one object to another with a single line of code.
Now that you've posted some code, I see 2 things:
First, is there a reason you're using the Advanced.DocumentQuery syntax?
// This is advanced query syntax. Is there a reason you're using it?
var dbTracks = _session.Advanced.DocumentQuery<TrackData, DefaultSearchIndex>().WhereEquals("Query", originalFileName).ToList();
Here's how I'd write your code using standard LINQ syntax:
var escapedFileName = Util.EscapeDatabaseQuery(originalFileName);
// Find the ID of the existing track in the database.
var existingTrackId = _session.Query<TrackData, DefaultSearchIndex>()
.Where(t => t.Query == escapedFileName)
.Select(t => t.Id);
if (existingTrackId != null)
{
track.Id = existingTrackId;
_session.Store(track);
_session.SaveChanges();
}
Finally, #2: what is track? Was it loaded via session.Load or session.Query? If so, that's not going to work, and it's causing your problem. If track is loaded from the database, you'll need to create a new object and save that:
var escapedFileName = Util.EscapeDatabaseQuery(originalFileName);
// Find the ID of the existing track in the database.
var existingTrackId = _session.Query<TrackData, DefaultSearchIndex>()
.Where(t => t.Query == escapedFileName)
.Select(t => t.Id);
if (existingTrackId != null)
{
var newTrack = new Track(...);
newTrack.Id = existingTrackId;
_session.Store(newTrack);
_session.SaveChanges();
}
This means you already have a different object in the session with the same id. The fix for me was to use a new session.

Validate Form on Change ONLY

Because much of our imported data technically has validation errors, users are unable to update fields without first correcting previously entered bad data. This wouldn't be a problem except that many times this user doesn't have the information needed to enter a correct value into that field but we still need to save their update.
Is it possible to disable the validate on submit for a DynamicForm?
Is it possible to disable the validate on submit for a DynamicForm?
there's a disableValidation attribute, it disables client-side validators.
The best solution I could find thus far.
I'm disabling validation and overridding getValues, which is called as part of saveData so I manually parse through any fields and look for errors. If I find an error I remove it from the return value and store it under the valuesManager.invalidatedFields.
If a field had an error it will not be included in the save, but because the server will return the original value I had to override setValues as well to prevent your (bad) change from being overridden.
Also, because getValues is called on initial load it validates on load as well.
isc.ValuesManager.create({
disableValidation: true,
invalidatedFields: {},
setValues: function(values){
console.log("setting values..", this.invalidatedFields);
for (var key in this.invalidatedFields) {
if (this.invalidatedFields.hasOwnProperty(key)) {
values[key] = this.invalidatedFields[key];
}
}
this.Super("setValues", arguments);
},
getValues: function () {
this.invalidatedFields = [];
var data = this.Super("getValues");
for (var key in data) {
if (data.hasOwnProperty(key)) {
var form = this.getMemberForField(key);
if (form && !form.getField(key).validate()) {
console.log(key + " failed validation", data[key]);
this.invalidatedFields[key] = data[key];
delete data[key];
}
}
}
return data;
}
});

Nested WLJSONStore Calls Not Executing in Expected Sequence and Not Adding Items to the Collection

This is Worklight 6.1 code with dojo, testing with Chrome and the std dev server Liberty. What I want this code to do is to query a collection, which should have 0 or 1 entries, and either retrieve the one entry if it exists or create an entry with a supplied set of values. What I'm trying to do is store a url, id, and password for a service. If this is the first time the app has run after installation I want to prompt the user for this info and store it. The code to prompt the user will be added later. If it is not the first run of the app then the values should be stored in the collection and be retrieved. I'll add code later to allow the user to change and update the values.
What is happening now is that the .add never seems to be executed, and also the execution sequence I'm seeing thru the breakpoints I've set seems weird.
Here is the setup code
// set up the jsonStore
var collectionName = 'servers';
var collections = {};
collections[collectionName] = {};
// initialize the default jsonStore Monitor credentials
var jsonURL = 'http://myserver.com:9082';
var jsonUser = 'keyser';
var jsonPassword = 'soze';
And here is the problem code
// Initialize the JSONStore
WL.JSONStore.init(collections)
.then(function() {
console.log("store initialized");
// query the store
var query = {_id: 0};
WL.JSONStore.get(collectionName)
.find(query)
.then(function(arrayResults) {
console.log("credentials retrieved " + arrayResults.length);
if (arrayResults.length > 0) {
// retrieve the credentials from the json object
console.log("password retrieved " + arrayResults[0].json.password);
jsonURL = arrayResults[0].json.url;
jsonUser = arrayResults[0].json.user;
jsonPassword = arrayResults[0].json.password;
} else {
// load the default credentials into jsonStore
var credentials = {url: jsonURL, user: jsonUser, password: jsonPassword};
WL.JSONStore.get(collectionName)
.add(credentials)
.then(function() {
console.log("credentials loaded " + credentials.url);
})
.fail(function(errorObject) {
console.log("credential load failed");
});
} // end of else
// Query the model list
queryModels();
}) // end of get(collectionName) then
.fail(function(errorObject) {
console.log("credentials not retrived");
}); // end of get(collectionName) fail
}) // end of init(collections) then
.fail(function(errorObject) {
console.log("store init failed" + errorObject);
}); // end of init(collections) fail
}); // end of ready
When I step thru it flows in this sequence.
init(collections)
Then it jumps immediately to the "end of ready". Seems weird but I'm a rookie so maybe it's OK?
Back to the get(collectionName)
to the .then and logs "credentials retrieved" with and array length of 0
To the else clause of the statement
And it breaks on the get(collectionName) in the else clause. So far so good
From here it jumps to queryModels(), skipping over the .add (far as I can tell)
Then it returns to the .then under the 2nd get and logs "credentials loaded"
At this point execution ends "normally" except,
The item never gets added to the collection, and
The queryModels runs before I expect it to, I want it to run after the item is added.
By now it's probably obvious that I'm a rookie, so I'm probably making the rookie mistake. I know
I'm dealing with deferreds here with the .then and .fails, and I'm nesting them, which seems to be
an accepted technique, but I'm not getting the execution sequence I want.
I've tried this code commenting out the 2nd get(collections) in a couple of formats and it barfs both ways.
// WL.JSONStore.get(collectionName)
.add(credentials)
and
// WL.JSONStore.get(collectionName)
servers.add(credentials)
Any help greatly appreciated. Thanks!
Here's my "answer" below based on what I learned from the other answers below.
Bluewing and cnandrue's answers were both very helpful, and I got it working. The main issues I had turned out to be.
I had failed to grasp that slot 0 in a collection equates to a document _id key of 1. I was trying to query _id = 0, and never getting a hit. The add to the collection was working all along, I was just not reading it correctly.
Moving the queryModels into the if/else clauses (bluewing's suggestion) worked, and reading the material cnandreu referenced (very worthwhile to read) explained why it worked. Thanks!
The tip about the "weird" execution sequence being an artifact of the breakpoints was also very useful, I quit chasing that red herring.
Here is a working draft of the code after fixing these issues. I did not implement all of the suggestions yet, but probably will as I polish this up. Thanks again.
// Initialize the JSONStore - you have to .init to start the collection before you can read it.
WL.JSONStore.init(collections)
.then(function() {
console.log("store initialized");
// query the store
var query = {_id: 1};
WL.JSONStore.get(collectionName) // get 1
.find(query)
.then(function(arrayResults) {
console.log("credentials retrieved " + arrayResults.length);
if (arrayResults.length > 0) {
// retrieve the credentials from the json object
console.log("password retrieved " + arrayResults[0].json.password);
jsonURL = arrayResults[0].json.url;
jsonUser = arrayResults[0].json.user;
jsonPassword = arrayResults[0].json.password;
queryModels();
} else {
// load the default credentials into jsonStore
var credentials = {url: jsonURL, user: jsonUser, password: jsonPassword};
WL.JSONStore.get(collectionName) // get 2
.add(credentials)
.then(function(numberOfDocumentsAdded) {
console.log("Number of Docs Added" + numberOfDocumentsAdded);
queryModels();
}); // end of .add then
} // end of else
}); // end of get(collectionName) 1 then
}) // end of init(collections) then
.fail(function(errorObject) {
console.log("something failed" + errorObject);
}); // end of init(collections) fail
All the JSON store calls ( like add , init etc) are asynchronous. So only you are getting that weird flows when you are checking with Breakpoints.
To get you execution sequence try to move the queryModels(); once the credentials are loaded.
WL.JSONStore.get(collectionName)
.add(credentials)
.then(function() {
console.log("credentials loaded " + credentials.url);
queryModels();
})
My suggestion is the same as Bluewings', but I wanted to share some pseudocode:
function handleCredentials (arrayResults, callback) {
if (arrayResults.length > 0) {
//.... synchronous code here.
setTimeout(function () {
callback();
}, 0);
} else {
WL.JSONStore.get(collectionName)
.add({url: jsonURL, user: jsonUser, password: jsonPassword})
.then(function() {
callback();
});
}
}
WL.JSONStore.init(collections)
.then(function() {
WL.JSONStore.get(collectionName)
.find({_id: 1})
.then(function (arrayResults) {
handleCredentials(arrayResults, function () {
queryModels();
});
});
});
Notice I created a function for handleCredentials, that function will either do a synchronous operation (setting some variables with the result from the find call) or an asynchronous operation (calling add to add credentials). A setTimeout with 0 is called to preserve async behavior, this is explained in detail here. After the handleCredentials function has finished, you call the queryModels function via the callback pattern.
As an aside, I recommended reading this blog post: What’s so great about JavaScript Promises?. Especially the "Error Handling" section. You don't need to add a .fail to every promise, you can get away with less failure functions and the error object should provide enough details into what went wrong. JSONStore error objects are documented here, notice they contain the source of the failure (e.g. src: 'find').

my zend session name spacing does not work

I am new to Zend and very keen to learn, so I would really appreciate some help and guidance.
I am trying to create a 'method in a class' that will save the session variables of product pages visited by members to a site i.e
i,e examplesite com/product/?producttype= 6
I want to save the number 6 in a session variable. I also do not want to have a global session for the entire site; I just want it for selected pages. So, I guess I have to have Zend_Session::start() on the selected page; but I am not clear how this should be done.
Should I instantiate it in the page view page. i.e products page or do this in the indexAction() method for the products page. I have attempted to instantiate it below but it did not work.
public function rememberLastProductSearched()
{ //my attempt to start a session start for this particular page.
Zend_Session::start();
}
$session->productSearchCategory = $this->_request->getParam('product-search-category');
return" $session->productSearchCategory ";
}
else
{
//echo " nothing there
return " $session->productSearchCategory";
//";
}
}
With the rememberLastProductSearched() method I was trying to get the method to first check whether the user had searched for a new product or just arrived at the page by default. i.e whether he had used the get() action to search for a new product. If the answer is no, then I wanted the system to check whether their had been a previous saved session variable. so in procedural syntax it would have gone like this:
if(isset($_Get['producttype']))
{
//$dbc database connection
$producttype = mysqli_real_escape_string($dbc,trim($_GET['producttype']));
}
else
if(isset($_SESSION['producttype'])){
$producttype = mysqli_real_escape_string($dbc,trim($_SESSION['producttype']));
}
Can you please help me with the Zend/oop syntax. I am totally confused how it should be?
you're asking about simple work flow in an action, it should begin something like:
//in any controller
public function anyAction()
{
//open seesion, start will be called if needed
$session = new Zend_Session_Namespace('products');
//get value
$productCategory = $this->getRequest()->getParam('producttype');
//save value to namespace
$session->productType = $productCategory;
//...
}
now to move this off to a separate method you have to pass the data to the method...
protected function rememberLastProductSearched($productType)
{
//open seesion, start will be called if needed
$session = new Zend_Session_Namespace('products');
$session->productType = $productType;
}
So now if you want to test for presence of a value...
//in any controller
public function anyAction()
{
//open seesion, call the namespace whenever you need to access it
$session = new Zend_Session_Namespace('products');
if (!isset($session->productType)) {
$productCategory = $this->getRequest()->getParam('producttype');
//save value to session
$this->rememberLastProductSearched($productCategory)
} else {
$productCategory = $session->productType;
}
}
That's the idea.
Be mindful of your work flow as it can sometimes be very simple to inadvertently overwrite your session values.
$session = new Zend_Session_Namespace("productSearch");
if ($this->getRequest()->getParam('producttype')) { //isset GET param ?
$session->productType = $this->getRequest()->getParam('producttype');
$searchedProductType = $session->productType;
} else { //take the session saved value
if ($session->productType) {
$searchedProductType = $session->productType;
}
}
//now use $searchedProductType for your query

Pros & cons bean vs SSJS?

I was trying to build a bean that always retrieves the same document ( a counter document), gets the current value, increment it and save the document with the new value. Finally it should return the value to the calling method and that would get me a new sequential number in my Xpage.
Since the Domino objects cannot be serialized or singleton'ed what's the benefit creating a bean doing this, over creating a SSJS function doing the exact same thing?
My bean must have calls to session, database, view and document, which then will be called every time.
The same within the SSJS-function except for session and database.
Bean:
public double getTransNo() {
try {
Session session = ExtLibUtil.getCurrentSession();
Database db = session.getCurrentDatabase();
View view = db.getView("vCount");
view.refresh();
doc = view.getFirstDocument();
transNo = doc.getItemValueDouble("count");
doc.replaceItemValue("count", ++transNo);
doc.save();
doc.recycle();
view.recycle();
} catch (NotesException e) {
e.printStackTrace();
}
return transNo;
}
SSJS:
function getTransNo() {
var view:NotesView = database.getView("vCount");
var doc:NotesDocument = view.getFirstDocument();
var transNo = doc.getItemValueDouble("count");
doc.replaceItemValue("count", ++transNo);
doc.save();
doc.recycle();
view.recycle();
return transNo;
}
Thank you
Both pieces of code are not good (sorry to be blunt).
If you have one document in your view, you don't need a view refresh which might be queued behind a refresh on another view and be very slow. Presumably you are talking about a single sever solution (since replication of the counter document would for sure lead to conflicts).
What you do in XPages is to create a Java class and declare it as application bean:
public class SequenceGenerator {
// Error handling is missing in this class
private double sequence = 0;
private String docID;
public SequenceGenerator() {
// Here you load from the document
Session session = ExtLibUtil.getCurrentSession();
Database db = session.getCurrentDatabase();
View view = db.getView("vCount");
doc = view.getFirstDocument();
this.sequence = doc.getItemValueDouble("count");
this.docID = doc.getUniversalId();
Utils.shred(doc, view); //Shred currenDatabase isn't a good idea
}
public synchronized double getNextSequence() {
return this.updateSequence();
}
private double updateSequence() {
this.sequence++;
// If speed if of essence I would spin out a new thread here
Session session = ExtLibUtil.getCurrentSession();
Database db = session.getCurrentDatabase();
doc = db.getDocumentByUnid(this.docID);
doc.ReplaceItemValue("count", this.sequence);
doc.save(true,true);
Utils.shred(doc);
// End of the candidate for a thread
return this.sequence;
}
}
The problem for the SSJS code: what happens if 2 users hit that together? At least you need to use synchronized there too. Using a bean makes it accessible in EL too (you need to watch out not to call it too often). Also in Java you can defer the writing back to a different thread - or not write it back at all and in your class initialization code read the view with the actual documents and pick the value from there.
Update: Utils is a class with static methods:
/**
* Get rid of all Notes objects
*
* #param morituri = the one designated to die, read your Caesar!
*/
public static void shred(Base... morituri) {
for (Base obsoleteObject : morituri) {
if (obsoleteObject != null) {
try {
obsoleteObject.recycle();
} catch (NotesException e) {
// We don't care we want go get
// rid of it anyway
} finally {
obsoleteObject = null;
}
}
}
}