apppreferences.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  1. var platform = {};
  2. if (typeof AppPreferencesLocalStorage === "undefined") {
  3. try {
  4. platform = require ('./platform');
  5. } catch (e) {
  6. }
  7. } else {
  8. platform = new AppPreferencesLocalStorage ();
  9. }
  10. /**
  11. * @constructor
  12. */
  13. function AppPreferences (defaultArgs) {
  14. this.defaultArgs = defaultArgs || {};
  15. }
  16. var promiseLib;
  17. if (typeof Promise !== "undefined") {
  18. promiseLib = Promise;
  19. } else if (typeof WinJS !== "undefined" && WinJS.Promise) {
  20. promiseLib = WinJS.Promise;
  21. } else if (typeof $ !== "undefined" && $.Deferred) {
  22. promiseLib = function (init) {
  23. var d = $.Deferred ();
  24. init (d.resolve.bind (d), d.reject.bind (d));
  25. return d.promise ();
  26. }
  27. }
  28. function promiseCheck (maxArgs, successCallback, errorCallback) {
  29. if (
  30. typeof successCallback !== 'function' && typeof errorCallback !== 'function'
  31. && arguments.length <= maxArgs + 1 // argCount
  32. && promiseLib
  33. ) {
  34. return true;
  35. } else {
  36. return false;
  37. }
  38. }
  39. if (!platform.nativeExec && typeof cordova !== "undefined")
  40. platform.nativeExec = cordova.exec.bind (cordova);
  41. AppPreferences.prototype.prepareKey = platform.prepareKey || function (mode, dict, key, value) {
  42. var args = {};
  43. for (var k in this.defaultArgs) {
  44. args[k] = this.defaultArgs[k];
  45. }
  46. var argList = [].slice.apply(arguments);
  47. argList.shift();
  48. if (
  49. (mode == 'get' && argList.length == 1) ||
  50. (mode == 'get' && argList.length == 2 && argList[1] == null) ||
  51. (mode == 'set' && argList.length == 2) ||
  52. (mode == 'set' && argList.length == 3 && argList[2] == null)
  53. ) {
  54. argList.unshift (undefined);
  55. }
  56. args.key = argList[1];
  57. if (argList[0] !== undefined)
  58. args.dict = argList[0]
  59. if (mode == 'set')
  60. args.value = argList[2];
  61. // console.log (JSON.stringify (argList), JSON.stringify (args));
  62. return args;
  63. }
  64. /**
  65. * Get a preference value
  66. *
  67. * @param {Function} successCallback The function to call when the value is available
  68. * @param {Function} errorCallback The function to call when value is unavailable
  69. * @param {String} dict Dictionary for key (OPTIONAL)
  70. * @param {String} key Key
  71. */
  72. AppPreferences.prototype.fetch = platform.fetch || function (
  73. successCallback, errorCallback, dict, key
  74. ) {
  75. var argCount = 2; // dict, key
  76. var promise = promiseCheck.apply (this, [argCount].concat ([].slice.call(arguments)));
  77. // for promises
  78. if (promise) {
  79. dict = successCallback;
  80. key = errorCallback;
  81. }
  82. var args = this.prepareKey ('get', dict, key);
  83. var _successCallback = function (_value) {
  84. var value = _value;
  85. try {
  86. value = JSON.parse (_value);
  87. } catch (e) {
  88. }
  89. successCallback (value);
  90. }
  91. var nativeExec = function (resolve, reject) {
  92. if (!args.key) {
  93. return reject ();
  94. }
  95. if (resolve !== successCallback) {
  96. successCallback = resolve;
  97. }
  98. if (platform.nativeFetch) {
  99. return platform.nativeFetch(_successCallback, reject, args);
  100. }
  101. return platform.nativeExec(_successCallback, reject, "AppPreferences", "fetch", [args]);
  102. }
  103. if (promise) {
  104. return new promiseLib (nativeExec);
  105. } else {
  106. nativeExec (successCallback, errorCallback);
  107. }
  108. };
  109. /**
  110. * Set a preference value
  111. *
  112. * @param {Function} successCallback The function to call when the value is set successfully
  113. * @param {Function} errorCallback The function to call when value is not set
  114. * @param {String} dict Dictionary for key (OPTIONAL)
  115. * @param {String} key Key
  116. * @param {String} value Value
  117. */
  118. AppPreferences.prototype.store = platform.store || function (
  119. successCallback, errorCallback, dict, key, value
  120. ) {
  121. var argCount = 3; // dict, key, value
  122. var promise = promiseCheck.apply (this, [argCount].concat ([].slice.call(arguments)));
  123. // for promises
  124. if (promise) {
  125. value = dict;
  126. key = errorCallback;
  127. dict = successCallback;
  128. }
  129. var args = this.prepareKey ('set', dict, key, value);
  130. args.type = typeof args.value;
  131. // VERY IMPORTANT THING
  132. // WP platform has some limitations, so we need to encode all values to JSON.
  133. // On plugin side we store value according to it's type.
  134. // So, every platform plugin must check for type, decode JSON and store
  135. // value decoded for basic types.
  136. // TODO: don't think about array of strings, it's android only.
  137. // Complex structures must be stored as JSON string.
  138. // On iOS strings stored as strings and JSON stored as NSData
  139. // Android:
  140. // Now, interesting thing: how to differentiate between string value
  141. // and complex value, encoded as json and stored as string?
  142. // I'm introduce setting named _<preference>_type with value "JSON"
  143. // Windows Phone ?
  144. args.value = JSON.stringify (args.value);
  145. var nativeExec = function (resolve, reject) {
  146. if (!args.key || args.value === null || args.value === undefined) {
  147. return reject ();
  148. }
  149. if (platform.nativeStore) {
  150. return platform.nativeStore (resolve, reject, args);
  151. }
  152. return platform.nativeExec (resolve, reject, "AppPreferences", "store", [args]);
  153. }
  154. if (promise) {
  155. return new promiseLib (nativeExec);
  156. } else {
  157. nativeExec (successCallback, errorCallback);
  158. }
  159. };
  160. /**
  161. * Remove value from preferences
  162. *
  163. * @param {Function} successCallback The function to call when the value is available
  164. * @param {Function} errorCallback The function to call when value is unavailable
  165. * @param {String} dict Dictionary for key (OPTIONAL)
  166. * @param {String} key Key
  167. */
  168. AppPreferences.prototype.remove = platform.remove || function (
  169. successCallback, errorCallback, dict, key
  170. ) {
  171. var argCount = 2; // dict, key
  172. var promise = promiseCheck.apply (this, [argCount].concat ([].slice.call(arguments)));
  173. // for promises
  174. if (promise) {
  175. key = errorCallback;
  176. dict = successCallback;
  177. }
  178. var args = this.prepareKey ('get', dict, key);
  179. var nativeExec = function (resolve, reject) {
  180. if (!args.key) {
  181. return reject ();
  182. }
  183. if (platform.nativeRemove) {
  184. return platform.nativeRemove (resolve, reject, args);
  185. }
  186. return platform.nativeExec (resolve, reject, "AppPreferences", "remove", [args]);
  187. }
  188. if (promise) {
  189. return new promiseLib (nativeExec);
  190. } else {
  191. nativeExec (successCallback, errorCallback);
  192. }
  193. };
  194. /**
  195. * Clear preferences
  196. *
  197. * @param {Function} successCallback The function to call when the value is available
  198. * @param {Function} errorCallback The function to call when value is unavailable
  199. * @param {String} dict Dictionary for key (OPTIONAL)
  200. * @param {String} key Key
  201. */
  202. AppPreferences.prototype.clearAll = platform.clearAll || function (
  203. successCallback, errorCallback
  204. ) {
  205. var argCount = 0;
  206. var promise = promiseCheck.apply (this, [argCount].concat ([].slice.call(arguments)));
  207. var args = {};
  208. for (var k in this.defaultArgs) {
  209. args[k] = this.defaultArgs[k];
  210. }
  211. var nativeExec = function (resolve, reject) {
  212. if (platform.nativeClearAll) {
  213. return platform.nativeClearAll (resolve, reject, args);
  214. }
  215. return platform.nativeExec (resolve, reject, "AppPreferences", "clearAll", [args]);
  216. }
  217. if (promise) {
  218. return new promiseLib (nativeExec);
  219. } else {
  220. nativeExec (successCallback, errorCallback);
  221. }
  222. };
  223. /**
  224. * Show native preferences interface
  225. *
  226. * @param {Function} successCallback The function to call when the value is available
  227. * @param {Function} errorCallback The function to call when value is unavailable
  228. * @param {String} dict Dictionary for key (OPTIONAL)
  229. * @param {String} key Key
  230. */
  231. AppPreferences.prototype.show = platform.show || function (
  232. successCallback, errorCallback
  233. ) {
  234. var argCount = 0;
  235. var promise = promiseCheck.apply (this, [argCount].concat ([].slice.call(arguments)));
  236. var nativeExec = function (resolve, reject) {
  237. return platform.nativeExec (resolve, reject, "AppPreferences", "show", []);
  238. }
  239. if (promise) {
  240. return new promiseLib (nativeExec);
  241. } else {
  242. nativeExec (successCallback, errorCallback);
  243. }
  244. };
  245. /**
  246. * Watch for preferences change
  247. *
  248. * @param {Function} successCallback The function to call when the value is available
  249. * @param {Function} errorCallback The function to call when value is unavailable
  250. * @param {Boolean} subscribe true value to subscribe, false - unsubscribe
  251. * @example How to get notified:
  252. * ```javascript
  253. * plugins.appPreferences.watch();
  254. * document.addEventListener ('preferencesChanged', function (evt) {
  255. * // with some platforms can give you details what is changed
  256. * if (evt.key) {
  257. * // handle key change
  258. * } else if (evt.all) {
  259. * // after clearAll
  260. * }
  261. * });
  262. * ```
  263. */
  264. AppPreferences.prototype.watch = platform.watch || function (
  265. successCallback, errorCallback, subscribe
  266. ) {
  267. if (typeof subscribe === "undefined") {
  268. subscribe = true;
  269. }
  270. var args = {};
  271. for (var k in this.defaultArgs) {
  272. args[k] = this.defaultArgs[k];
  273. }
  274. args.subscribe = subscribe;
  275. var nativeExec = function (resolve, reject) {
  276. if (platform.nativeWatch) {
  277. return platform.nativeWatch (resolve, reject, args);
  278. }
  279. return platform.nativeExec (resolve, reject, "AppPreferences", "watch", [args]);
  280. }
  281. nativeExec (successCallback, errorCallback);
  282. };
  283. /**
  284. * Return named configuration context
  285. * In iOS you'll get a suite configuration, on Android — named file
  286. * Supports: Android, iOS
  287. * @param {String} suiteName suite name
  288. * @returns {AppPreferences} AppPreferences object, bound to that suite
  289. */
  290. AppPreferences.prototype.iosSuite =
  291. AppPreferences.prototype.suite =
  292. function (suiteName) {
  293. var appPrefs = new AppPreferences ({
  294. iosSuiteName: suiteName, // deprecated, remove when ios code is ready
  295. suiteName: suiteName,
  296. });
  297. return appPrefs;
  298. }
  299. /**
  300. * Return cloud synchronized configuration context
  301. * Currently supports Windows and iOS/macOS
  302. * @returns {AppPreferences} AppPreferences object, bound to that suite
  303. */
  304. AppPreferences.prototype.cloudSync = function () {
  305. var appPrefs = new AppPreferences ({cloudSync: true});
  306. return appPrefs;
  307. }
  308. /**
  309. * Return default configuration context
  310. * Currently supports Windows and iOS/macOS
  311. * @returns {AppPreferences} AppPreferences object, bound to that suite
  312. */
  313. AppPreferences.prototype.defaults = function () {
  314. var appPrefs = new AppPreferences ();
  315. return appPrefs;
  316. }
  317. // WIP: functions to bind selected preferences to the form
  318. function setFormFields (formEl, fieldsData) {
  319. for (var i = 0; i < formEl.elements.length; i ++) {
  320. var formField = formEl.elements[i];
  321. if (!(formField.name in fieldsData)) {
  322. continue;
  323. }
  324. // TODO: multiple checkboxes value for one form field
  325. if (formField.type === 'radio' || formField.type === 'checkbox') {
  326. if (
  327. formField.value === fieldsData[formField.name]
  328. || formField.value === fieldsData[formField.name].toString()
  329. ) {
  330. formField.checked = true;
  331. }
  332. } else {
  333. formField.value = fieldsData[formField.name];
  334. }
  335. }
  336. }
  337. function bindFormToData (formEl, formData) {
  338. [].slice.apply (formEl.elements).forEach (function (el) {
  339. if (el.type.match (/^(?:radio|checkbox)$/)) {
  340. el.addEventListener ('change', getFormFields.bind (window, formEl, formData), false);
  341. } else {
  342. el.addEventListener ('input', getFormFields.bind (window, formEl, formData), false);
  343. }
  344. });
  345. }
  346. function getFormFields (formEl, formData) {
  347. formData = formData || {};
  348. for (var k in formData) {
  349. delete formData[k];
  350. }
  351. for (var i = 0; i < formEl.elements.length; i ++) {
  352. var formField = formEl.elements[i];
  353. var checkedType = formField.type.match (/^(?:radio|checkbox)$/);
  354. if ((checkedType && formField.checked) || !checkedType) {
  355. formData[formField.name] = formField.value;
  356. }
  357. }
  358. // console.log (formData);
  359. return formData;
  360. }
  361. if (typeof module !== "undefined") {
  362. module.exports = new AppPreferences();
  363. }