I want to create a drag and droppable playlist using the Spotify API.
var tracks = models.library.tracks;
var playlist = new models.Playlist();
// loop through all tracks and add them to the playlist
for (var i = 0; i < 25; i++) {
var track = models.Track.fromURI(tracks[i].data.uri);
playlist.add(track);
}
// add playlist to view
var playlistView = new views.List(playlist);
$('#playlist').html(playlistView.node);
How would I make this playlist enabled for dragging and dropping the tracks?
Is this already built in to the Spotify API (but not documented yet)?
I tried to implement it myself using jQuery UIs sortable function.
$('#playlist').sortable();
However, Spotify playlists aren't simple ul and li child elements, so this didn't work out.
The issue isn't that the playlist isn't displayed using ul and li elements. The jQuery UI sortable function actually works fine with a div tag containing a tags, particularly if (as is the case with Spotify) the a tags have their display style set to table. You simply need to tell the function what child items are sortable by using something like:
$('#playlist').sortable({ items: 'a.sp-item' });
That tells it that links with the class of sp-item are sortable.
However, it still doesn't quite work when doing that as the items snap back to the original positions. This is because each row in the playlist is set to use absolute positioning and uses the -webkit-transform CSS property to position it at the correct Y-coordinates. This results in the "snap-back" experienced.
I have found a workaround for this, although it is rather a hack. Simply add the following code before you set the list to sortable:
setTimeout(function() {
$('#playlist > div > div a.sp-item').attr("style","").css("position","relative");
}, 100);
This basically removes the "-webkit-transform" style from all the links in the playlist and changes them to use relative positioning (the timeout is because the list doesn't appear to be fully constructed immediately after creating it). Strangely this doesn't seem to affect the display of the playlist at all, so it makes me wonder why it was done like this...
I did this exact thing a few days ago, for the site below. Scroll down, click the tabs, add a few tracks to build up a list and drag and drop about.
http://cultureclash.redbull.co.uk/getonstage/
The pertinent code is at http://cultureclash.redbull.co.uk/getonstage/js/getonstage-ui.js starting at line 106. The playlist itself is stored somewhere else as an array of tracks encoded just as Spotify has them.
When the user adds a track, I grab that array and put it through a template to generate the DOM for the sortable, then I give data-index attributes to each li to keep track of which index of the original array they represent. When the sortstop event fires, I go through the ul, take note of the new order, and shift the array to match.
Have a deeper look at that file and play about with the app using a DOM inspector to work out the finer details.
Related
I'm developing a web application that is making use of tabs. I've run one issue that seems small, but I haven't been able to locate a solution and I'm worried it is indicative of larger problems with my code.
My application has one main page that includes a tab container with several content panes then inserted as children. I figured that having each content pane load an external HTML file and loading it that way was a good solution - it seemed to provide modular design and allow for easy changing of the contents of an individual tab. The issue I am running into now is that while everything loads correctly, I'm unable to provide anchor links in inside a tab or between tabs. Here is some sample code:
var tabs = new TabContainer({
style: "height: 100%; width: 100%;"
}, "tab-container");
tabs.startup();
var metadata = new ContentPane({
title: "Metadata",
id: "Metadata"
});
/* repeat for the non-metadata content panes */
request.get("/js/viewer/templates/splash.html").then(function (results) {
splash.set("content", results);
});
/* repeat for each pane */
For my metadata page I want it to contain information about the datasets I am providing to users. Ideally, it would have a table of contents with anchor links to the proper entries. However, when I implement an anchor link, like so:
Click me to jump down the page!
<!-- some content here -->
<div id="test">We made it!</div>
The link is clickable and it does in fact bring you to the proper location, but it seems to invariably load this in a new frame that requires a user to reload the page if they wish to do anything else. I've tried playing with tag properties but it's been to no avail. I'm also hoping to be able to link between tabs (say, if a user is querying on one of the query pages I have presented them and then wants to know where a dataset came from or other information).
Here is a simple imgur album showing what happens: http://imgur.com/a/JCnlH
After clicking the link in the first image, you're sent down the page. However, the tab bar and the navigation bar of the page disappear completely, even when you scroll back up. I don't know why this is.
So, this has been a long question, but here is the core of it:
What am I doing wrong with anchor links?
To answer your first question:
You should put all JavaScript inside your main page, not inside partials. This is not considered a best practice with JavaScript because it means you will have to eval() the content and usually when you start doing that when you don't need to, then you're doing something wrong.
In this case you can easily add all JavaScript code to the main page. If you need to wait for a specific tab to be opened or loaded, you can use the onShow or onLoad events on the dijit/layout/ContentPane widgets.
Also, when you're using ContentPane you should use the proper setters for adding content/HTML to it.
Rather than doing:
request.get("/js/viewer/templates/splash.html").then(function (results) {
dojo.byId("Splash").innerHTML = results;
});
You should be doing:
request.get("/js/viewer/templates/splash.html").then(function (results) {
splash.set("content", results);
});
Or if you don't have a reference anymore to the splash variable, you should be using registry.byId("splash").set("content", results).
About the hyperlinks, I have no idea. I'm not getting the same behavior, so could you explain a bit further on that?
Here's a (as far as I can see) working example: http://jsfiddle.net/n4515tsz/
In my case, this behavior seems to be caused by interaction of the overflow: hidden CSS property of my website's body interacting with the height: 100% property of my tab container. I didn't realize that the overflow: hidden property was set because it was part of a framework I was using. By changing the overflow property I have been able to achieve the desired behavior.
Scenario:
I'm currently working with KnockoutJS, jQuery UI's sortable and Masonry. I use Knockout to display a list of cards, Masonry to arrange the cards properly, and sortable for sorting the cards.
The cards are of similar size, but I'll be introducing different sizes later on so I surely need Masonry to help me arrange them.
I'm using a customized bindingHandler function for the sortable so that when the user sorts the cards, the item's position gets updated in the knockout observableArray.
Problem:
Since the knockout sortable function removes the item from the last position and puts it back to a new position, Masonry messes up and somehow sortable also not functions properly. What I discovered was if I simply float left all the cards without using Masonry, sortable works fine.
I plan to undo Masonry at the start of the sortable, and put it back at the stop of the sortable. However I don't know how to unbind Masonry. Is there a way? Or at least a way to get it working.
Here's a prototype of what I'm trying to do:
http://jsbin.com/avujom/9/edit
you could use masonry's destroy to remove masonry. so it removes the Masonry functionality completely. This will return the element back to its pre-initialized state. and you could create masonry element when you needed by msnry = new Masonry( container );. Also you could use layout method.
JQuery UI Sortable works great for lists with no gaps, but say I want to render a list of items with gaps, e.g.
1, 2, empty, 4, 5, 6, empty, 8
where the number represents the slot number. The behavior expected then would be if the user drags an element over the 2 slot, the 2 value gets pushed to the empty slot 3, and the user could drop the new element in the 2 slot, whereas if they drag a new element over the empty 3 slot, the list items would not push down, and the user could just drop the new item into the empty 3 slot. Hopefully this makes sense.
I've been looking at the JQuery UI Sortable code, and it seems that I need to utilize the change and receive callbacks in order to achieve this, but, being new to JQuery/JS in general, it's not clear to me what to use to add these empty slot placeholders and manage the selection list so that I don't break the sorting functionality with custom code.
Any pointers, examples, etc. would be much appreciated.
After banging my head on this for a while, I created a jsFiddle: http://jsfiddle.net/pdHnX/
To help explain the problem. I believe everything I'm trying to accomplish can happen in an overridden _rearrange method. The fiddle code handles the case where an item is replacing a filler item, but there is an odd issue, where if you drag an item from the item list to the filler list, drop the item, then drag the same item within the filler list, the filler list is shrunk by 1, which is a problem.
There are more issues once you start dragging more items into the filler list, but this is where I'm at with the problem at the moment.
I'm not sure if I understood you correctly, but here's what I came up with:
http://jsfiddle.net/py3DE/
$(".source .item").draggable({ revert: "invalid", appendTo: 'body', helper: 'clone',
start: function(ev, ui){ ui.helper.width($(this).width()); } // ensure helper width
});
$(".target .empty").droppable({ tolerance: 'pointer', hoverClass: 'highlight',
drop: function(ev, ui){
var item = ui.draggable;
if (!ui.draggable.closest('.empty').length) item = item.clone().draggable();// if item was dragged from the source list - clone it
this.innerHTML = ''; // clean the placeholder
item.css({ top: 0, left: 0 }).appendTo(this); // append item to placeholder
}
});
Assumptions:
If you drag item from the top list - it will be "cloned" (original will remain in the list)
If you drag item from the bottom list - it will move (leaving empty placeholder)
If you drop any item on a bottom list's placeholder - it will replace the placeholder
If you drop any item on bottom list's non-empty placeholder/item - it will replace it
sizes of both lists always stay the same
Let me know if this helps or are you looking for something else.
Also explaining the whole task (what's the purpose of this dragging) might help :-)
Sorry, this was a really difficult question to detail out. The requirements were really specific and hard to describe.
Here is the solution I was looking for.
The others answers here, while they may have been solutions to the poorly described problem, were not what I was looking for.
http://jsfiddle.net/vfezJ/
Unfortunately, the sortable.js code is very hairy so the code linked there is also very hairy. The solution I've posted also makes some assumptions on CSS classes and DOM ids to get the job done.
http://jsbin.com/igozat/2
Here's something I whipped up. It's not perfect or deserving of much credit because sortable() can't be interruped mid-drag. If that was possible, then a drop action could take over completely. Also, my attempt to re-sort was buggy. I know that this methods works with just regular jQuery, but it didn't work with jQuery UI mucking me up.
At risk of sounding like a jerk, if it were me, I'd write the functionality with just jQuery. jQuery UI isn't all its cracked up to be. :P
I have a script that uses jQuery UI's graggable and droppable functionality.
Items are dragged from left panel into main area and dropped.
There are two scenarios:
Items are dropped into other boxes, preset on page load or can be dropped into main area to create a new box. The items can be moved between the boxes. Everything works fine with this setup.
In second scenario there are no preset boxes in main area. They are supposed to be created whn an item is dragged and dropped from the left panel. However, for some reason it only allows me to create one box. Here's an example.
My level of UI proficiency is not high enough to spot what the problem is.
Your problem is that in this code:
$.when(createEmptyStack()).then(function(data) { ... }
data is always "" meaning that you're attempting to add a new element to the page with the same id over and over again (your AJAX request to createEmptyStack is not returning anything in the response). This is why it works fine the first time; the id doesn't exist and so the code works as expected.
When you drag another item into the main area, the element may be successfully appended, but it has a duplicate id. This code:
putCardIntoStack(ui.draggable,newstackId);
(in the same block as the code above)
Is always called with the draggable object and just STACK as the newstackId.
In short, this is a problem with your server-side code not returning a stack id. According to Firebug, your AJAX request isn't returning anything at all.
There are two sortable UL elements which are linked to each other via 'connectWith' options so that items from one list can be moved to another list and vice versa. I'm in need to prohibit from moving/dragging some items to another list while still letting them to be draggable within their own list. Is there any way to implement such behavior?
For now I can only restrict items from dragging using 'items' option of those sortable lists but while this prevents the desired items from dragging to another list this also prevents those items from dragging within their own lists (which is bad).
The way I was able to get around this is by using the li elements onMouseDown and onMouseUp events. When the mouse is clicked, I changed my CSS class (or whatever else) so that it falls out of the items list.