Added: incubator/shindig/trunk/javascript/gadgets/prefs.js URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/javascript/gadgets/prefs.js?rev=608302&view=auto ============================================================================== --- incubator/shindig/trunk/javascript/gadgets/prefs.js (added) +++ incubator/shindig/trunk/javascript/gadgets/prefs.js Wed Jan 2 17:20:35 2008 @@ -0,0 +1,363 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Provides access to user prefs, module dimensions, and messages. + * + * Clients can access their preferences by constructing an instance of + * gadgets.Prefs and passing in their module id. Example: + * + * var prefs = new gadgets.Prefs(); + * var name = prefs.getString("name"); + * var lang = prefs.getLang(); + * + * Modules with type=url can also use this library to parse arguments passed + * by URL, but this is not the common case: + * + * <script src="http://apache.org/shindig/prefs.js"></script> + * <script> + * gadgets.Prefs.parseUrl(); + * var prefs = new gadgets.Prefs(); + * var name = prefs.getString("name"); + * </script> + */ + +var gadgets = gadgets || {}; + +/** + * Stores preferences for the default shindig implementation. + * @private + */ +gadgets.PrefStore_ = function() { + var modules = {}; + + /** + * Returns the module named by moduleId + * @param {Number | String} moduleId The module id to fetch. + * @return {Object} An object containing module data. + */ + function getModuleData(moduleId) { + if (!modules[moduleId]) { + modules[moduleId] = { + prefs:{}, + msgs:{}, + language:"all", + country:"all" + }; + } + return modules[moduleId]; + } + + /** + * Adds a new user preference to the stored set for the given module id. + * + * @param {Number | String} moduleId The module id to add the pref for. + * @param {String} key The key to add. May be an object where keys = key and + * values = value. + * @param {String} value The value for the key. Optional. + */ + function setPref(moduleId, key, value) { + var module = getModuleData(moduleId); + if (typeof key !== "string") { + for (var i in key) { + module.prefs[i] = key[i]; + } + } else { + module.prefs[key] = value; + } + } + + /** + * Adds a new message to the stored set for the given module id. + * + * @param {Number | String} moduleId The module id to add the pref for. + * @param {String | Object} key The key to add. May be an object where keys = + * key and values = value. + * @param {String} value The value for the key. Optional. + */ + function setMsg(moduleId, key, value) { + var module = getModuleData(moduleId); + if (typeof key !== "string") { + for (var i in key) { + module.msgs[i] = key[i]; + } + } else { + module.msgs[key] = value; + } + } + + var defaultModuleId = 0; + + /** + * @param {String | Number} moduleId The module id to set as default. + */ + function setDefaultModuleId(moduleId) { + defaultModuleId = moduleId; + } + + /** + * @return {String | Number} The default module id. + */ + function getDefaultModuleId() { + return defaultModuleId; + } + + /** + * @param {Number | String} moduleId The module id to set the language for. + * @param {String} language The language to use. + */ + function setLanguage(moduleId, language) { + getModuleData(moduleId).language = language; + } + + /** + * Sets the default country for this module id. + */ + function setCountry(moduleId, country) { + getModuleData(moduleId).country = country; + } + + // Export public API. + return { + setPref:setPref, + setMsg:setMsg, + setCountry:setCountry, + setLanguage:setLanguage, + getModuleData:getModuleData, + setDefaultModuleId:setDefaultModuleId, + getDefaultModuleId:getDefaultModuleId + }; +}(); + +/** + * @constructor + * @param {String | Number} moduleId The module id to create prefs for. + */ +gadgets.Prefs = function(moduleId) { + if (typeof moduleId === "undefined") { + this.moduleId_ = gadgets.PrefStore_.getDefaultModuleId(); + } else { + this.moduleId_ = moduleId; + } + this.data_ = gadgets.PrefStore_.getModuleData(this.moduleId_); + // This is used to eliminate one hash table lookup per value fetched. + this.prefs_ = this.data_.prefs; + this.msgs_ = this.data_.msgs; +}; + +/** + * Static pref parser. Parses all parameters from the url and stores them + * for later use when creating a new gadgets.Prefs object. + * You should only ever call this if you are a type=url gadget. + */ +gadgets.Prefs.parseUrl = function(moduleId) { + var prefs = {}; + var msgs = {}; + var country = "all"; + var language = "all"; + if (gadgets.Util) { + var params = gadgets.Util.getUrlParameters(); + for (var i in params) { + if (i.indexOf("up_") === 0 && i.length > 3) { + prefs[i.substr(3)] = String(params[i]); + } else if (i.indexOf("msg_") === 0 && i.length > 4) { + msgs[i.substr(4)] = String(params[i]); + } else if (i === "country") { + country = params[i]; + } else if (i === "lang") { + language = params[i]; + } else if (i === "mid") { + moduleId = params[i]; + } + } + } + gadgets.PrefStore_.setDefaultModuleId(moduleId); + gadgets.PrefStore_.setPref(moduleId, prefs); + gadgets.PrefStore_.setMsg(moduleId, msgs); + gadgets.PrefStore_.setLanguage(moduleId, language); + gadgets.PrefStore_.setCountry(moduleId, country); +}; + +/** + * Internal helper for pref fetching. + * @param {String} key The key to fetch. + * @return {String} + */ +gadgets.Prefs.prototype.getPref_ = function(key) { + var val = this.prefs_[key]; + return typeof val === "undefined" ? null : val; +} + +/** + * Retrieves the named preference as a string. + * @param {String} key The preference to fetch. + * @return {String} The preference. If not set, an empty string. + */ +gadgets.Prefs.prototype.getString = function(key) { + var val = this.getPref_(key); + return val === null ? "" : val; +}; + +/** + * Retrieves the named preference as an integer. + * @param {String} key The preference to fetch. + * @return {Number} The preference. If not set, 0. + */ +gadgets.Prefs.prototype.getInt = function(key) { + var val = parseInt(this.getPref_(key), 10); + return isNaN(val) ? 0 : val; +}; + +/** + * Retrieve the named preference as a floating point value. + * @param {String} key The preference to fetch. + * @return {Number} The preference. If not set, 0. + */ +gadgets.Prefs.prototype.getFloat = function(key) { + var val = parseFloat(this.getPref_(key)); + return isNaN(val) ? 0 : val; +}; + +/** + * Retrieves the named preference as a boolean. + * @param {String} key The preference to fetch. + * @return {Boolean} The preference. If not set, false. + */ +gadgets.Prefs.prototype.getBool = function(key) { + var val = this.getPref_(key); + if (val !== null) { + return val === "true" || val === true || !!parseInt(val, 10); + } + return false; +}; + +/** + * Stores a preference. + * @param {String | Object} key The pref to store. + * @param {String} val The values to store. + */ +gadgets.Prefs.prototype.set = function(key, value) { + throw new Error("setprefs feature required to make this call."); +}; + +/** + * Retrieves the named preference as an array. + * @param {String} key The preference to fetch. + * @return {Array.<String>} The preference. If not set, an empty array. + * UserPref values that were not declared as lists will be treated as + * 1 element arrays. + */ +gadgets.Prefs.prototype.getArray = function(key) { + var val = this.getPref_(key); + if (val !== null) { + var arr = val.split("|"); + // Decode pipe characters. + for (var i = 0, j = arr.length; i < j; ++i) { + arr[i] = arr[i].replace(/%7C/g, "|"); + } + return arr; + } + return []; +}; + +/** + * Stores a preference from the given list. + * @param {String} key The pref to store. + * @param {Array.<String | Number>} val The values to store. + */ +gadgets.Prefs.prototype.setArray = function(key, val) { + throw new Error("setprefs feature required to make this call."); +}; + +/** + * Fetches an unformatted message. + * @param {String} key The message to fetch + * @return {String} The message. + */ +gadgets.Prefs.prototype.getMsg = function(key) { + var val = this.msgs_[key]; + return typeof val === "undefined" ? "" : val; +}; + +/** + * The regex pulls out the text before and after the positional argument + * and digs down for a possible example value in case no actual value + * was provided. It is used by the function getMsgFormatted. + * + * Example: "Foo <ph name="number"><ex>bar</ex>%1</ph> baz." + * 0 = "Foo <ph name="number"><ex>bar</ex>%1</ph> baz." : match for the + * whole regex. + * + * 1 = "Foo " : matches first (.*) in regex + * + * 2 = "<ph name="number"><ex>bar</ex>%1</ph>" : matches + * (\<ph.*?\>\s*(\<ex\>(.*?)\<\/ex\>)?\s*%1\s*\<\/ph\>) in regex + * 3 = "<ex>bar</ex>" : matches (\<ex\>(.*?)\<\/ex\>)? in regex, since it + * is an optional param it may have the value "undefined" + * 4 = "bar" : matches (.*?) in regex (it is a non-greedy regex) + * if 3=undefined then 4 = "undefined". + * + * 5 = " baz." : matches final (.*) in regex + * + * TODO: this may need to be a single line even though it's > 80 characters + * because some browsers may not properly interepret the line continuation. + */ +gadgets.Prefs.MESSAGE_SUBST_REGEX = + /(.*)(\<ph.*?\>\s*(\<ex\>(.*?)\<\/ex\>)?\s*%1\s*\<\/ph\>)(.*)/; + +/** + * Returns a message value with the positional argument opt_subst in place if + * it is provided or the provided example value if it is not, or the empty + * string if the message is not found. + * Eventually we may provide controls to return different default messages. + * + * @param {String} key The message to fetch. + * @param {String} subst ???? + * @return {String} The formatted string. + */ +gadgets.Prefs.prototype.getMsgFormatted = function(key, opt_subst) { + var val = this.getMsg(key); + + var result = val.match(gadgets.Prefs.MESSAGE_SUBST_REGEX); + // Allows string that should be getMsg to also call getMsgFormatted + if (!result || !result[0]) { + return val; + } + if (typeof opt_subst === "undefined") { + var sub = result[4] || ""; + return result[1] + sub + result[5]; + } + return result[1] + opt_subst + result[5]; +}; + +/** + * @return {String} The country for this module instance. + */ +gadgets.Prefs.prototype.getCountry = function() { + return this.data_.country; +}; + +/** + * @return {String} The language for this module instance. + */ +gadgets.Prefs.prototype.getLang = function() { + return this.data_.language; +}; + +/** + * @return {String | Number} The module id for this module instance. + */ +gadgets.Prefs.prototype.getModuleId = function() { + return this.moduleId_; +};
Added: incubator/shindig/trunk/javascript/gadgets/setprefs.js URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/javascript/gadgets/setprefs.js?rev=608302&view=auto ============================================================================== --- incubator/shindig/trunk/javascript/gadgets/setprefs.js (added) +++ incubator/shindig/trunk/javascript/gadgets/setprefs.js Wed Jan 2 17:20:35 2008 @@ -0,0 +1,59 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + /* This library augments gadgets.Prefs with functionality to store prefs + * dynamically. + */ + +/** + * Stores a preference. + * @param {String | Object} key The pref to store. + * @param {String} val The values to store. + */ +gadgets.Prefs.prototype.set = function(key, value) { + if (arguments.length > 2) { + // For backwards compatibility. This can take the form: + // prefs.set(key0, value0, key1, value1, key2, value2); + + // prefs.set({key0: value0, key1: value1, key2: value2}); + var obj = {}; + for (var i = 0, j = arguments.length; i < j; i += 2) { + obj[arguments[i]] = arguments[i + 1]; + } + gadgets.PrefStore_.setPref(this.moduleId_, obj); + } else { + gadgets.PrefStore_.setPref(this.moduleId_, key, value); + } + // TODO: Update user pref store somehow. +}; + +/** + * Stores a preference from the given list. + * @param {String} key The pref to store. + * @param {Array.<String | Number>} val The values to store. + */ +gadgets.Prefs.prototype.setArray = function(key, val) { + if (!val.length || !val.join) { + throw new Error("Value is not an array."); + } + + // We must escape pipe (|) characters to ensure that decoding in + // getArray actually works properly. + for (var i = 0, j = val.length; i < j; ++i) { + val[i] = val[i].replace(/\|/g, "%7C"); + } + gadgets.PrefStore_.setPref(this.moduleId_, key, val.join("|")); + + // TODO: Update user pref store somehow. Where do we hook into the container? +}; \ No newline at end of file Added: incubator/shindig/trunk/javascript/gadgets/util.js URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/javascript/gadgets/util.js?rev=608302&view=auto ============================================================================== --- incubator/shindig/trunk/javascript/gadgets/util.js (added) +++ incubator/shindig/trunk/javascript/gadgets/util.js Wed Jan 2 17:20:35 2008 @@ -0,0 +1,130 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +var gadgets = gadgets || {}; + +/** + * General purpose utilities. + */ +gadgets.Util = function() { + /** + * Parses url parameters into an object. + * @return {Array.<String>} the parameters. + */ + function parseUrlParams() { + // Get settings from url, 'hash' takes precedence over 'search' component + // don't use document.location.hash due to browser differences. + var query; + var l = document.location.href; + var queryIdx = l.indexOf("?"); + var hashIdx = l.indexOf("#"); + if (hashIdx === -1) { + query = l.substr(queryIdx + 1); + } else { + // essentially replaces "#" with "&" + query = [l.substr(queryIdx + 1, hashIdx - queryIdx - 1), "&", + l.substr(hashIdx + 1)].join(""); + } + return query.split("&"); + } + + var parameters = null; + + /** + * @return {Object} Parameters passed into the query string. + */ + function getUrlParameters() { + if (parameters !== null) { + return parameters; + } + parameters = {}; + var pairs = parseUrlParams(); + var unesc = window.decodeURIComponent ? decodeURIComponent : unescape; + for (var i = 0, j = pairs.length; i < j; ++i) { + var pos = pairs[i].indexOf('='); + if (pos === -1) { + continue; + } + var argName = pairs[i].substring(0, pos); + var value = pairs[i].substring(pos + 1); + // difference to IG_Prefs, is that args doesn't replace spaces in argname: + // unclear on if it should do: argname = argname.replace(/\+/g, " "); + value = value.replace(/\+/g, " "); + parameters[argName] = unesc(value); + } + return parameters; + } + + /** + * Creates a closure which is suitable for passing as a callback. + * + * @param {Object} scope The execution scope. May be null if there is no + * need to associate a specific instance of an object with this callback. + * @param {Function} callback The callback to invoke when this is run. + * any arguments passed in will be passed after your initial arguments. + * @param {Object} var_args Any number of arguments may be passed to the + * callback. They will be received in the order they are passed in. + */ + function makeClosure(scope, callback, var_args) { + // arguments isn't a real array, so we copy it into one. + var tmpArgs = []; + for (var i = 2, j = arguments.length; i < j; ++i) { + tmpArgs.push(arguments[i]); + } + return function() { + // append new arguments. + for (var i = 0, j = arguments.length; i < j; ++i) { + tmpArgs.push(arguments[i]); + } + callback.apply(scope, tmpArgs); + }; + } + + var features = {}; + + /** + * @param {String} feature The feature to get parameters for. + * @return {Object} The parameters for the given feature, or null. + */ + function getFeatureParameters(feature) { + return typeof features[feature] === "undefined" ? null : features[feature]; + } + + /** + * @param {String} feature The feature to test for. + * @return {Boolean} True if the feature is supported. + */ + function hasFeature(feature) { + return typeof features[feature] === "undefined"; + } + + /** + * @param {Object} featureData The features that are supported, and + * their parameters. + */ + function init(featureData) { + features = featureData; + } + + // Export public API. + return { + getUrlParameters: getUrlParameters, + getFeatureParameters: getFeatureParameters, + hasFeature: hasFeature, + makeClosure: makeClosure, + init: init + }; +}(); + +// TODO: Check for any other commonly used aliases