2

UPDATE - Thanks for all the great answers and incredibly fast response. I've learned a great deal from the suggested solutions. I ultimately chose the answer I did because the outcome was exactly as I asked, and I was able to get it working in my application with minimal effort - including the search function. This site is an invaluable resource for developers.


Probably a simple task, but I can't seem to get this working nor find anything on Google. I am a Javascript novice and complex JSON confuses the hell out of me. What I am trying to do is make a PhoneGap Application (Phone Directory) for our company. I'll try to explain my reasoning and illustrate my attempts below.

I have JSON data of all of our employees in the following format:

[  
  {  
    "id":"1",
    "firstname":"John",
    "lastname":"Apple",
    "jobtitle":"Engineer"
  },
  {  
    "id":"2",
    "firstname":"Mark",
    "lastname":"Banana",
    "jobtitle":"Artist"
  },
  ... and so on
]

The mobile framework (Framework 7) that I am using offers a "Virtual List" solution which I need to take advantage of as our directory is fairly large. The virtual list requires you to know the exact height of each list item, however, you can use a function to set a dynamic height.

What I am trying to do is create "headers" for the alphabetical listing based on their last name. The JSON data would have to be restructured as such:

[  
  {
    "title":"A"
  },
  {  
    "id":"1",
    "firstname":"John",
    "lastname":"Apple",
    "jobtitle":"Engineer"
  },
  {
    "title":"B"
  },
  {  
    "id":"2",
    "firstname":"Mark",
    "lastname":"Banana",
    "jobtitle":"Artist"
  },
  ... and so on
]

I've been able to add key/value pairs to existing objects in the data using a for loop:

var letter, newLetter;
for(var i = 0; i < data.length; i++) {
    newLetter = data[i].lastname.charAt(0);
    if(letter != newLetter) {
        letter = newLetter
        data[i].title = letter;
    }
}

This solution changes the JSON, thus outputting a title bar that is connected to the list item (the virtual list only accepts ONE <li></li> so the header bar is a div inside that bar):

{  
  "id":"1",
  "firstname":"John",
  "lastname":"Apple",
  "jobtitle":"Engineer",
  "title":"A"
},
{  
  "id":"1",
  "firstname":"Mike",
  "lastname":"Apricot",
  "jobtitle":"Engineer",
  "title":""
}

This solution worked until I tried implementing a search function to the listing. When I search, it works as expected but looks broken as the header titles ("A", "B", etc...) are connected to the list items that start the particular alphabetical section. For this reason, I need to be able to separate the titles from the existing elements and use them for the dynamic height / exclude from search results.

The question: How can I do a for loop that inserts [prepends] a NEW object (title:letter) at the start of a new letter grouping? If there is a better way, please enlighten me. As I mentioned, I am a JS novice and I'd love to become more efficient programming web applications.

4
  • 1
    Why do you want to use such data structure? Wouldn't it better to have {"A": [...], "B": [...], ...}? Commented Dec 23, 2014 at 14:39
  • @Ginden, that sounds to be a good alternative, however I am unsure as to how I would get the dynamic height function working with that structure - see here Commented Dec 23, 2014 at 14:42
  • @Ginden, my thoughts exactly! I would regroup by title too. Commented Dec 23, 2014 at 14:42
  • @Phillips126: assuming you have a set height for each title: {"A": { "height" : "someHeight", "items" : [...] } Commented Dec 23, 2014 at 14:43

3 Answers 3

5

var items = [  
  { "lastname":"Apple" },
  { "lastname":"Banana" },
  { "lastname":"Box"  },
  { "lastname":"Bump" },
  { "lastname":"Can" },
  { "lastname":"Switch" }
];
  
var lastC = null;  //holds current title
var updated = [];  //where the updated array will live
for( var i=0;i<items.length;i++) {
  var val = items[i];  //get current item
  var firstLetter = val.lastname.substr(0,1);  //grab first letter
  if (firstLetter!==lastC) {  //if current title does not match first letter than add new title
    updated.push({title:firstLetter});  //push title
    lastC = firstLetter;  //update heading
  }
  updated.push(val);  //push current index
}
console.log(updated);

Sign up to request clarification or add additional context in comments.

3 Comments

Thank you for your solution, and adding comments to help explain what is going on. This worked perfectly for every issue I stated in my original post.
You might just want to do val.lastname.substr(0,1).toUpperCase() to make sure they are properly cased.
I was actually just about to implement this - just realized the search results were case-sensitive. Thanks for the suggestion!
1

Well right now you have an array of objects - prefixing the title as its own object may be a bit confusing - a better structure may be:

[
  {
    title: "A",
    contacts: [
        {  
            "id":"1",
            "firstname":"John",
            "lastname":"Apple",
            "jobtitle":"Engineer",
            "title":"A"
  }
]

Given your current structure, you could loop and push:

var nameIndexMap = {};
var newContactStructure = [];

for (var i = 0; i < data.length; i++) {
    var letter = data[i].lastname.charAt(0);
    if (nameIndexMap.hasOwnProperty(letter)) {
        //push to existing
        newContactStructure[nameIndexMap[letter]].contacts.push(data[i])
    } else {
        //Create new
        nameIndexMap[letter] = newContactStructure.length;
        newContactStructure.push({
            title: letter,
            contacts: [
                data[i]
            ]
        });
    }
}

newContactStructure will now contain your sorted data.

Demo: http://jsfiddle.net/7s50k104/

2 Comments

This is nice if that is what the 3rd party component expects.
This is a great alternative, however, the framework is pretty strict and this structure would result in considerably more code/tweaking to get working.
0

Simple for loop with Array.prototype.splice will do the trick:

for (var i = 0; i < data.length; i++) {
    if (i == 0 || data[i-1].lastname[0] !== data[i].lastname[0]) {
        data.splice(i, 0, {title: data[i].lastname[0]});
        i++;
    }
}

Demo. Check the demo below.

var data = [  
    {"lastname":"Apple"},
    {"lastname":"Banana"},
    {"lastname":"Bob"},
    {"lastname":"Car"},
    {"lastname":"Christ"},
    {"lastname":"Dart"},
    {"lastname":"Dog"}
];

for (var i = 0; i < data.length; i++) {
    if (i == 0 || data[i-1].lastname[0] !== data[i].lastname[0]) {
        data.splice(i, 0, {title: data[i].lastname[0]});
        i++;
    }
}

alert(JSON.stringify( data, null, 4 ));

1 Comment

You are right, fixed. It was interesting to do in one simple loop.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.