1

I'm probably in deep water here, and I'm new to JavaScript and jQuery, But I would like to attempt to create a panel system sort of like how spotify has it done.

Have a look at this picture:

Diag

On the Spotify player website, when you click on something such as an artist or song/album, It slides in a topmost panel onto the right side of the screen, if you click something else, A new one will appear in its place, and the previous panel will get added to what I call the panel stack on the left side of the screen.

Here is a look at Spotify: enter image description here

I'd like to understand how to do this in JavaScript/jquery, does anyone have some input to share?

What I've tried: (http://jsfiddle.net/k0Lh3ama/)

I understand my attempt was pretty poor, this is why I'm here

var idx = 0;
var panels = [];
var numpanels = 0;

function panel () {
    this.id = 0;
    this.active = false;
    this.container = {};
}

var panelmgr = new function () {
    this.Create = function (lpPanel) {

        //set all current panels to inactive 
        //and then push them left
        for (var i = 0; i < panels.length; i++) {
            panels[i].active = false;
            panels[i].container.css("left", "10%");
        }

        //set the newest panel to Active and Top Most
        lpPanel.container.css("z-index", 10000);
        lpPanel.container.css("left", "25%");

        //setup some info for the new panel
        lpPanel.id = numpanels++;
        lpPanel.active = true;

        //add it to array
        panels.push(lpPanel);
    };
}

$(".box").click(function (e) {            
    var id = $(this).attr("id");
    var selected = -1;

    //find the panel we've selected and retrieve the index
    for (var i = 0; i < panels.length; i++) {
        if (id == panels[i].container.attr("id")) {
            selected = I;
            break;
        }                    
    }

    //do we have a valid selected panel?
    if (selected >= 0) {

        //Make all panels not selected
        for (var i = 0; i < panels.length; i++) {
            panels[i].active = false;
            panels[i].container.css("left", "10%");
        }

        //now make the panel we selected 
        //the top most panel
        panels[selected].active = true;
        panels[selected].container.css("z-index", 10000);
        panels[selected].container.css("left", "25%");
    }



});

$(document).ready(function () {

    var p1 = new panel();
    var p2 = new panel();
    var p3 = new panel();
    var p4 = new panel();
    var p5 = new panel();

    p1.container = $("#panel1");
    p2.container = $("#panel2");
    p3.container = $("#panel3");
    p4.container = $("#panel4");
    p5.container = $("#panel5");

    panelmgr.Create(p1);
    panelmgr.Create(p2);
    panelmgr.Create(p3);
    panelmgr.Create(p4);
    panelmgr.Create(p5);
});
guest
  • 6,450
  • 30
  • 44
Dean
  • 499
  • 6
  • 13
  • 34
  • I realized I just posted my answer and might have misinterpreted the use of the word slide -- my version of spotify doesn't slide, it just loads when changing between playlists, artists, albums, etc. I went for a very basic mock-up of what I saw spotify doing, could you expand on what you mean by slide? Or Perhaps, what does Yatin Mistry's response lack that you are looking for? – Arthur Weborg Oct 08 '14 at 04:21

4 Answers4

2

In short there are several issues with your original code. Below are my attempts at getting the current code set working (with suggestions for improvement at the bottom. Please see my original fiddle for working code without changing to much of your original code. I also modified the original fiddle (with sliding) to use css transitions to give a slide effect)

1) You are constantly writing over your panels because their css top value is set to zero for all. You can get around this by setting the top value for all non-active panels. (I assumed you wanted the item to dissappear from the stack, since you were moving it over to the current when it was selected)

//Increment height
var ii = 0;
for (var i = 0; i < panels.length; i++) {
    if(panels[i].active == false){
        panels[i].container.css("top", ii*30+"px");
        ii++;
    }
}

2) Your Z-index and width are causing the panel stack panels and the current panel to overlap, defaulting to whatever dom element was created last.

you can get around this by setting the z-index value to a lower value in the `//make all panels not selected`` loop

panels[i].container.css("z-index", "1");

3) You'd likely also want to hide the current panels contents when it is in the stack, otherwise your stack is going to get rather messy. You can get around this by splitting up your DOM panel's into something like:

<div id="panel5" class="box">
    <div class="panelTitle">Panel 4</div>
    <div class="contents">Stuff to display</div>
</div>

and adding the following code to the following loops:

//Make all panels not selected
for (var i = 0; i < panels.length; i++) {
    //other code emitted for space...
    //hide the contents
    panels[i].container.find(".contents").css("display","none");
}

//now make the panel we selected 
//the top most panel
panels[selected].active = true;
panels[selected].container.css("z-index", 2);
panels[selected].container.css("left", "25%");
panels[selected].container.css("top","0px");
//it's active, so display the contents
panels[selected].container.find(".contents").css("display","block");

4) In the event you want the panels to slide around on the clicks, I added CSS transitions - only in the second fiddle I added a transition rule to your .box selector that will put a second delay on all css changes for elements that have the class box. Please see css3 transitions for more on customizing transitions.

transition: all 1s;
-webkit-transition: all 1s;
-moz-transition: all 1s;
-o-transition: all 1s;

Suggestions for improvement/better development

To be honest I'd probably do a large rewrite. DOM manipulation gets the job done and is a fair start, but if things get complex it'd be much easier to track the data you need for each panel in the javascript and generate the DOM elements fresh when they are needed. I'd probably do something along the lines of the following disclaimer, I didn't test the following code as a whole, meant to be a guideline:

1) Define your data structure containing all relevant information to a panel and implement it in the javascript. This is similar to what you already have, but at a minimum I'd add a panel title and panel contents of sort.

Similar to what you have but I would add to it and do some parameter checking and remove the container

function Panel (jobj) {
    if(typeof jobj === "undefined") throw new ReferenceError("Bad value passed to constructor");
    if(typeof jobj.id === "undefined") throw new ReferenceError("Missing an id");
    this.id = 0;
    this.active = false;
    if(typeof jobj.title === "undefined") throw new ReferenceError("Missing panel title");
    this.title = jobj.title;
    //set to passed value, or if doesn't exist, default to empty array;
    this.tracks = jobj.tracks || [];
}

as a bonus you could make use of prototyping and construct different types of panels with different forms of parameter checking... I'm pretty sure there are other ways of doing this, but I've found the following works for me.

var DiscoverPanel = function(jsonObj){
    if(typeof jsonObj === "undefined")throw new ReferenceError("Expecting a JSON object but received none");
    var panel = new Panel(jsonObj);
    if(typeof jsonObj.genre === "undefined") throw new ReferenceError("Missing genre!")
    panel.genre = jsonObj.genre;
    return panel;
}

2) create the HTML for a stack and a current panel, so instead of the 5 div elements you currently have, I'd narrow it down to 2. Don't forget to style it to your liking

<ul id="stack"></ul>
<div id="current"></div>

3) Define your panels in the javascript instead of the html. Once you have working examples of the underlying json objects that you pass to the constructors, you might find it beneficial to write a server side service that you call via AJAX that returns the necessary JSON.

the constructor would be called like so:

//there are plenty of ways to generate the id within the constructor if hard coding
//it bugs you, your post showed a means of doing so.
var p1 = new Panel({title:"Panel 1",id:"p1"});
var p2 = new Panel({title:"Panel 2",id:"p2"});
var dp = new DiscoverPanel({title:"discover",id:"dp",genre:"rock"});
//etc.
var pn = new Panel({title:"Panel n"});
var panels = [p1,p2,dp,...,pn];

4) Onload/domready generate the innerHTML for the inactive panels, from potentially the panel titles.

$(document).ready(function () {
    generateList();
}
function generateList(){
    var stack = document.getElementById("stack");
    stack.innerHTML = "";
    panels.forEach(function(panel){
        var item = document.createElement("li");
        item.setAttribute("id",panel.id)
        item.innerHTML = panel.title;
        item.onclick = function(){
            //load this specific panel to the current
            loadCurrent(panel);
            //regenerate the stack
            generateList();
        }
        stack.appendChild(item);
    });
}

5) Proceed to generate the innerHTML for the current panel. This would be very similar to the above method of creating the element and the generating it's content off of the panel's data.

function loadCurrent(panel){
    panel.active = true;
    var cur = document.getElementById("current");
    //Figured table was a good example, but you could get as creative as you want
    //using floats, divs, etc. Could also check the types and handle different types of panels
    var table = document.createElement("table");
    panel.tracks.forEach(function(track){
        var row = document.createElement("tr");
        var td = document.createElement("td");
        td.innerHTML = track.number;
        row.appendChild(td);
        //repeat for whatever other values you have for a track
        //you'd likely add a play function or something of that sort to each.
        table.appendChild(row);
    });
    table.appendChild(cur);
}
Arthur Weborg
  • 8,280
  • 5
  • 30
  • 67
1

Check this fiddle : http://jsfiddle.net/k0Lh3ama/1/

Do the css stuff. I am not master in it..

Your CSS ::

.sidebar{
    width:10%;
    background-color:#000;
    color:#FFF;
    height:800px;
    float:left;

}
.box{
    width:7%;
    float:left;
    height:800px;
    background-color:red;
    border:1px solid #000;
}
.active{
    width:64%;
    float:right !important;
     -webkit-transition: all 1s ease-in-out;
-moz-transition: all 1s ease-in-out;
-o-transition: all 1s ease-in-out;
transition: all 1s ease-in-out;
}

Your Html Code
SideBar

    <div id="panel2" class="box active">Panel 1 </div>

    <div id="panel3" class="box">Panel 2 </div>

    <div id="panel4" class="box">Panel 3</div>

    <div id="panel5" class="box">Panel 4 </div>

Jquery

$(function(){
    $(".box").click(function(){
        $(".box").removeClass("active");
        $(this).addClass("active");
    }); 
});
Yatin Mistry
  • 1,246
  • 2
  • 13
  • 35
1

This isn't exactly what you're looking for, nor is it at all related to the code you posted. But heres something I threw together that might be a good resource for you to look at. It doesn't implement the stack, but you could apply the concepts fairly easily. Check it out.

You could get rid of the createPanel function and pre-create/assign all the panels in an init function, and then have controllers that use those panels as a base. Then it's just a matter of controlling whats showing in the DOM

display: none

will effectively remove an element from the DOM remember, and can be used to hide certain panels.

jamesism
  • 206
  • 1
  • 3
0

You probably shouldn't be changing the CSS. Basically, what you want are two divs, one that holds a list of "panels" and one that holds a detailed view of a single "panel". I see this problem as breaking down into a number of sub-problems:

1) Get a list of panel objects from the server, and dynamically populate the left div, more than likely with sub-divs that hold the panel info. In other words, at page load some code will need to run that will loop through the list and add divs to the left div, one for each panel, and CSS for those pieces should handle how they get laid out and everything. A simple place to start might be to just add a single button for each item in the list, or a single image, etc.

2) Event handler so that when you click on a panel in the list it loads the detailed view into the right div. This may also remove the panel from the list, or change it visually in some way (gray it out), or whatever.

3) Code to display detailed info in the right div if an item has been loaded into it, and doing some other default if not.

One way to cheat a little to simplify all this might be to have the panels and detailed views be pre-built pages, and load them into the main page in iframes or the like.

HamHamJ
  • 435
  • 2
  • 10