First, I must humbly admit that I've been a developer for many years but I've almost never had to work as a front-end, Web UI developer. I've been given an opportunity to work for an organization as a front-end and back-end web developer and I'm having to learn about the in's-and-out's of HTML/CSS/JS code. This is my first attempt at writing any real JavaScript.
I have a case where I need to create a type of collection object within JS. Specifically, I need to have a list of key-value pairs and I need to be able to readily sort the object for use within the UI.
After much reading on the topic of JavaScript arrays and objects, and how they behave, I've created the following JS class for use within my application.
A couple of points worth mentioning. This doesn't have to by hyper-optimized. This list will likely never contain more than 100 sortable objects and even that is the high-end of an estimation. Yes, I'd like for this to be efficient but clean, readable code is a little more important than being able to use this with 1,000,000 entries at the blink of an eye.
Second, this is an internal JS object. Of course, someone may come along, find my code posted here and decide to use. That is fine but I'm not looking to make an open source library.
Dictionary.js
function SortableDictionary() { var self = this; var sf = ""; var keys = []; //PUBLICS this.sort = function (field) { sf = field == null || field === undefined ? "" : field; var arr = getSortedArray(); for (i = 0, len = arr.length; i < len; i++) { var obj = arr[i]; if (obj == null) continue; else if (obj.hasOwnProperty("key") && obj.hasOwnProperty("value")) { if (obj["key"] != null) { this.remove(obj["key"]); this[obj["key"]] = obj["value"]; keys.push(obj["key"]); } } } } this.add = function (key, val) { if (keys.indexOf(key) != -1) throw "The key value '" + key + "' already exists."; else keys.push(key); this[key] = val; this.sort(); } this.remove = function (key) { if (this.hasOwnProperty(key)) delete this[key]; var ndx = keys.indexOf(key); if (ndx > -1) keys.splice(ndx,1); } this.toArray = function () { var arr = []; for (var i = 0; i < keys.length; i++) arr.push({ 'key': keys[i], 'value': this[keys[i]] }); return arr; } //PRIVATES var getSortedArray = function () { return self.toArray().sort(function (a, b) { if (a == null) return 1; if ((sf == "" || !a.hasOwnProperty("value")) && a.hasOwnProperty("key")) return a["key"] < b["key"] ? -1 : a["key"] > b["key"] ? 1 : 0 else if ((sf == "" || !a.hasOwnProperty("value")) && !a.hasOwnProperty("key")) return a < b ? -1 : a > b ? 1 : 0 else if (a["value"].hasOwnProperty(sf) && b["value"].hasOwnProperty(sf)) return a["value"][sf] < b["value"][sf] ? -1 : a["value"][sf] > b["value"][sf] ? 1 : 0 else if (a["value"].hasOwnProperty(sf) && !b["value"].hasOwnProperty(sf)) return -1; else if (!a["value"].hasOwnProperty(sf) && b["value"].hasOwnProperty(sf)) return 1; else return 0; }) } //OVERRIDES this.toString = function () { return JSON.stringify(this, function(k, v) {return k == "sf" ? undefined : v}); }}Test.html
<html><head> <script type="text/javascript" src="Scripts/Dictionary.js"></script></head><body><script> stdout = function(str) { document.writeln(str); document.writeln("<br>"); } dict = new SortableDictionary(); stdout(dict.toString()); stdout("Adding 'a'..."); dict.add("a", { text: "foo" }); stdout(dict.toString()); stdout("Adding 'c'..."); dict.add("c", { text: "bee" }); stdout(dict.toString()); stdout("Adding 'd'..."); dict.add("d", { text: "baz" }); stdout(dict.toString()); stdout("Adding 'b'..."); dict.add("b", { text: "bar" }); stdout(dict.toString()); stdout("Sorting generic..."); dict.sort(); stdout(dict); stdout("Sorting on 'text' value..."); dict.sort("text"); stdout(dict); stdout("Let's remove something...") dict.remove("b"); stdout(dict.toString());</script></body></html>For posterity, here is the code moved to JS Fiddle. TheDictionary object code is defined at the top, and the test code is executed at the bottom.
- \$\begingroup\$By using
this[key] = valyou make your dictionary not able to hold the names of your methods. For example, after you add thekeyremove,this.removewon't be the method you wrote anymore.\$\endgroup\$Rodrigo5244– Rodrigo52442015-12-04 16:42:17 +00:00CommentedDec 4, 2015 at 16:42 - \$\begingroup\$@Waterscroll That is a good point worth mentioning, but in this context there will not be a key "remove". However, I'll keep that in mind if I ever consider using something like this for a future project. Is there a way to add functions that are part of the prototype chain or something, so that I wouldn't have to worry about it. JS is such an interesting beast when you start digging into the "advanced" topics. It's so different than C#. It's easy to get lost and confused in the "weeds".\$\endgroup\$RLH– RLH2015-12-04 21:18:45 +00:00CommentedDec 4, 2015 at 21:18
- \$\begingroup\$The only difference, if the method is in the prototype, is that you can remove the key to get access to your method again with
this.remove. If it is in the prototype you could access your method from the prototype instead of the instance like thisSortableDictionary.prototype.remove.call(this, ...).\$\endgroup\$Rodrigo5244– Rodrigo52442015-12-06 00:36:35 +00:00CommentedDec 6, 2015 at 0:36
1 Answer1
If I'm reading this right, you're essentially sorting the object by adding/removing properties. However, order isn't guaranteed. Yes, it almost always works out so that properties are listed in the order they were added. But it's notguaranteed.
This SO answer has more on the subject, and suggests usingMap instead - if that's an option in the clients you have to support.
But to stick with a custom solution, you have to use arrays if you want guaranteed ordering. Much like you do already intoArray. You'll still want to wrap it in an API, so you can have custom add/remove methods and avoid duplicate keys.
Something like this, for instance:
function SortableCollection() { var items = []; // private this.add = function(key, value) { this.remove(key); items.push({ key: String(key), value: value }); }; this.remove = function(key, item) { // the key has to be stringified to match the behavior of // object property names key = String(key); items = items.filter(function(item) { return item.key != key; }); }; // a less clever but more versatile sort function this.sort = function(comparator) { items.sort(function(a, b) { return comparator(a.value, b.value); }); }; this.toArray = function() { return items.slice(); // return a copy, not a reference }; this.toObject = function() { return items.reduce({}, function(obj, item) { obj[item.key] = item.value; return obj; }); };}// for testing purposesfunction writeLine(text) { document.write(text + "<br>");}dict = new SortableCollection();writeLine(JSON.stringify(dict.toArray()));writeLine("Adding 'a'...");dict.add("a", { text: "foo" });writeLine(JSON.stringify(dict.toArray()));writeLine("Adding 'c'...");dict.add("c", { text: "bee" });writeLine(JSON.stringify(dict.toArray()));writeLine("Adding 'd'...");dict.add("d", { text: "baz" });writeLine(JSON.stringify(dict.toArray()));writeLine("Adding 'b'...");dict.add("b", { text: "bar" });writeLine(JSON.stringify(dict.toArray()));writeLine("Sorting on 'text' value...");dict.sort(function (a, b) { return a.text < b.text ? -1 : 1 });writeLine(JSON.stringify(dict.toArray()));writeLine("Let's remove something...")dict.remove("b");writeLine(JSON.stringify(dict.toArray()));- \$\begingroup\$Thanks Flambino for the answer. I should have added this to the test, but one of the requirements that I'd really like to maintain is that I'd like to be able to access objects as simply as
myDictionary["a"]and get back an object ({"text": "foo"}). Is readily possible with an implementation similar to yours?\$\endgroup\$RLH– RLH2015-12-03 20:24:26 +00:00CommentedDec 3, 2015 at 20:24 - \$\begingroup\$@RLH Access, yes - if go through toObject. E.g. dict.toObject()["a"]. Not pretty, but it'll work. The trick still is that you can't have an object with named properties and []-style accessand guarantee property ordering.\$\endgroup\$Flambino– Flambino2015-12-03 20:33:30 +00:00CommentedDec 3, 2015 at 20:33
You mustlog in to answer this question.
Explore related questions
See similar questions with these tags.
