1 // ========================================================================== 2 // Project: SproutCore - JavaScript Application Framework 3 // Copyright: ©2006-2011 Strobe Inc. and contributors. 4 // Portions ©2008-2011 Apple Inc. All rights reserved. 5 // License: Licensed under MIT license (see license.js) 6 // ========================================================================== 7 /*globals ie7userdata openDatabase*/ 8 /** 9 @class 10 11 The UserDefaults object provides an easy way to store user preferences in 12 your application on the local machine. You use this by providing built-in 13 defaults using the SC.userDefaults.defaults() method. You can also 14 implement the UserDefaultsDelegate interface to be notified whenever a 15 default is required. 16 17 You should also set the userDomain property on the defaults on page load. 18 This will allow the UserDefaults application to store/fetch keys from 19 localStorage for the correct user. 20 21 You can also set an appDomain property if you want. This will be 22 automatically prepended to key names with no slashes in them. 23 24 SC.userDefaults.getPath("global:contactInfo.userName"); 25 26 @extends SC.Object 27 @since SproutCore 1.0 28 */ 29 SC.UserDefaults = SC.Object.extend(/** @scope SC.UserDefaults.prototype */ { 30 31 ready: NO, 32 33 /** 34 the default domain for the user. This will be used to store keys in 35 local storage. If you do not set this property, the wrong values may be 36 returned. 37 */ 38 userDomain: null, 39 40 /** 41 The default app domain for the user. Any keys that do not include a 42 slash will be prefixed with this app domain key when getting/setting. 43 */ 44 appDomain: null, 45 46 /** @private 47 Defaults. These will be used if not defined on localStorage. 48 */ 49 _defaults: null, 50 51 _safari3DB: null, 52 53 /** 54 Invoke this method to set the builtin defaults. This will cause all 55 properties to change. 56 */ 57 defaults: function(newDefaults) { 58 this._defaults = newDefaults ; 59 this.allPropertiesDidChange(); 60 }, 61 62 /** 63 Attempts to read a user default from local storage. If not found on 64 localStorage, use the the local defaults, if defined. If the key passed 65 does not include a slash, then add the appDomain or use "app/". 66 67 @param {String} keyName 68 @returns {Object} read value 69 */ 70 readDefault: function(keyName) { 71 // Note: different implementations of localStorage may return 'null' or 72 // may return 'undefined' for missing properties so use SC.none() to check 73 // for the existence of ret throughout this function. 74 var isIE7, ret, userKeyName, localStorage, key, del, storageSafari3; 75 76 // namespace keyname 77 keyName = this._normalizeKeyName(keyName); 78 userKeyName = this._userKeyName(keyName); 79 80 // look into recently written values 81 if (this._written) { ret = this._written[userKeyName]; } 82 83 // attempt to read from localStorage 84 isIE7 = SC.browser.isIE && 85 SC.browser.compare(SC.browser.version, '7') === 0; 86 87 if(isIE7) { 88 localStorage=document.body; 89 try{ 90 localStorage.load("SC.UserDefaults"); 91 }catch(e){ 92 SC.Logger.error("Couldn't load userDefaults in IE7: "+e.description); 93 } 94 }else if(this.HTML5DB_noLocalStorage){ 95 storageSafari3 = this._safari3DB; 96 }else{ 97 localStorage = window.localStorage ; 98 if (!localStorage && window.globalStorage) { 99 localStorage = window.globalStorage[window.location.hostname]; 100 } 101 } 102 if (localStorage || storageSafari3) { 103 key=["SC.UserDefaults",userKeyName].join('-at-'); 104 if(isIE7) { 105 ret=localStorage.getAttribute(key.replace(/\W/gi, '')); 106 } else if(storageSafari3) { 107 ret = this.dataHash[key]; 108 } else { 109 ret = localStorage[key]; 110 } 111 if (!SC.none(ret)) { 112 try { ret = SC.json.decode(ret); } 113 catch(ex) {} 114 } 115 } 116 117 // if not found in localStorage, try to notify delegate 118 del = this.delegate ; 119 if (del && del.userDefaultsNeedsDefault) { 120 ret = del.userDefaultsNeedsDefault(this, keyName, userKeyName); 121 } 122 123 // if not found in localStorage or delegate, try to find in defaults 124 if (SC.none(ret) && this._defaults) { 125 ret = this._defaults[userKeyName] || this._defaults[keyName]; 126 } 127 128 return ret ; 129 }, 130 131 /** 132 Attempts to write the user default to local storage or at least saves them 133 for now. Also notifies that the value has changed. 134 135 @param {String} keyName 136 @param {Object} value 137 @returns {SC.UserDefault} receiver 138 */ 139 writeDefault: function(keyName, value) { 140 var isIE7, userKeyName, written, localStorage, key, del, storageSafari3; 141 142 keyName = this._normalizeKeyName(keyName); 143 userKeyName = this._userKeyName(keyName); 144 145 // save to local hash 146 written = this._written ; 147 if (!written) { written = this._written = {}; } 148 written[userKeyName] = value ; 149 150 // save to local storage 151 isIE7 = SC.browser.isIE && 152 SC.browser.compare(SC.browser.version, '7') === 0; 153 154 if(isIE7){ 155 localStorage=document.body; 156 }else if(this.HTML5DB_noLocalStorage){ 157 storageSafari3 = this._safari3DB; 158 }else{ 159 localStorage = window.localStorage ; 160 if (!localStorage && window.globalStorage) { 161 localStorage = window.globalStorage[window.location.hostname]; 162 } 163 } 164 key=["SC.UserDefaults",userKeyName].join('-at-'); 165 if (localStorage || storageSafari3) { 166 var encodedValue = SC.json.encode(value); 167 if(isIE7){ 168 localStorage.setAttribute(key.replace(/\W/gi, ''), encodedValue); 169 localStorage.save("SC.UserDefaults"); 170 }else if(storageSafari3){ 171 var obj = this; 172 storageSafari3.transaction( 173 function (t) { 174 t.executeSql("delete from SCLocalStorage where key = ?", [key], 175 function (){ 176 t.executeSql("insert into SCLocalStorage(key, value)"+ 177 " VALUES ('"+key+"', '"+encodedValue+"');", 178 [], obj._nullDataHandler, obj.killTransaction 179 ); 180 } 181 ); 182 } 183 ); 184 this.dataHash[key] = encodedValue; 185 }else{ 186 try{ 187 localStorage[key] = encodedValue; 188 }catch(e){ 189 SC.Logger.error("Failed using localStorage. "+e); 190 } 191 } 192 } 193 194 // also notify delegate 195 del = this.delegate; 196 if (del && del.userDefaultsDidChange) { 197 del.userDefaultsDidChange(this, keyName, value, userKeyName); 198 } 199 200 return this ; 201 }, 202 203 /** 204 Removed the passed keyName from the written hash and local storage. 205 206 @param {String} keyName 207 @returns {SC.UserDefaults} receiver 208 */ 209 resetDefault: function(keyName) { 210 var fullKeyName, isIE7, userKeyName, written, localStorage, key, storageSafari3; 211 fullKeyName = this._normalizeKeyName(keyName); 212 userKeyName = this._userKeyName(fullKeyName); 213 214 this.propertyWillChange(keyName); 215 this.propertyWillChange(fullKeyName); 216 217 written = this._written; 218 if (written) delete written[userKeyName]; 219 220 isIE7 = SC.browser.isIE && 221 SC.browser.compare(SC.browser.version, '7') === 0; 222 223 if(isIE7){ 224 localStorage=document.body; 225 }else if(this.HTML5DB_noLocalStorage){ 226 storageSafari3 = this._safari3DB; 227 }else{ 228 localStorage = window.localStorage ; 229 if (!localStorage && window.globalStorage) { 230 localStorage = window.globalStorage[window.location.hostname]; 231 } 232 } 233 234 key=["SC.UserDefaults",userKeyName].join('-at-'); 235 236 if (localStorage) { 237 if(isIE7){ 238 localStorage.setAttribute(key.replace(/\W/gi, ''), null); 239 localStorage.save("SC.UserDefaults"); 240 } else if(storageSafari3){ 241 var obj = this; 242 storageSafari3.transaction( 243 function (t) { 244 t.executeSql("delete from SCLocalStorage where key = ?", [key], null); 245 } 246 ); 247 delete this.dataHash[key]; 248 }else{ 249 // In case error occurs while deleting local storage in any browser, 250 // do not allow it to propagate further 251 try{ 252 delete localStorage[key]; 253 } catch(e) { 254 SC.Logger.warn('Deleting local storage encountered a problem. '+e); 255 } 256 } 257 } 258 259 260 this.propertyDidChange(keyName); 261 this.propertyDidChange(fullKeyName); 262 return this ; 263 }, 264 265 /** 266 Is called whenever you .get() or .set() values on this object 267 268 @param {Object} key 269 @param {Object} value 270 @returns {Object} 271 */ 272 unknownProperty: function(key, value) { 273 if (value === undefined) { 274 return this.readDefault(key) ; 275 } else { 276 this.writeDefault(key, value); 277 return value ; 278 } 279 }, 280 281 /** 282 Normalize the passed key name. Used by all accessors to automatically 283 insert an appName if needed. 284 */ 285 _normalizeKeyName: function(keyName) { 286 if (keyName.indexOf(':')<0) { 287 var domain = this.get('appDomain') || 'app'; 288 keyName = [domain, keyName].join(':'); 289 } 290 return keyName; 291 }, 292 293 /** 294 Builds a user key name from the passed key name 295 */ 296 _userKeyName: function(keyName) { 297 var user = this.get('userDomain') || '(anonymous)' ; 298 return [user,keyName].join('-at-'); 299 }, 300 301 _domainDidChange: function() { 302 var didChange = NO; 303 if (this.get("userDomain") !== this._scud_userDomain) { 304 this._scud_userDomain = this.get('userDomain'); 305 didChange = YES; 306 } 307 308 if (this.get('appDomain') !== this._scud_appDomain) { 309 this._scud_appDomain = this.get('appDomain'); 310 didChange = YES; 311 } 312 313 if (didChange) this.allPropertiesDidChange(); 314 }.observes('userDomain', 'appDomain'), 315 316 init: function() { 317 sc_super(); 318 var isIE7; 319 320 // Increment the jQuery ready counter, so that SproutCore will 321 // defer loading the app until the user defaults are available. 322 jQuery.readyWait++; 323 324 if(SC.userDefaults && SC.userDefaults.get('dataHash')){ 325 var dh = SC.userDefaults.get('dataHash'); 326 if (dh) this.dataHash=SC.userDefaults.get('dataHash'); 327 } 328 this._scud_userDomain = this.get('userDomain'); 329 this._scud_appDomain = this.get('appDomain'); 330 331 isIE7 = SC.browser.isIE && 332 SC.browser.compare(SC.browser.version, '7') === 0; 333 334 if(isIE7){ 335 //Add user behavior userData. This works in all versions of IE. 336 //Adding to the body as is the only element never removed. 337 document.body.addBehavior('#default#userData'); 338 } 339 this.HTML5DB_noLocalStorage = SC.browser.isWebkit && 340 SC.browser.compare(SC.browser.engineVersion, '523')>0 && 341 SC.browser.compare(SC.browser.engineVersion, '528')<0; 342 if(this.HTML5DB_noLocalStorage){ 343 var myDB; 344 try { 345 if (!window.openDatabase) { 346 SC.Logger.error("Trying to load a database with safari version 3.1 "+ 347 "to get SC.UserDefaults to work. You are either in a"+ 348 " previous version or there is a problem with your browser."); 349 return; 350 } else { 351 var shortName = 'scdb', 352 version = '1.0', 353 displayName = 'SproutCore database', 354 maxSize = 65536; // in bytes, 355 myDB = openDatabase(shortName, version, displayName, maxSize); 356 357 // You should have a database instance in myDB. 358 359 } 360 } catch(e) { 361 SC.Logger.error("Trying to load a database with safari version 3.1 "+ 362 "to get SC.UserDefaults to work. You are either in a"+ 363 " previous version or there is a problem with your browser."); 364 return; 365 } 366 367 if(myDB){ 368 var obj = this; 369 myDB.transaction( 370 function (transaction) { 371 transaction.executeSql('CREATE TABLE IF NOT EXISTS SCLocalStorage'+ 372 '(key TEXT NOT NULL PRIMARY KEY, value TEXT NOT NULL);', 373 [], obj._nullDataHandler, obj.killTransaction); 374 } 375 ); 376 myDB.transaction( 377 function (transaction) { 378 379 transaction.parent = obj; 380 transaction.executeSql('SELECT * from SCLocalStorage;', 381 [], function(transaction, results){ 382 var hash={}, row; 383 for(var i=0, iLen=results.rows.length; i<iLen; i++){ 384 row=results.rows.item(i); 385 hash[row['key']]=row['value']; 386 } 387 transaction.parent.dataHash = hash; 388 SC.run(function() { jQuery.ready(true); }); 389 }, obj.killTransaction); 390 } 391 ); 392 this._safari3DB=myDB; 393 } 394 }else{ 395 jQuery.ready(true); 396 } 397 }, 398 399 400 //Private methods to use if user defaults uses the database in safari 3 401 _killTransaction: function(transaction, error){ 402 return true; // fatal transaction error 403 }, 404 405 _nullDataHandler: function(transaction, results){} 406 }); 407 408 /** global user defaults. */ 409 SC.userDefaults = SC.UserDefaults.create(); 410