Difference between revisions of "User:Abel/PersonalData.js"

From Wikibase Personal data
Jump to navigation Jump to search
m
m
Line 1: Line 1:
 
// License: GPL
 
// License: GPL
// User Interface Rendering
+
// Indexeddb JavaScript
console.log('Interface handler loading');
+
console.log('Indb loading');
 +
 
 +
function IndexedDBContainer( ww ) {
 +
 
 +
// suppress console.log, switch to true for debug
 +
var Debug_switch = false;
 +
// seemingly the Safari engine uses a somewhat different implementation of arguments for db.transaction and index.get
 +
// because no browser on any mac / ios is allowed to use a different engine, rather than user agent, platform can be tested
 +
var iOS = !!navigator.platform && /Macintosh|MacIntel|MacPPC|Mac68K|Mac|iPad|iPhone|iPod/.test(navigator.platform);
 +
 
 +
// deterministic hash generator for creating user id#s from usernames, should be replaced by something more functional (guaranteed to be transitively different)
 +
hashCode = function(s) {
 +
if ( s == undefined) {
 +
s = '00'
 +
}
 +
var h = 0, l = s.length, i = 0;
 +
if ( l > 0 )
 +
while (i < l)
 +
h = (h << 5) - h + s.charCodeAt(i++) | 0;
 +
return h;
 +
};
  
function InterfaceMediaContainer ( WikibaseProcessor, indexedDBobject ) {
 
 
 
this.renderInterface = function( sqitch ) {
+
if ( ww.user != undefined ) {
switch( sqitch ) {
+
// if user exists in object passed to constructor, get username
case 'controller_id':
+
var self = ww.user.getName();
controller_render();
+
// create userid by hashing username
break;
+
var idgen = hashCode(self);
case 'personal_data_id':
+
debuglog(idgen);
pd_site_render();
+
 
break;
+
// Take username for standard databasename format:
case 'interface_button_id':
+
// Our current convention for personal databasename (separate dbname for each user)
personal_data_edit_interface_render();
+
var PersonalDataDataBaseName = "PD.IO" + idgen + "Database";
break;
+
// Use username in user to get up to date version of self
 +
// Blank object for personal data Record with id and username included
 +
this.CurrentPerson = {id: idgen, name: {user: self}, age: 0};
 +
debuglog(PersonalDataDataBaseName);
 +
debuglog(this.CurrentPerson);
 
}
 
}
}
 
  
var button_save = [],
+
// ADD MORE DOC
button_remove = [],
+
 
textInput = [];
+
// EnqueuePDIO a PDIOLocalDatabase:User database access function with modes
 +
// Externally accessible wrapper for Enquire with current user / db preloaded
  
async function controller_render(){
+
this.EnqueuePDIO = async function( sw, c1, c2, c3 ) {
console.log("this function is done by the current page controller for now");
+
debuglog("enquing pdio db " + PersonalDataDataBaseName + " with user " + JSON.stringify(this.CurrentPerson));
notify_porting();
+
var k = await Enquire( PersonalDataDataBaseName, this.CurrentPerson, sw, c1, c2, c3 );
 +
return k;
 
}
 
}
  
async function pd_site_render(){
+
// Enquire, a general purpose indexeddb update function with modes
  
};
+
// Mode 'aggressive push':
 +
// parameter record is pushed to database, overwriting whatever is there
  
async function personal_data_edit_interface_render(){
+
// Mode 'checkin':
clear_render();
+
// record is retrieved from database, parameter record is pushed if not found
 
// Get concerns relation :: Parts of this must be moved upstream, MAIN should know that this is the data the renderer will need and provide it
 
  
var concern_origin = WikibaseProcessor.checkentity( { this_concerns: [ WBEStructure['concernsRelation'], WBEStructure['IDPropLoc'], WBEStructure['qIDPropName'] ] } )['this_concerns'][0];
+
// Mode 'get record'
 +
// updates record given as argument
  
// Get defval for entity :: Parts of this must be moved upstream, MAIN should know that this is the data the renderer will need and provide it
+
// Mode 'update record' field1 field2 field3
 +
// retrieve record, make record.field1.field2 = field3, push back
  
var default_value = WikibaseProcessor.checkentity( { default_value: [ WBEStructure['defaultValRelation'], WBEStructure['IDPropLoc'] ] } )['default_value'][0]; //defal
+
// Mode 'update record' field1 field2
 +
// retrieve record, make record.field1 = field2, push back
  
var get_record = indexedDBobject.EnqueuePDIO('update record');
+
// Mode 'remove from record' field1
 +
// retrieve record, splice/delete record.field1, push back
 +
 +
// Mode 'remove from record' field1 field2
 +
// retrieve record, splice/delete record.field1.field2, push back
  
get_record.then( function(result) {
+
async function Enquire( pddbname, record, sqitch, control_1, control_2, control_3 ) {
 +
return new Promise(
 +
function(resolve, reject) {
  
mw.loader.using( 'oojs-ui-core' ).done( function () {
+
var pddb = window.indexedDB.open(pddbname, 3);
 +
var inp_obj;
  
button_save = [],
+
pddb.onerror = function() { 
button_remove = [],
+
    console.log("Warning: Access to IndexedDB for application has been rejected.");
textInput = [];
+
};
  
var extrafield = ( result[concern_origin] != undefined ) ? Object.keys(result[concern_origin]).length : 0;
+
// indexeddb internal mechanism for version tracking of schema
 
+
pddb.onupgradeneeded = function() {
button_save[ extrafield ] = new OO.ui.ButtonWidget( { label: "Save", classes: [ 'pduimark' ] } );
+
console.log('upgrading database');
 +
var db = pddb.result;
 +
var store = db.createObjectStore(pddbname, {keyPath: "id"});
 +
var index = store.createIndex("NameIndex", ["id"]);
 +
 +
};
 +
// Database successfully opened function.
 +
pddb.onsuccess = function() {
 +
debuglog(pddb.result);
 +
var db = pddb.result;
 +
var tx;
 +
 +
if ( iOS ) {
 +
tx = db.transaction([pddbname], "readwrite");
 +
} else {
 +
tx = db.transaction(pddbname, "readwrite");
 +
}
 
 
textInput[ extrafield ] = new OO.ui.TextInputWidget( {
+
var store = tx.objectStore(pddbname);
placeholder: default_value,
+
var index = store.index("NameIndex");
classes: [ 'pduimark' ]
 
} );
 
  
button_save[ extrafield ].on( 'click', function () {
+
// Get the original record from the db...
indexedDBobject.EnqueuePDIO('update record', concern_origin, extrafield, textInput[ extrafield ].value );
+
var getRecord;
personal_data_edit_interface_render();
+
if ( iOS ) {
});
+
getRecord = index.get(record.id);
 +
} else {
 +
getRecord = index.get([record.id]);
 +
}
  
$( '#mw-content-text' ).prepend( button_save[ extrafield ].$element );
+
getRecord.onsuccess = function() {
$( '#mw-content-text' ).prepend( textInput[ extrafield ].$element );
+
debuglog( "Record in database: \n <<");
 +
debuglog( getRecord.result );
 +
debuglog( ">>");
 +
// Essential recordAdd
 +
if ( sqitch === "aggressive push" ) {
 +
// Mode 'aggressive push':
 +
// parameter record is pushed to database, overwriting whatever is there
 +
inp_obj = record;
 +
store.put( inp_obj );
 +
return inp_obj;
 +
} else if ( sqitch === "checkin" ) {
 +
// Mode 'checkin':
 +
// record is retrieved from database, parameter record is pushed if not found
 +
if ( getRecord.result != undefined ) {
 +
debuglog("getting from store");
 +
inp_obj = getRecord.result;
 +
} else {
 +
debuglog("overriding whatever in there");
 +
inp_obj = record;
 +
store.put(inp_obj);
 +
}
  
if ( extrafield > 0) {
+
} else {
Object.keys( result[concern_origin] ).forEach( function(data) {
+
// Methods manipulationg the actual record come here
button_save[data] = new OO.ui.ButtonWidget( { label: "Save", classes: [ 'pduimark' ] } );
+
// Update & Remove from
button_remove[data] = new OO.ui.ButtonWidget( { label: "Delete", classes: [ 'pduimark' ] } );
+
// If we got the record from store, use that version to be manipulated
textInput[data] = new OO.ui.TextInputWidget( {
+
if ( getRecord.result !== undefined ) {
value: result[concern_origin][data], ///
+
inp_obj = getRecord.result;
placeholder: default_value,
+
classes: [ 'pduimark' ]
+
if ( sqitch === "get record" ) {
} );
+
button_save[data].on( 'click', function () {
+
debuglog("Getting record");
 +
// we will return later
 +
 +
}  
  
indexedDBobject.EnqueuePDIO('update record', concern_origin, data, textInput[ data ].value );
+
// endof onsuccess
 +
} else {
  
personal_data_edit_interface_render();
+
debuglog("Record not found");
});
 
  
button_remove[data].on( 'click', function () {
+
if ( sqitch === "update record" ) {
indexedDBobject.EnqueuePDIO('remove from record', concern_origin, data );
+
// Update record will use supplied copy to update it
personal_data_edit_interface_render();
+
inp_obj = record;
});
+
} else {
$( '#mw-content-text' ).prepend( button_remove[data].$element );
+
if ( sqitch === "get record" ) {
$( '#mw-content-text' ).prepend( button_save[data].$element );
+
inp_obj = undefined;
$( '#mw-content-text' ).prepend( textInput[data].$element );
+
} else if ( sqitch === "remove from record" ) {
 +
inp_obj = record;
 +
} else {
 +
// any other method ...... would quit as record was not even foun
 +
return undefined;
 +
}
 +
}
 +
}
 +
}
  
});
+
if ( sqitch === "update record" ) { // Record Update
 +
if ( control_1 != undefined ) {
 +
if ( control_2 != undefined ) {
 +
if ( control_3 != undefined ) {
  
}
+
// Mode 'update record' field1 field2 field3
});
+
// retrieve record, make record.field1.field2 = field3, push back
});
 
};
 
  
function clear_render() {
+
if ( inp_obj[ control_1 ] == undefined ) { inp_obj[ control_1 ] = (isNaN( control_2 )) ? {} : [] };
var ifel = document.getElementsByClassName('pduimark');
+
inp_obj[ control_1 ][ control_2 ] = control_3; // => "Bob"
while( ifel[0] ) {
+
store.put(inp_obj);
ifel[0].parentNode.removeChild(ifel[0]);
+
} else {
};
 
};
 
 
function notify_porting() {
 
  
mw.loader.using( 'oojs-ui-core' ).done( function () {
+
// Mode 'update record' field1 field2
 +
// retrieve record, make record.field1 = field2, push back
  
var button_import = new OO.ui.SelectFileWidget( { label: "Import Personal Data", classes: [ 'pduimark' ] } ),
+
inp_obj[ control_1 ] = inp_obj[ control_1 ] || {};
 +
if ( Array.isArray( inp_obj[ control_1 ] ) ) {
 +
inp_obj[ control_1 ].push( control_2 );
 +
} else {
 +
inp_obj[ control_1 ] = control_2;
 +
}
 +
store.put(inp_obj);
  
button_export = new OO.ui.ButtonWidget( { label: "Export Personal Data", classes: [ 'pduimark' ] } );
+
}
 +
} else {
 +
// Only one control defined. We will return that part of the record, and change nothing
 +
inp_obj = inp_obj[ control_1 ];
 +
}
 +
} else {
  
button_import.on( 'change', function (event) {
+
// There's not even control_1, we will just return the record itself
  
if (window.webkitURL != null) {
+
}
console.log("loading file on webkit")
+
debuglog("return 3")
} else {
+
// return inp_obj;
console.log("loading file non webkit")
 
}
 
  
console.log( event );
+
};
  
});
+
if ( sqitch === "remove from record" ) { // Record Update
 +
if ( control_1 != undefined ) {
 +
if ( control_2 != undefined ) {
  
button_export.on( 'click', function () {
+
// Mode 'remove from record' field1 field2
 +
// retrieve record, splice/delete record.field1.field2, push back
 +
 +
if ( Array.isArray( inp_obj[ control_1 ] ) && !isNaN( control_2 ) ) {
 +
inp_obj[ control_1 ].splice( control_2, 1 );
 +
} else {
 +
delete inp_obj[ control_1 ][ control_2 ];
 +
}
  
var get_record = indexedDBobject.EnqueuePDIO('get record');
+
} else {
  
get_record.then( function(result) {
+
// Mode 'remove from record' field1
console.log("happening");
+
// retrieve record, splice/delete record.field1, push back
if ( result === undefined ) { window.alert("No local data to export") } else {
 
  
var filename_tosaveas = "pdiolocalexport_" + result.name.user + "_" + Date.now() + '.json';
+
delete inp_obj[ control_1 ];
+
}
result.id = '';
+
}
result.name.user = '';
+
debuglog("putting object to store:");
+
debuglog(inp_obj);
var export_to_text = JSON.stringify(result);
+
store.put(inp_obj);
var textFileAsBlob = new Blob([export_to_text], {type:'text/plain'});
 
var downloadLink = document.createElement("a");
 
downloadLink.download = filename_tosaveas;
 
if (window.webkitURL != null)
 
{
 
// Chrome allows the link to be clicked
 
// without actually adding it to the DOM.
 
downloadLink.href = window.webkitURL.createObjectURL(textFileAsBlob);
 
}
 
else
 
{
 
// Firefox requires the link to be added to the DOM
 
// before it can be clicked.
 
downloadLink.href = window.URL.createObjectURL(textFileAsBlob);
 
downloadLink.style.display = "none";
 
document.body.appendChild(downloadLink);
 
 
}
 
}
  
downloadLink.click();
+
// We are past all methodic code, Async return
 +
 +
resolve( inp_obj );
  
}
+
};
 
+
});
+
// Indexeddb internal mechanism, close db
 +
 +
tx.oncomplete = function() {
 +
debuglog("return 1")
 +
db.close();
 +
};
 +
};
 +
debuglog("return 0")
  
 +
// End of promise
 +
 +
}).then( function(result) {
 +
debuglog("Mark 3");
 +
debuglog(result);
 +
return result;
 
});
 
});
 +
}
  
mw.notify( $( button_import.$element ), { autoHide: false }  );
+
function debuglog( l0g ){
mw.notify( $( button_export.$element ), { autoHide: false }  );
+
if ( Debug_switch ) {
});
+
console.log( l0g );
 +
}
 
}
 
}
 
+
};
}
 

Revision as of 17:53, 27 May 2019

// License: GPL
// Indexeddb JavaScript 
console.log('Indb loading');

function IndexedDBContainer( ww	) {

	// suppress console.log, switch to true for debug
	var Debug_switch = false;
	// seemingly the Safari engine uses a somewhat different implementation of arguments for db.transaction and index.get
	// because no browser on any mac / ios is allowed to use a different engine, rather than user agent, platform can be tested
	var iOS = !!navigator.platform && /Macintosh|MacIntel|MacPPC|Mac68K|Mac|iPad|iPhone|iPod/.test(navigator.platform);

	// deterministic hash generator for creating user id#s from usernames, should be replaced by something more functional (guaranteed to be transitively different)
	hashCode = function(s) {
		if ( s == undefined) {
			s = '00'
		}
		var h = 0, l = s.length, i = 0;
		if ( l > 0 )
			while (i < l)
				h = (h << 5) - h + s.charCodeAt(i++) | 0;
			return h;
		};

	
		if ( ww.user != undefined ) {
// if user exists in object passed to constructor, get username		
			var self = ww.user.getName();
// create userid by hashing username
			var idgen = hashCode(self);																										
			debuglog(idgen);

// Take username for standard databasename format:
// Our current convention for personal databasename (separate dbname for each user)
			var PersonalDataDataBaseName = "PD.IO" + idgen + "Database";
			// Use username in user to get up to date version of self
// Blank object for personal data Record with id and username included
			this.CurrentPerson = {id: idgen, name: {user: self}, age: 0};
			debuglog(PersonalDataDataBaseName);
			debuglog(this.CurrentPerson);
		}

	// ADD MORE DOC

	// EnqueuePDIO a PDIOLocalDatabase:User database access function with modes
	// Externally accessible wrapper for Enquire with current user / db preloaded

	this.EnqueuePDIO = async function( sw, c1, c2, c3 ) {
		debuglog("enquing pdio db " + PersonalDataDataBaseName + " with user " + JSON.stringify(this.CurrentPerson));
		var k = await Enquire( PersonalDataDataBaseName, this.CurrentPerson, sw, c1, c2, c3 );
		return k;
	}

	// Enquire, a general purpose indexeddb update function with modes

	// Mode 'aggressive push':
	// parameter record is pushed to database, overwriting whatever is there

	// Mode 'checkin':
	// record is retrieved from database, parameter record is pushed if not found

	// Mode 'get record'
	// updates record given as argument

	// Mode 'update record' field1 field2 field3
	// retrieve record, make record.field1.field2 = field3, push back

	// Mode 'update record' field1 field2
	// retrieve record, make record.field1 = field2, push back

	// Mode 'remove from record' field1 
	// retrieve record, splice/delete record.field1, push back
	
	// Mode 'remove from record' field1 field2 
	// retrieve record, splice/delete record.field1.field2, push back

	async function Enquire( pddbname, record, sqitch, control_1, control_2, control_3 ) {
		return new Promise(
			function(resolve, reject) {

				var pddb = window.indexedDB.open(pddbname, 3);
				var inp_obj;

				pddb.onerror = function() {  
				    console.log("Warning: Access to IndexedDB for application has been rejected.");
				};

				// indexeddb internal mechanism for version tracking of schema
				pddb.onupgradeneeded = function() {
					console.log('upgrading database');
					var db = pddb.result;
					var store = db.createObjectStore(pddbname, {keyPath: "id"});
					var index = store.createIndex("NameIndex", ["id"]);
						
				};
				// Database successfully opened function.
				pddb.onsuccess = function() {
					debuglog(pddb.result);
					var db = pddb.result;
					var tx;
				
					if ( iOS ) {
						tx = db.transaction([pddbname], "readwrite");
					} else {
						tx = db.transaction(pddbname, "readwrite");
					}
				
					var store = tx.objectStore(pddbname);
					var index = store.index("NameIndex");	

					// Get the original record from the db...
					var getRecord;
					if ( iOS ) {
					 	getRecord = index.get(record.id);
					} else {
						getRecord = index.get([record.id]);
					}

					getRecord.onsuccess = function() {
							debuglog( "Record in database: \n <<");
							debuglog( getRecord.result );
							debuglog( ">>");
						// Essential recordAdd																													
						if ( sqitch === "aggressive push" ) {
	// Mode 'aggressive push':
	// parameter record is pushed to database, overwriting whatever is there
							inp_obj = record;
							store.put( inp_obj );
							return inp_obj;
						} else if ( sqitch === "checkin" ) {
	// Mode 'checkin':
	// record is retrieved from database, parameter record is pushed if not found
							if ( getRecord.result != undefined ) {
								debuglog("getting from store");
								inp_obj = getRecord.result;
							} else {
								debuglog("overriding whatever in there");
								inp_obj = record;
								store.put(inp_obj);
							}

						} else {
	// Methods manipulationg the actual record come here
	// Update & Remove from
							// If we got the record from store, use that version to be manipulated
							if ( getRecord.result !== undefined ) {		
								inp_obj = getRecord.result;
								
								if ( sqitch === "get record" ) {
									
									debuglog("Getting record");
									// we will return later
									
								} 

								// endof onsuccess
							} else {

								debuglog("Record not found");

								if ( sqitch === "update record" ) {
									// Update record will use supplied copy to update it
									inp_obj = record;
								} else {
									if ( sqitch === "get record" ) {
										inp_obj = undefined;
									} else if ( sqitch === "remove from record" ) {
										inp_obj = record;
									} else {
										// any other method ...... would quit as record was not even foun
										return undefined;
									}
								}
							}
						} 

						if ( sqitch === "update record" ) { // Record Update															
							if ( control_1 != undefined ) {
								if ( control_2 != undefined ) {
									if ( control_3 != undefined ) {

	// Mode 'update record' field1 field2 field3
	// retrieve record, make record.field1.field2 = field3, push back

										if ( inp_obj[ control_1 ] == undefined ) { inp_obj[ control_1 ] = (isNaN( control_2 )) ? {} : [] };
										inp_obj[ control_1 ][ control_2 ] = control_3;	// => "Bob"
										store.put(inp_obj);
									} else {

	// Mode 'update record' field1 field2
	// retrieve record, make record.field1 = field2, push back

										inp_obj[ control_1 ] = inp_obj[ control_1 ] || {};
										if ( Array.isArray( inp_obj[ control_1 ] ) ) {
											inp_obj[ control_1 ].push( control_2 );
										} else {
											inp_obj[ control_1 ] = control_2;
										}
										store.put(inp_obj);

									}
								} else {
									// Only one control defined. We will return that part of the record, and change nothing
									inp_obj = inp_obj[ control_1 ];
								}
							} else {

								// There's not even control_1, we will just return the record itself

							}
							debuglog("return 3")
							// return inp_obj;

						};

						if ( sqitch === "remove from record" ) { // Record Update															
							if ( control_1 != undefined ) {
								if ( control_2 != undefined ) {

	// Mode 'remove from record' field1 field2 
	// retrieve record, splice/delete record.field1.field2, push back
									
									if ( Array.isArray( inp_obj[ control_1 ] ) && !isNaN( control_2 ) ) {
										inp_obj[ control_1 ].splice( control_2, 1 );
									} else {
										delete inp_obj[ control_1 ][ control_2 ];
									}

								} else {

	// Mode 'remove from record' field1 
	// retrieve record, splice/delete record.field1, push back

									delete inp_obj[ control_1 ];
								}
							}
							debuglog("putting object to store:");
							debuglog(inp_obj);
							store.put(inp_obj);
						}

	// We are past all methodic code, Async return						
	
						resolve( inp_obj );

					};
	
	// Indexeddb internal mechanism, close db
	
					tx.oncomplete = function() {
						debuglog("return 1")
						db.close();
					};
				};
				debuglog("return 0")

	// End of promise
	
			}).then( function(result) {
				debuglog("Mark 3");
				debuglog(result);
				return result;
			});
	}

	function debuglog( l0g ){
		if ( Debug_switch ) {
			console.log( l0g );
		}
	}
};