Braeden Smith Braeden Smith - 7 months ago 11
HTML Question

Struggling to determine how to store and retrieve JSON using drop down menus

I'm creating a chrome extension in which I have information in JSON

options.js:

var data = {
"websites" : [
{
id: 1,
'baseURL': 'http://www.monoprice.com/search/index?keyword=',
'spaceValue': '+',
'img': 'monoprice.png',
'name': 'Monoprice'
},
{
id: 2,
'baseURL': 'https://www.youtube.com/results?search_query=',
'spaceValue': '+',
'img': 'youtube.png',
'name': 'Youtube'
},
{
id: 3,
'baseURL': 'http://www.amazon.com/s/field-keywords=',
'spaceValue': '%20',
'img': 'amazon.png',
'name': 'Amazon'
},
{
id: 4,
'baseURL': 'http://stackoverflow.com/search?q=',
'spaceValue': '+',
'img': 'stack.png',
'name': 'Stack'
}
]
};

function save_options() {
var itemOne = document.getElementById('1').value;
var itemTwo = document.getElementById('2').value;
var itemThree = document.getElementById('3').value;
var itemFour = document.getElementById('4').value;

chrome.storage.sync.set({
'One': itemOne,
'Two': itemTwo,
'Three': itemThree,
'Four': itemFour,
}, function() {
// Update status to let user know options were saved.
var status = document.getElementById('status');
status.textContent = 'Options saved.';
setTimeout(function() {
status.textContent = '';
}, 750);
});
}


// Restores select box and checkbox state using the preferences
// stored in chrome.storage.
function restore_options() {
// Use default value color = 'red' and likesColor = true.
chrome.storage.sync.get({
"One": "none",
"Two": "none",
"Three": "none",
"Four": "none",
}, function(items) {
document.getElementById('1').value = items.One;
document.getElementById('2').value = items.Two;
document.getElementById('3').value = items.Three;
document.getElementById('4').value = items.Four;
});
}
document.addEventListener('DOMContentLoaded', restore_options);
document.getElementById('save').addEventListener('click', save_options);


With this information, I want the user to choose the order in which this info is applied to the actual chrome extension.

Image of extension:

enter image description here

I'm using drop down menus in the options.html page:

options.html:

<!DOCTYPE html>
<html>
<head><title>My Test Extension Options</title></head>
<body>
<select id="1">
<option value="">None</option>
<option value="amazon">Amazon</option>
<option value="youtube">Youtube</option>
<option value="monoprice">Monoprice</option>
<option value="stack">Stack</option>

</select>

<select id="2">
<option value="">None</option>
<option value="amazon">Amazon</option>
<option value="youtube">Youtube</option>
<option value="monoprice">Monoprice</option>
<option value="stack">Stack</option>

</select>
<select id="3">
<option value="">None</option>
<option value="amazon">Amazon</option>
<option value="youtube">Youtube</option>
<option value="monoprice">Monoprice</option>
<option value="stack">Stack</option>

</select>
<select id="4">
<option value="">None</option>
<option value="amazon">Amazon</option>
<option value="youtube">Youtube</option>
<option value="monoprice">Monoprice</option>
<option value="stack">Stack</option>

</select>

<div id="status"></div>
<button id="save">Save</button>

<script src="options.js"></script>
</body>
</html>


But I can't figure out how to programatically link between the chosen drop down menus and the JSON array, and then to store and recall that data using
chrome.storage.sync.set({});


Thanks!

I know this might be confusing, I'd be happy to provide any clarification required.

Xan Xan
Answer

So, you already have working, proper code for saving/loading values to storage. It's a question of using that data efficiently and improving the user (and programmer!) experience.

Let's see problems you have here that make it awkward to access data:

  1. You store identifiers from the value attribute, but those do not (yet) mean anything for your data structure. On the other hand, you have id field in your data that you don't anyhow use.

    Conclusion: use the same identifier for both. It establishes correlation.

  2. Your unique identifiers in the data are numerical, and yet they are coded by hand. It's probably easier to maintain your data if they were meaningful, and that would also allow you to reorder your data without the IDs losing sense of order.

    Conclusion: use unique but meaningful identifiers, like the ones you use for value already.

  3. You have your data organised in an array, yet the intended use for it is to look it up by identifier. That's inefficient: you'll have to loop through the array until you find the identifier. An object solves this problem: it directly maps identifiers to data.

    Arrays are easy to add to, but you still want an unique identifier (that persists through adding/deleting data), so that negates the advantage.

    Conclusion: Don't use arrays that hold id/value pairs, use objects mapping ids to values.

    Putting the three together:

    websites: {
      amazon: {
        baseURL: 'http://www.amazon.com/s/field-keywords=',
        spaceValue: '%20',
        img: 'amazon.png',
        name: 'Amazon'
      },
      // ...
    ]
    

    (note that inside JS code, there's no need to quote object identifiers, and certainly one shouldn't mix those, but if you store it in a separate JSON file you want them quoted)

Now you can access whatever properties you need by ID that is stored in storage: data.websites[id].name, for instance.


We could be done here, but let's make the programmer's (yours!) life easier for later.

  1. Suppose you want to add a new website. Not only you have to add it to data, which is of course required, but you also need to copy-paste a new <option> all across your page. Or, if you change the name of a website you'd need to patch it everywhere. This is something that should be avoided, and it's not hard to automate by generating <option>s on the fly.

    Let's modify your HTML:

    <select class="website-select" id="1"></select>
    <!-- ... -->
    <select class="website-select" id="4"></select>
    

    And now let's populate it:

    // Assumes Chrome 45+ for newer JS things
    var selects = Array.from(document.getElementsByClassName('website-select'));
    for (var select of selects) {
      var noneOption = document.createElement('option');
      noneOption.value = '';
      noneOption.text = 'None';
      select.appendChild(noneOption);
      for (id in data.websites) {
        var websiteOption = document.createElement('option');
        websiteOption.value = id;
        websiteOption.text = data.websites[id].name;
        select.appendChild(websiteOption);
      }
    }
    
  2. In principle, the same applies to <select>s themselves. What if you want to allow a variable number of them, or just change the number later? The IDs for data ("One", "Two", ...) also require hardcoding and don't scale.

    Perhaps it's better to save an array in the storage, and build the whole interface based on that. It's going to be easier to access saved preferences as well.

    Left as an exercise for the reader.

  3. If you modify your data.websites, it may happen that id you have saved becomes invalid - it no longer maps to any website. That would cause code referencing data.websites[id] to fail.

    While it's possible to add checks that it exists everywhere, it's probably better to validate your data once (or as often as your data.websites changes, if you add that functionality) and then rely on the fact it's valid.

    Here's a sample validation:

    function validate(callback) {
      var defaults = {
        "One": "",
        "Two": "",
        "Three": "",
        "Four": "",
      };        
    
      chrome.storage.sync.get(defaults, function(items) {
        for (var option in items) {
          if (items[option] && !data.websites[items[option]]) {
            // Reset invalid data
            items[option] = "";
          }
        }
        chrome.storage.sync.set(items, callback);
      });
    }
    
  4. You might want to improve your user's experience by not requiring them to click "Save". It involves binding your saving logic to events on change event of the <select> elements instead of click on Save.

Comments