12

I want to get a list of items in firebase, but each element of the item has a list of related items. I haven't been able to get the list, neither using firebase-util nor firebase array $extend functionality.

My firebase data looks something like this:

items
    item1
        name: "Item 1"
        user: user1
        images
           image1: true
           image2: true
    item2
        name: "Item 2"
        user: user1
        images:
            image3: true
            image4: true
    item3
        name: "Item 3"
        user: user2
        images:
            image5: true
            image6: true

users
    user1
        name: "User 1"
        email: "user1@email.com"
    user2
        name: "User 2"
        email: "user2@email.com"

images
    image1
        image: "data:image/jpeg;base64,/9j/..."
        thumb: "data:image/jpeg;base64,/9j/..."
    image2
        image: "data:image/jpeg;base64,/9j/..."
        thumb: "data:image/jpeg;base64,/9j/..."
    image3
        image: "data:image/jpeg;base64,/9j/..."
        thumb: "data:image/jpeg;base64,/9j/..."
    image4
        image: "data:image/jpeg;base64,/9j/..."
        thumb: "data:image/jpeg;base64,/9j/..."
    image5
        image: "data:image/jpeg;base64,/9j/..."
        thumb: "data:image/jpeg;base64,/9j/..."

And I just want to get a list of items with all the data. Something like:

items
    item1
        name: "Item 1"
        user
            name: "User 1"
            email: "user1@email.com"
        images
            image1
                image: "data:image/jpeg;base64,/9j/..."
                thumb: "data:image/jpeg;base64,/9j/..."
            image2
                image: "data:image/jpeg;base64,/9j/..."
                thumb: "data:image/jpeg;base64,/9j/..."
    item2
        name: "Item 2"
        user
            name: "User 1"
            email: "user1@email.com"
        images
            image3
                image: "data:image/jpeg;base64,/9j/..."
                thumb: "data:image/jpeg;base64,/9j/..."
            image4
                image: "data:image/jpeg;base64,/9j/..."
                thumb: "data:image/jpeg;base64,/9j/..."
    item3
        name: "Item 3"
        user
            name: "User 2"
            email: "user2@email.com"
        images
            image5
                image: "data:image/jpeg;base64,/9j/..."
                thumb: "data:image/jpeg;base64,/9j/..."
            image6
                image: "data:image/jpeg;base64,/9j/..."
                thumb: "data:image/jpeg;base64,/9j/..."

It looks like a fairly common use case, but I'm stucked here. I have tried this solution (in both ways) but I couldn't get it work. The data structure is also a bit different since I need to relate a list which is inside another list.

Community
  • 1
  • 1
cor
  • 3,323
  • 25
  • 46
  • As it is, you cannot retrieve the data in the format you are asking for based on your current structure. Can you provide information as to why you want to get ALL of that data at one time? Typically you would get a list of user names, or a list of images that go with Item 3 but usually not all at once. I think there may be a solution to your question and understanding the use case will help. – Jay May 02 '16 at 20:14
  • @Jay I want to show an item list. Each item has a list of images, but I will just show one of them at the list as a main picture of the item. And why not, maybe on mouseover I would like to change to another one. But at least if I could do it just with one it would be nice. – cor May 03 '16 at 07:27

3 Answers3

6

The objective is to show an item list.

Each item has a list of images.

Initially, show the item list and one of the images for each item.

Suggested approach:

To populate the list of items and their initial thumbnails, we need to have a separate node that we pull the initial setup from.

Updated items node

items:
  item_id_xx: //this should be a Firebase generated node name
   name: "Item 2"
   user: "uid_for_user_1"
   images:
     image3: "data:image/jpeg;base64,/9j/..."
     image4: "data:image/jpeg;base64,/9j/..."

Here's the node used for the main list where the user can click on an item thumb to get more detail:

item_list_for_ui
  random_node_0
    item_id: "item_id_aa"
    name: "Item 1" //if you want to display the name in the list
    initial_thumb: "data:image/jpeg;base64,/9j/..." //initial thumb
    link_to: "image1"
  random_node_1
    item_id: "item_id_xx"
    name: "Item 2"
    initial_thumb: "data:image/jpeg;base64,/9j/..."
    link_to: "image3"
  random_node_2
    item_id: "item_id_qq"
    name: "Item 3"
    initial_thumb: "data:image/jpeg;base64,/9j/..."
    link_to: "image1"

When the app starts, populate the list from the items_list_for_ui node.

The node is shallow and contains the Firebase item_id, the item name (if needed), the link to get the initial image thumbnail from, and the link_to of the main image in Firebase.

For example: If the user clicks the thumbnail for Item 2, the item details can be loaded by observeSingleEvent with .value at

/items/item_id_xx/images/image3

You could expound on this by adding say, a rollover link to the item_list_for_ui

  random_node_1
    item_id: "item_id_xx"
    name: "Item 2"
    initial_thumb: "data:image/jpeg;base64,/9j/..."
    thumb_link: "image3"
    rollover_thumb: "external link to rollover"
    rollover_link: "image4"

This structure is very flexible in that you can change out what thumb and rollovers you want to display in the main list by just updating those respective child nodes.

It's also efficient as it avoids loading hundreds of items and hundreds of child image nodes - loading all of those nodes and child nodes would overload the ui (in some cases).

With this structure, the item_list_for_ui is compact so even with hundreds of items, it's a small subset of that data.

You are probably saying to yourself 'self, that's duplicate data'. Yes, it is, and duplicating data in Firebase is a normal process and encouraged: it keeps the structure flatter and makes queries and working with data much faster.

For more reading see Denormalizing Data Keeps Your Breath Minty Fresh

Jay
  • 34,438
  • 18
  • 52
  • 81
  • Thanks for the answer @Jay. But how would you join the data? would you use `firebase-util` plugin? If so, how? I don't know how to do the relation since the item id is dynamic – cor May 05 '16 at 13:36
  • @cor Firebase doesn't have a join function as it's not SQL. There are a number of ways to associate data and duplicating data is okay to do in Firebase. I think my answer answered your direct question. Your comment/follow up is a great new question (and there are some answers that address it already on Stackoverflow). Check out those answers and if you have a specific question, post it! – Jay May 05 '16 at 13:46
  • @cor If it helped, accept the answer! Then work through the code a bit and check out some of the other answers on associating (joining) data in Firebase. If you get stuck, post another question and we'll try help out! – Jay May 05 '16 at 14:00
  • I will upvote it, but I won't accept it while the bounty period is open. It may appear another better answer ;) – cor May 05 '16 at 14:04
3

Thanks to @Jay and @Eric for the answers, they have been very helpful, my solution has a bit of both. I will explain how have I figured it out.

Firstly, I have changed the schema and added a new key for the item's main picture. I have called it cover. But answering the original question, I will do it loading all the images. So here is the new items schema:

items
    item1
        name: "Item 1"
        user: user1
        cover: image1
        images
           image1: true
           image2: true
    item2
        name: "Item 2"
        user: user1
        cover: image3
        images:
            image3: true
            image4: true
    item3
        name: "Item 3"
        user: user2
        cover: image5
        images:
            image5: true
            image6: true

Then, this is how I get the list mentioned above (using async library). It may be a better approach to accomplish the same:

getItems: function(cb){
    var items = ref.child("items");
    items.on("value", function(snapshot){

        var item_length = snapshot.numChildren(),
            final_items = [],
            readed = 0;

        ref.child("items").on("child_added", function(item){

            var item_id = item.key(),
                itemData = item.val(),

                user = ref.child("users").child(itemData.user),
                cover = ref.child("images").child(itemData.cover),
                images = new Firebase.util.NormalizedCollection(
                       [ref.child("items").child(item_id).child("images"),'alertImages'],
                        ref.child('images')
                 ).select('images.image','images.thumb').ref();

                async.parallel([
                    function(callback){
                        user.on("value", function(user_snap){
                            callback(null, user_snap.val());
                        });
                    },
                    function(callback){
                        images.on("value", function(images_snap){
                            callback(null, images_snap.val());
                        });
                    },
                    function(callback){
                        cover.on("value", function(cover_snap){
                            callback(null, cover_snap.val());
                        });
                    }
                ], function(err, results){
                    if(!!err){
                        cb(err,null)
                    }else{
                        itemData.user = results[0];
                        itemData.images = results[1];
                        itemData.cover = results[2];

                        final_items.push(itemData);
                        readed += 1;
                        if(readed === item_length){
                            cb(null,final_items);
                        }
                    }
               });
        });
    });
}

And this will output something like:

item1
    name: "Item 1"
    cover:
        image: "data:image/jpeg;base64,/9j/..."
        thumb: "data:image/jpeg;base64,/9j/..."
    user
        name: "User 1"
        email: "user1@email.com"
    images
        image1
            image: "data:image/jpeg;base64,/9j/..."
            thumb: "data:image/jpeg;base64,/9j/..."
        image2
            image: "data:image/jpeg;base64,/9j/..."
            thumb: "data:image/jpeg;base64,/9j/..."
item2
    name: "Item 2"
    cover:
        image: "data:image/jpeg;base64,/9j/..."
        thumb: "data:image/jpeg;base64,/9j/..."
    user
        name: "User 1"
        email: "user1@email.com"
    images
        image3
            image: "data:image/jpeg;base64,/9j/..."
            thumb: "data:image/jpeg;base64,/9j/..."
        image4
            image: "data:image/jpeg;base64,/9j/..."
            thumb: "data:image/jpeg;base64,/9j/..."
item3
    name: "Item 3"
    cover:
        image: "data:image/jpeg;base64,/9j/..."
        thumb: "data:image/jpeg;base64,/9j/..."
    user
        name: "User 2"
        email: "user2@email.com"
    images
        image5
            image: "data:image/jpeg;base64,/9j/..."
            thumb: "data:image/jpeg;base64,/9j/..."
        image6
            image: "data:image/jpeg;base64,/9j/..."
            thumb: "data:image/jpeg;base64,/9j/..."
cor
  • 3,323
  • 25
  • 46
  • Glad you have a solution. Keep an eye on those asynchronous calls inside other asynchronous calls. That may get you into trouble so I would check your data frequently - especially with the push() and test on several devices reading/writing data at about the same time. – Jay May 06 '16 at 22:51
  • 1
    Thanks for the advice @Jay, I was looking for some code like that in the answers, not just about the code structure. At the moment I haven't found anything better. I will keep trying though – cor May 09 '16 at 14:26
2

If you have all that data available you could loop through your images and use its metadata as keys within the rest of the database.

var itemsArr = [];
    for(var i in items) {
        var item = items[i];
        var images = [];
        for(var image in item[images]) {
            item.push(images[image]);
        }
        itemsArr.push({
            name: item.name,
            user: users[item.user],
            images: images
        });
    }

Which should yield an array of objects looking like this:

{
    name: "Item 1",
    user: {
        name: "User 1",
        email: "user1@email.com"
    },
    images: [{
            image: "data:image/jpeg;base64,/9j/..."
            thumb: "data:image/jpeg;base64,/9j/..."
        },{
            image: "data:image/jpeg;base64,/9j/..."
            thumb: "data:image/jpeg;base64,/9j/..."
    }]
}
Eric N
  • 2,136
  • 13
  • 13