var platform = {}; if (typeof AppPreferencesLocalStorage === "undefined") { try { platform = require ('./platform'); } catch (e) { } } else { platform = new AppPreferencesLocalStorage (); } /** * @constructor */ function AppPreferences (defaultArgs) { this.defaultArgs = defaultArgs || {}; } var promiseLib; if (typeof Promise !== "undefined") { promiseLib = Promise; } else if (typeof WinJS !== "undefined" && WinJS.Promise) { promiseLib = WinJS.Promise; } else if (typeof $ !== "undefined" && $.Deferred) { promiseLib = function (init) { var d = $.Deferred (); init (d.resolve.bind (d), d.reject.bind (d)); return d.promise (); } } function promiseCheck (maxArgs, successCallback, errorCallback) { if ( typeof successCallback !== 'function' && typeof errorCallback !== 'function' && arguments.length <= maxArgs + 1 // argCount && promiseLib ) { return true; } else { return false; } } if (!platform.nativeExec && typeof cordova !== "undefined") platform.nativeExec = cordova.exec.bind (cordova); AppPreferences.prototype.prepareKey = platform.prepareKey || function (mode, dict, key, value) { var args = {}; for (var k in this.defaultArgs) { args[k] = this.defaultArgs[k]; } var argList = [].slice.apply(arguments); argList.shift(); if ( (mode == 'get' && argList.length == 1) || (mode == 'get' && argList.length == 2 && argList[1] == null) || (mode == 'set' && argList.length == 2) || (mode == 'set' && argList.length == 3 && argList[2] == null) ) { argList.unshift (undefined); } args.key = argList[1]; if (argList[0] !== undefined) args.dict = argList[0] if (mode == 'set') args.value = argList[2]; // console.log (JSON.stringify (argList), JSON.stringify (args)); return args; } /** * Get a preference value * * @param {Function} successCallback The function to call when the value is available * @param {Function} errorCallback The function to call when value is unavailable * @param {String} dict Dictionary for key (OPTIONAL) * @param {String} key Key */ AppPreferences.prototype.fetch = platform.fetch || function ( successCallback, errorCallback, dict, key ) { var argCount = 2; // dict, key var promise = promiseCheck.apply (this, [argCount].concat ([].slice.call(arguments))); // for promises if (promise) { dict = successCallback; key = errorCallback; } var args = this.prepareKey ('get', dict, key); var _successCallback = function (_value) { var value = _value; try { value = JSON.parse (_value); } catch (e) { } successCallback (value); } var nativeExec = function (resolve, reject) { if (!args.key) { return reject (); } if (resolve !== successCallback) { successCallback = resolve; } if (platform.nativeFetch) { return platform.nativeFetch(_successCallback, reject, args); } return platform.nativeExec(_successCallback, reject, "AppPreferences", "fetch", [args]); } if (promise) { return new promiseLib (nativeExec); } else { nativeExec (successCallback, errorCallback); } }; /** * Set a preference value * * @param {Function} successCallback The function to call when the value is set successfully * @param {Function} errorCallback The function to call when value is not set * @param {String} dict Dictionary for key (OPTIONAL) * @param {String} key Key * @param {String} value Value */ AppPreferences.prototype.store = platform.store || function ( successCallback, errorCallback, dict, key, value ) { var argCount = 3; // dict, key, value var promise = promiseCheck.apply (this, [argCount].concat ([].slice.call(arguments))); // for promises if (promise) { value = dict; key = errorCallback; dict = successCallback; } var args = this.prepareKey ('set', dict, key, value); args.type = typeof args.value; // VERY IMPORTANT THING // WP platform has some limitations, so we need to encode all values to JSON. // On plugin side we store value according to it's type. // So, every platform plugin must check for type, decode JSON and store // value decoded for basic types. // TODO: don't think about array of strings, it's android only. // Complex structures must be stored as JSON string. // On iOS strings stored as strings and JSON stored as NSData // Android: // Now, interesting thing: how to differentiate between string value // and complex value, encoded as json and stored as string? // I'm introduce setting named __type with value "JSON" // Windows Phone ? args.value = JSON.stringify (args.value); var nativeExec = function (resolve, reject) { if (!args.key || args.value === null || args.value === undefined) { return reject (); } if (platform.nativeStore) { return platform.nativeStore (resolve, reject, args); } return platform.nativeExec (resolve, reject, "AppPreferences", "store", [args]); } if (promise) { return new promiseLib (nativeExec); } else { nativeExec (successCallback, errorCallback); } }; /** * Remove value from preferences * * @param {Function} successCallback The function to call when the value is available * @param {Function} errorCallback The function to call when value is unavailable * @param {String} dict Dictionary for key (OPTIONAL) * @param {String} key Key */ AppPreferences.prototype.remove = platform.remove || function ( successCallback, errorCallback, dict, key ) { var argCount = 2; // dict, key var promise = promiseCheck.apply (this, [argCount].concat ([].slice.call(arguments))); // for promises if (promise) { key = errorCallback; dict = successCallback; } var args = this.prepareKey ('get', dict, key); var nativeExec = function (resolve, reject) { if (!args.key) { return reject (); } if (platform.nativeRemove) { return platform.nativeRemove (resolve, reject, args); } return platform.nativeExec (resolve, reject, "AppPreferences", "remove", [args]); } if (promise) { return new promiseLib (nativeExec); } else { nativeExec (successCallback, errorCallback); } }; /** * Clear preferences * * @param {Function} successCallback The function to call when the value is available * @param {Function} errorCallback The function to call when value is unavailable * @param {String} dict Dictionary for key (OPTIONAL) * @param {String} key Key */ AppPreferences.prototype.clearAll = platform.clearAll || function ( successCallback, errorCallback ) { var argCount = 0; var promise = promiseCheck.apply (this, [argCount].concat ([].slice.call(arguments))); var args = {}; for (var k in this.defaultArgs) { args[k] = this.defaultArgs[k]; } var nativeExec = function (resolve, reject) { if (platform.nativeClearAll) { return platform.nativeClearAll (resolve, reject, args); } return platform.nativeExec (resolve, reject, "AppPreferences", "clearAll", [args]); } if (promise) { return new promiseLib (nativeExec); } else { nativeExec (successCallback, errorCallback); } }; /** * Show native preferences interface * * @param {Function} successCallback The function to call when the value is available * @param {Function} errorCallback The function to call when value is unavailable * @param {String} dict Dictionary for key (OPTIONAL) * @param {String} key Key */ AppPreferences.prototype.show = platform.show || function ( successCallback, errorCallback ) { var argCount = 0; var promise = promiseCheck.apply (this, [argCount].concat ([].slice.call(arguments))); var nativeExec = function (resolve, reject) { return platform.nativeExec (resolve, reject, "AppPreferences", "show", []); } if (promise) { return new promiseLib (nativeExec); } else { nativeExec (successCallback, errorCallback); } }; /** * Watch for preferences change * * @param {Function} successCallback The function to call when the value is available * @param {Function} errorCallback The function to call when value is unavailable * @param {Boolean} subscribe true value to subscribe, false - unsubscribe * @example How to get notified: * ```javascript * plugins.appPreferences.watch(); * document.addEventListener ('preferencesChanged', function (evt) { * // with some platforms can give you details what is changed * if (evt.key) { * // handle key change * } else if (evt.all) { * // after clearAll * } * }); * ``` */ AppPreferences.prototype.watch = platform.watch || function ( successCallback, errorCallback, subscribe ) { if (typeof subscribe === "undefined") { subscribe = true; } var args = {}; for (var k in this.defaultArgs) { args[k] = this.defaultArgs[k]; } args.subscribe = subscribe; var nativeExec = function (resolve, reject) { if (platform.nativeWatch) { return platform.nativeWatch (resolve, reject, args); } return platform.nativeExec (resolve, reject, "AppPreferences", "watch", [args]); } nativeExec (successCallback, errorCallback); }; /** * Return named configuration context * In iOS you'll get a suite configuration, on Android — named file * Supports: Android, iOS * @param {String} suiteName suite name * @returns {AppPreferences} AppPreferences object, bound to that suite */ AppPreferences.prototype.iosSuite = AppPreferences.prototype.suite = function (suiteName) { var appPrefs = new AppPreferences ({ iosSuiteName: suiteName, // deprecated, remove when ios code is ready suiteName: suiteName, }); return appPrefs; } /** * Return cloud synchronized configuration context * Currently supports Windows and iOS/macOS * @returns {AppPreferences} AppPreferences object, bound to that suite */ AppPreferences.prototype.cloudSync = function () { var appPrefs = new AppPreferences ({cloudSync: true}); return appPrefs; } /** * Return default configuration context * Currently supports Windows and iOS/macOS * @returns {AppPreferences} AppPreferences object, bound to that suite */ AppPreferences.prototype.defaults = function () { var appPrefs = new AppPreferences (); return appPrefs; } // WIP: functions to bind selected preferences to the form function setFormFields (formEl, fieldsData) { for (var i = 0; i < formEl.elements.length; i ++) { var formField = formEl.elements[i]; if (!(formField.name in fieldsData)) { continue; } // TODO: multiple checkboxes value for one form field if (formField.type === 'radio' || formField.type === 'checkbox') { if ( formField.value === fieldsData[formField.name] || formField.value === fieldsData[formField.name].toString() ) { formField.checked = true; } } else { formField.value = fieldsData[formField.name]; } } } function bindFormToData (formEl, formData) { [].slice.apply (formEl.elements).forEach (function (el) { if (el.type.match (/^(?:radio|checkbox)$/)) { el.addEventListener ('change', getFormFields.bind (window, formEl, formData), false); } else { el.addEventListener ('input', getFormFields.bind (window, formEl, formData), false); } }); } function getFormFields (formEl, formData) { formData = formData || {}; for (var k in formData) { delete formData[k]; } for (var i = 0; i < formEl.elements.length; i ++) { var formField = formEl.elements[i]; var checkedType = formField.type.match (/^(?:radio|checkbox)$/); if ((checkedType && formField.checked) || !checkedType) { formData[formField.name] = formField.value; } } // console.log (formData); return formData; } if (typeof module !== "undefined") { module.exports = new AppPreferences(); }