I'm writing an interface for user settings in a Chrome extension. Here's how I define the settings:
function Setting(label, type, defaultData) {
this.label = label;
this.type = 'checkbox';
this.defaultData = defaultData;
}
var Settings = {};
Settings['setting-one'] = new Setting('Setting one', 'checkbox', 'true');
Settings['setting-two'] = new Setting('Setting two', 'checkbox', 'true');
Here's how I set the default value for each setting:
function setDefaultSetting(setting, defaultValue) {
chrome.storage.sync.get(setting, function(results) {
if (!results[setting]) {
// If the setting has not yet been defined, define it now
var dataToSave = {};
dataToSave[setting] = defaultValue;
chrome.storage.sync.set(dataToSave, function() {
debugMsg('set `' + setting + '` to ' + defaultValue);
});
}
});
}
for (var setting in Settings) {
if (Settings.hasOwnProperty(setting)) {
var s = Settings[setting];
if (s.type == 'checkbox') {
setDefaultSetting(setting, s.defaultData);
}
}
}
So far, so good. Now I want to print the list of settings as checkboxes. Here's what I've tried:
function printSettingsModal() {
var output += '<form>';
for (var setting in Settings) {
if (Settings.hasOwnProperty(setting)) {
var s = Settings[setting];
if (s.type == 'checkbox') {
chrome.storage.sync.get(setting, function(results) {
output += '<p><input id="setting-' + setting + '" type="checkbox"';
if (results[setting] == 'true') { output += ' checked="checked"'; }
output += '><label for="setting-' + setting + '">' + s.name + '</label></p>';
});
}
}
}
output += '</form>';
return output;
}
This doesn't work because chrome.storage.sync.get() is asynchronous. How can I loop through my array of settings and retrieve the associated chrome.storage data for each setting?
You don't actually have to do that sequentially, or even set defaults explicitly beforehand.
chrome.storage.local.get has 3 forms for its first argument:
"key" (string): retrieves just one key, as you're doing
["key1", "key2"] (array): retrieves all the values for keys in the array
{ key1: default1, key2: default2 } (dictionary object): retrieves all the values for keys specified, returning the provided default value if it's not in storage
You can collect all the settings you want to retrieve and get them in one operation.
A simple way to do it is to use a recursive function, add each option inside the callback of the chrome.storage.sync.get() method, and call the printSettingsModal() function increasing the index. To do this you'll need an array structure to at least store the name of the settings so you can iterate over it, you can edit your Settings object and add the method add() that will do this for you. You can then call the chrome.storage.sync.get() method recursively and call a callback when finished.
So, first, here is your new Settings object:
function Setting(label, type, defaultData) {
this.label = label;
this.type = 'checkbox';
this.defaultData = defaultData;
}
function Settings() {
var _this = this;
this.list = [];
this.add = function(key, obj) {
this.list.push(key);
_this[key] = obj;
}
}
var mySettings = new Settings();
mySettings.add('setting-one', new Setting('Setting one', 'checkbox', 'true'));
mySettings.add('setting-two', new Setting('Setting two', 'checkbox', 'true'));
Now you will have something like this:
console.log(mySettings);
Settings {list: Array[2], add: function, setting-one: Setting, setting-two: Setting}
Here comes the recursive function, you will need to:
Initialize the output as "<form>" at the beginning.
Add all the settings in the intermediate steps.
Finalize the output adding "</form>" at the end.
Call a callback with the output so you can use it properly.
Here is an example of what your function will look like using recursion:
function printSettingsModal(index, output, callback) {
var s = mySettings[mySettings.list[index]];
if (index === 0) {
// first element is going to be created
output = "<form>";
} else if (index > mySettings.list.length-1) {
// the last setting has been added
// call the callback
callback(options);
}
if (s.type == 'checkbox') {
chrome.storage.sync.get(setting, function(results) {
output += '<p><input id="setting-' + setting + '" type="checkbox"';
if (results[setting] == 'true') { output += ' checked="checked"'; }
output += '><label for="setting-' + setting + '">' + s.name + '</label></p>';
if (index === mySettings.list.length-1) {
// last element has been created
output += '</form>';
printSettingsModal(++index, output, callback);
}
});
}
}
Now you just need to define a callback and call it:
function myCallback(options) {
// do something with the option string
// like:
document.getElementById('form-container').innerHTML = options;
}
printSettingsModal(0, null, myCallback);
If I wrote all correctly, this will work for you. Hope it helped.
Related
I want to do:
properties.email.value without triggering an error like: Can't read 'value' of 'undefined'
However, I don't want to do:
properties.email && properties.email.value and I don't want to use an helper, something like: get(properties, 'email.value').
I really want to keep the syntax properties.email.value
I can solve this by doing:
Object.defineProperty(properties, 'email', {
get: () => properties.email && properties.email.value,
enumerable: true,
configurable: true
});
Now the getter is in charge of doing my safety check. Perfect.
But I also want to be able to do properties.name.value safely.
But as the properties object comes from the API (json), I don't know the full list of properties possible.
So, is there a way to use this "magical" get syntax for any prop access like: properties[ANYTHING].value ?
OK, I've got something like this.
But you must create properties that way.
Hope this help :)
var properties = {
phone : {
value: "123456789"
}
}
var handler = {
get: function(target, name) {
return target.hasOwnProperty(name) ? target[name] : {};
}
};
var new_properties = new Proxy(properties, handler);
console.log("phone.value = " + new_properties.phone.value);
console.log("email.value = " + new_properties.email.value);
new_properties.email = {
value: 1
};
console.log("email.value after assign = " + new_properties.email.value);
The document reference here.
Edited
Even if the original properties object is unknown, this kind of usage works as well.
You could use a Proxy and get known properties and a custom result for unknow properties.
For changing properties, you could take the same approach and set the value.
var properties = { email: { value: 'foo#example.com' } },
proxy = new Proxy(
properties,
{
get: function(target, prop, receiver) {
if (prop in target) {
return target[prop] && target[prop].value
} else {
return;
}
},
set: function(target, prop, value) {
if (prop in target) {
target[prop].value = value;
} else {
target[prop] = { value };
}
}
}
);
console.log(proxy.email);
console.log(proxy.bar);
proxy.email = '41';
console.log(proxy.email);
I can't believe I'm doing this...
var wordlength = 7;
var alphabet="abcdefghijklmnopqrstuvwxyz";
alphabet += alphabet.toUpperCase() + "0123456789_";
var alen = alphabet.length;
var buildWord = function(number){
if(number===0){
return '';
}
return alphabet[number%alen]+buildWord(Math.floor(number/alen));
};
var total = Math.pow(alen, wordlength);
for(var i = 1; i<total; i++){
var w = buildWord(i);
if(isNaN(w[0]) && Object.prototype[w]===undefined){
Object.prototype[w]={};
}
}
In my Nativescript app I have a loop and want to display a dialog for each item being iterated over. When the dialog displays it contains "Accept" and "Reject" options, both of which when clicked I would like to call a method which I pass the iterated item into. The issue is since the option selection returns a promise I lose the reference to the iterated item. What can I do to get around this? Here's an example of my code.
EDIT: I also really don't like that I'm declaring a function in the loop after the promise returns.
function _showPendingConnections() {
for (var i = 0; i < ViewModel.pendingConnections.length; i++) {
var pendingConnection = ViewModel.pendingConnections[i];
dialog.confirm({
message: pendingConnection.PatientFirstName + " would like to share their glucose readings with you.",
okButtonText:"Accept",
cancelButtonText:"Reject"
}).then(function(result) {
if(result === true) {
ViewModel.acceptConnection(pendingConnection);
} else {
ViewModel.removeConnection(pendingConnection);
}
});
}
}
The following change worked for me (I have probably created different viewModel but however the idea is the same) - all I have done is to change when your item index is passed.
For example:
// main-page.js
"use strict";
var main_view_model_1 = require("./main-view-model");
var dialogModule = require("ui/dialogs");
var viewModel = new main_view_model_1.MyViewModel();
viewModel.pendingConnections = [{ PatientFirstName: "John" }, { PatientFirstName: "Merry" }, { PatientFirstName: "Abygeil" }];
// Event handler for Page "navigatingTo" event attached in main-page.xml
function navigatingTo(args) {
// Get the event sender
var page = args.object;
page.bindingContext = viewModel;
for (var index = viewModel.pendingConnections.length - 1; index >= 0; index--) {
connectionDealer(index);
}
}
exports.navigatingTo = navigatingTo;
function connectionDealer(index) {
var pendingConnection = viewModel.pendingConnections[index];
dialogModule.confirm({
message: pendingConnection["PatientFirstName"] + " would like to share their glucose readings with you.",
okButtonText: "Accept",
cancelButtonText: "Reject"
}).then(function (result) {
if (result === true) {
// your code follow.. pass pendingConnection[index] to your method
console.log("accepted by " + pendingConnection["PatientFirstName"]);
}
else {
// your code follow.. pass pendingConnection[index] to your method
console.log("Rejected by " + pendingConnection["PatientFirstName"]);
}
});
}
// main-view-model.js
"use strict";
var observable = require("data/observable");
var MyViewModel = (function (_super) {
__extends(MyViewModel, _super);
function MyViewModel() {
_super.apply(this, arguments);
}
Object.defineProperty(MyViewModel.prototype, "pendingConnections", {
get: function () {
return this._pendingConnections;
},
set: function (value) {
if (this._pendingConnections !== value) {
this._pendingConnections = value;
}
},
enumerable: true,
configurable: true
});
return MyViewModel;
}(observable.Observable));
exports.MyViewModel = MyViewModel;
I am coding a chat program but i am stuck in this part.
var Controller=function conversation() {
this.createMessageNode=function(msg,sender,time,mid){
var newMessage;
if(sender==sessionStorage.getItem('userid')){
newMessage="<div class='message-sent' id='"+mid+"'>"+msg+"<span class='time'>"+time+"</span></div>";
}else{
newMessage="<div class='message-recv' id='"+mid+"'>"+msg+"<span class='time'>"+time+"</span></div>";
}
sessionStorage.setItem('lastMessage',mid);
$('.chat-messages').append(newMessage);
}
this.getMessages=function(){
if(sessionStorage.getItem('lastMessage')==null){
sessionStorage.setItem('lastMessage',0);
}
$.ajax({url:"getmessages.php",type:"POST",data:{last:sessionStorage.getItem('lastMessage'),cid:sessionStorage.getItem('conversationid')},success:function(result) {
var messages=JSON.parse(result);
for (var i = 0; i < messages.length; i++) {
createMessageNode(messages[i].message,messages[i].sender,messages[i].time,messages[i].mid);
var cont=document.getElementById('chat-messages');
cont.scrollTop=cont.scrollHeight;
};
}});
}
}
now when i do this it shows an error
Uncaught ReferenceError: createMessageNode is not defined
now in the for loop "this" variable is referring to the ajax object. how can i call the createMessageNode function?
Your functions are bound to the this object. If it is a global object (top most parent scope) then you can reference the functions within this by this.yourfunction
You must study SCOPE properly to understand
http://www.w3schools.com/js/js_scope.asp
The issue is createMessageNode() is a method of the Controller object instance, so you need to refer to the instance when calling it. Without refering to the instance, the JavaScript engine is looking for the function in the current scope, then each higher scope all the way up to the global scope.
Typically you would use the this keyword to reference the instance, but in your case, the jQuery ajax call has changed the this context, so you can't directly use this.
A possible solution is, before the ajax call, store the this context:
var that = this;
Now, in the ajax success function:
that.createMessageNode(messages[i].message,messages[i].sender,messages[i].time,messages[i].mid);
^^ refer to the instance
It'd probably be better to write your code following better prototypical inheritance models, like so:
function Controller() {
this.chatMessages = $('.chat-messages');
}
Controller.prototype.createMessageNode = function (msg, sender, time, mid) {
var newMessage;
if (sender == sessionStorage.getItem('userid')) {
newMessage = "<div class='message-sent' id='" + mid + "'>" + msg + "<span class='time'>" + time + "</span></div>";
} else {
newMessage = "<div class='message-recv' id='" + mid + "'>" + msg + "<span class='time'>" + time + "</span></div>";
}
sessionStorage.setItem('lastMessage', mid);
this.chatMessages.append(newMessage);
};
Controller.prototype.getMessages = function () {
var _this = this;
if (sessionStorage.getItem('lastMessage') === null) {
sessionStorage.setItem('lastMessage', 0);
}
$.ajax({
url: "getmessages.php",
type: "POST",
data: {
last: sessionStorage.getItem('lastMessage'),
cid: sessionStorage.getItem('conversationid')
},
success: function (result) {
var messages = JSON.parse(result);
for (var i = 0; i < messages.length; i++) {
_this.createMessageNode(messages[i].message, messages[i].sender, messages[i].time, messages[i].mid);
}
var cont = $('#chat-messages');
cont.scrollTop(cont.scrollHeight);
}
});
};
This solves the issue of the context by creating a true class, for example:
var conversation = new Controller();
conversation.getMessages();
conversation.createMessageNode('Hello, World!', 'JimmyBoh', new Date(), 8268124);
Also, whenever you have nested functions, it can help to store the desired context in a local variable, such as _this or that, etc. Here is a more simple example:
function outer() {
// Temporarily store your desired context.
var _this = this;
// Make any call that executes with a different context.
$.ajax({
url: "getmessages.php",
type: "GET",
success: function inner(result) {
_this.doSomething(result);
}
});
};
Lastly, there might be a time when you want to execute a method in a different context than the current. .call() and .apply() can be used to run a method with a specified context and arguments. For example:
function printThis() {
console.log(this.toString());
console.dir(arguments);
}
printThis.call('Hello, World!');
printThis.call('Call array:', [2, 4, 6, 8], 10); // Keeps arguments as-is.
printThis.apply('Apply array:', [2, 4, 6, 8], 10); // Accepts an array of the arguments.
function Sum(startingValue) {
this.value = startingValue || 0;
}
Sum.prototype.add = function (number) {
this.value += number;
}
var realSum = new Sum(2);
var fakeSum = {
value: 3
};
realSum.add(1);
Sum.prototype.add.call(fakeSum, 2);
console.log(fakeSum.value); // Prints '5'
console.log(realSum.value); // Prints '3'
I'm having an issue with a helper function inside my Backbon.js View. When it's run, it dies with the following error message about the first line of the "addCalc" function:
TypeError: this.getCalcValue is not a function
It's really puzzling because in the "initialize" function defined just above, all the functions seem to be defined. It feels like I'm calling the sibling method wrong, and the "initialize" method is an exception where "this" can be used to reference the object.
Is there something wrong/missing with the following code, or something I missed with the backbone documentation?
CalcView = Backbone.View.extend({
el: $("#calcView"),
initialize: function () {
this.resetCalc();
},
addCalc: function (model) {
var cost = this.getCalcValue(model.get('currentCost'));
var custom = this.getCalcValue(model.get('customProgram'));
var variables = { id: model.get('id'),
category: model.get('category'),
shortDesc: model.get('shortDescription'),
description: model.get('description'),
currentCost: cost,
customProgram: custom,
};
var template = _.template($('#calc_template').html(), variables);
$("#calc_payload").append(template);
},
resetCalc: function(models) {
$("#calc_payload tr").remove();
},
removeCalc: function(model){
$("#calc_payload #" + model.get('id')).remove();
},
updateCalcs: function(model) {
var cost = model.get('currentCost');
var custom = model.get('customProgram');
$("#" + model.get("id") + " .currentCost").text(this.getCalcValue(cost));
$("#" + model.get("id") + " .customProgram").text(this.getCalcValue(custom));
/*var currentCostSum = 0;
var customProgramSum = 0;
$("#calc_payload .currentCost").each(function() {
var temp = Number(($(this).text()).replace(/[^0-9\.]+/g, ""));
if (!isNaN(temp))
currentCostSum += temp;
});
$("#calc_payload .customProgram").each(function() {
var temp = Number(($(this).text()).replace(/[^0-9\.]+/g, ""));
if (!isNaN(temp))
customProgramSum += temp;
});
$("#calc_footer .currentCost").text("$" + ((currentCostSum == 0) ? " -- " : CurrencyFormatted(currentCostSum.toFixed(2))));
$("#calc_footer .customProgram").text("$" + ((customProgramSum == 0) ? " -- " : CurrencyFormatted(customProgramSum.toFixed(2))));*/
},
getCalcValue: function(value) {
if (typeof value == 'string' || value instanceof String)
return value.toString();
else if (isNaN(value))
return "$ -- ";
else
return "$" + value.toFixed(2);
},
});
The code that executes the "addCalc" function is driven by a backbone collection. Basically, when the collection is added to, the CalcView.addCalc is called
Calculations = Backbone.Collection.extend({
model: Calculation,
//This is our Friends collection and holds our Friend models
initialize: function (models, options) {
this.on("add", options.onAdd);
this.on("remove", options.onRemove);
this.on("reset", options.onReset);
//Listen for new additions to the collection and call a view function if so
}
});
//This is where addCalc is used.
var calcview = new CalcView();
var calc_collection = new Calculations( null, {
onAdd: calcview.addCalc,
onRemove: calcview.removeCalc,
onReset: calcview.resetCalc
});
In your initialize function add this line of code:
_.bindAll(this,'addCalc');
This will bind this to be your CalcView for the addCalc function. You can put multiple comma separated method names in there if you need to bind more than one function...
See Underscore's documentation on it here.
When you bind events on collection you can send the context as third argument. Try sending one more option property as your calcview and pass it as context.
this.on("add", options.onAdd, options.calcview);
this.on("remove", options.onRemove, options.calcview);
this.on("reset", options.onReset, options.calcview);
I have this code:
var getValuesArray = [];
var setValuesArray = [];
function SetValueJson(key, value, scormVersion, methodCalled)
{
if (key1 != null) {
var obj = {
key: key1,
value: value1
}
setValuesArray.push(obj);
alert("pushing the key as: " + setValuesArray[key] + " and value as: " + setValuesArray[key].value); //not shure how te reference it?
return value;
}
and:
function GetValueJson(key, scormVersion, methodCalled) {
//I will get to this later, want to get the array right first
}
How do I reference the array?alert("pushing the key as: " + setValuesArray[key] + " and value as: " + setValuesArray[key].value); is not correct..
thanks
I suggest you to use Object instead of Array.
Modified code
var setValuesArray = {};
function SetValueJson(key, value, scormVersion, methodCalled)
{
setValuesArray[key]= value;
alert("pushing the key as: " + key + " and value as: " + value);
return value;
}
function GetValueJson(key, scormVersion, methodCalled) {
return setValuesArray[key]; // will return 'undefined' if key is not present.
}
Well, this basically depends on what you want to do with this data structure.
If keeping these keys and values sequentially and their order is important than you would need to store them in an array.
If you only need to look up the values based on keys, then a simple object would do just fine.
Lets say you need to maintain the order of the key-value data as it comes in, and you're using an array for that, then you have two options:
Keep the actual key-value store inside an object. For the order, maintain a separate array only keeping the keys. Here's what it should look like somewhat
var keyValueStore = {},
keyOrderStore = [];
function store(key, value) {
keyValueStore[key] = value;
keyOrderStore.push(key);
}
function getValueByIndex(index) {
// do some bounds checking ofcourse :)
return keyValueStore[keyOrderStore[index]];
}
function getValue(key) {
return keyValueStore[key];
}
This is in my opinion a better option. However, if you don't want to maintain and sync two separate data structures, you can use this:
var storage = [];
function store(key, value) {
storage.push({
key: key,
value: value
});
}
function getAtIndex(index) {
return storage[index].value;
}
function getValue(key) {
for (var i = storage.length-1; i >= 0; i--) {
var obj = storage[i];
if (obj.key === key) return obj.value;
}
}
Hope this helps.