|
NestedSortableWidgetDocumentation
Documentation for the NestedSortable Widget.
IntroductionThe NestedSortable Widget (from now on just widget) is a component that allows you to create a multi-columned table of entries that can be sorted and nested very easily by your user, using drag-and-drop, with full support for pagination. It is built on top of the plain NestedSortable plugin and will, basically, do almost all the work you would have to do if you were to use the regular NestedSortable, as long as you provide it with a data source that will pass in your list of nested elements in JSON format. Given that list, it will build the HTML for the whole component, construct the NestedSortable and show visual clues as to when things are being loaded or saved. The list items can have multiple columns, since the widget will draw your list in a way that looks like a table. The widget also has support for pagination of the list, both on the client (you don't have to show the whole list at once) and the server (you don't have to download the whole list at once). This can be a necessity when you are sorting a list with a large number of items. The widget code, perhaps surprisingly, is almost 3 times larger than the NestedSortable plugin's code. The good news is that all is already written for you and with it in your hands you will be able to create a list that can be sorted and nested by your user in no time. You should check the demo, to really get a grasp of its main functionalities. Note that the data source used in the demo is a little stupid: it won't save the order of the elements, it will just pretend it did. This widget was designed as part of a Google Summer of Code project for WordPress, in 2007, in order to provide a easy way for users to sort their pages. You can see it in action for WordPress in my blog. DownloadYou can download the widget package (both the compressed and regular JS file, a CSS file, and 2 images) from the link bellow: NestedSortable Widget 1.0 You can also get the uncompressed code, as well as the test code used in the demo: NestedSortables Source 1.0 You will also need the dependencies. DependenciesIt was built on the latest versions of both jQuery and Interface.
Those dependencies have to be loaded in your HTML file in that order and before the widget itself. UsageBasic UsageFirst you need to set up your data source, which will provide the list of nested elements to be displayed by the widget, in JSON format. This data source should also be capable of receiving data from the widget, once the user saves the order of the elements, and update your database to reflect that. More details about this data source will be given bellow. For testing purposes, the easiest data source you could create is a plain text file with the contents of your list in JSON format. It obviously won't save your list order, but will allow you to view the list. So, create a file, named my_list_json.js, with the following contents: {
"requestFirstIndex" : 0,
"firstIndex" : 0,
"count": 4,
"totalCount" : 4,
"columns":["Title(ID)", "Owner", "Updated"],
"items":
[
{
"id":1,
"info":["Page Title(1)", "Bernardo Pádua", "2007-06-09 2:44 pm"]
},
{
"id":2,
"info":["Page Title(2)", "Bernardo Pádua", "2007-06-09 2:44 pm"],
"children":
[
{
"id":3,
"info":["Page Title(3)", "Bernardo Pádua", "2007-06-09 2:44 pm"],
"children": [
{
"id":4,
"info":["Page Title(4)", "Bernardo Pádua", "2007-06-09 2:44 pm"]
}
]
}
]
}
]
}
Now include the dependencies and the plugin itself in your HTML file. <script type="text/javascript" src="jquery.js"></script> <script type="text/javascript" src="interface.js"></script> <script type="text/javascript" src="inestedsortable.js"></script> <script type="text/javascript" src="jquery.nestedsortablewidget.js"></script> You will also need to include the CSS for the component, if you don't want it to look too ugly. Also be aware this CSS loads a couple of images (included in the download), so you need to make sure they are in your server as well. <link rel="stylesheet" href="../../src/widget/style/nestedsortablewidget.css" type="text/css"> In you HTML file, create an empty HTML element (usually a DIV) where the component will be drawn. The contents of this element will be deleted once the widget is configured. <div id="my_widget"></div> Add the JS code that will configure the widget. Once this code is executed, the widget will fetch the list contents from the loadUrl and display it for you. jQuery( function($) {
$('#my_widget').NestedSortableWidget({
loadUrl: "my_list_json.js"
});
});
Configuration ParametersThe NestedSortable plugin will add three functions to the jQuery object: NestedSortableWidget, NestedSortableWidgetDestroy and NestedSortableWidgetSave. The first will configure the widget and takes a object of configuration parameters. The other two should be called on already built widgets and take no parameters. NestedSortableWidget needs to be called on a jQuery object after selecting the element that will hold the component. You should probably use $("#element_id").NestedSortableWidget({(...)}) to do it. Like in the regular NestedSortable, you configure the widget by passing a single object to the NestedSortableWidget method. Use the {} notation to create such object. Lots of things can be configured in the widget. First, all the options of the underlying NestedSortable and Interface's Sortable can be altered, by passing an object to the nestedSortCfg property. You can also change the names of the classes used to draw the component, by passing an object to the classes property. You can also change the text used in the widget, by passing an object to the text property. Finally, other options change the way things are displayed, loaded and saved, and allow you to add callbacks that are executed at special moments of the widget's life. The table bellow shows all the parameters that can be passed to the NestedSortableWidget.
More Details about PaginationIn the demo I am showing the widget being used with pagination on a dataset of about 30 items. I put up a simple PHP page as a demo data source that will spit clusters of a bigger JSON with the whole data, according to the page requested. Optionally, you may simplify the data source and always reply with the whole dataset, pagination will still happen on the client side. Or your data source may be “greedy” and reply with a bigger chunk than the one requested. The component will be smart enough not to request any data it already has. Using Firebug in Firefox allows you to see very easily when and what data is being requested/returned. You will notice that in my demo a page won’t always have 5 items, sometimes it will have more: this is by design, as if I was to enforce only 5 items per page we would have to break hierarchies in half, what would be weird for the user and terrible to program as well. Initially, when the widget is loaded, it requests the first item of the page being loaded. If loading the third page with 5 elements per page, that would be the element with the zero-based index order of 15. But it will actually only display the first element after the one with index 15 that doesn’t have any parents - we will call it a ‘root’ element from now on. And it will display at least 5 items, since it will always show complete hierarchies. When the component needs some data to display a page, it will make an HTTP request with 2 parameters:
If those parameters are not supplied, your server side script should understand the widget is requesting the whole list (when there is no pagination or the greedy option is on). The server should reply with the data, in JSON format, that, besides the data itself, has the following parameters:
Note that the server should always return the whole data that the component wanted to display. Eg: if we request firstIndex = 10, count=5, the server could return firstIndex = 5, count=20, but not firstIndex = 5, count=8. Data SourceThere are two basic things you will need to implement on the server to be able to use the widget: Loading and Saving. This is what we are calling the "data source". LoadingLoading is basically fetching your list of items and sending it to the widget in JSON format. This is fairly easy, as there are lots of JSON libraries out there, all you will have to do is generate an array or equivalent in your server side language. If you want pagination on the server side, in order to only send the user the data he is going to display in the next page, you need to read the firstIndex and count request parameters that will be passed in and only return the data in that range, following the rules given in the More Details about Pagination section, of not breaking hierarchies. If you read Basic Usage and More Details about Pagination you should have a good idea of the format of the JSON data you need to return. You could look at the tests/widget folder at the widget's source archive for a simple implementation of the loading operation in PHP. SavingSaving should be pretty easy as well. By default, your script will be passed in an array/hash in the form of query string parameters, so you should be able to access it instantaneously in your server side script. I will give a PHP representation of an example which should make the format pretty obvious. This is what I call a "cluster" of data sent by the widget: /*
Represents the following disposition of 7 items
1
3
2
5
|- 4
|- 6
|- 7
*/
$REQUEST['nested-sortable-widget']['firstIndex'] = 0
$REQUEST['nested-sortable-widget']['count'] = 7
$REQUEST['nested-sortable-widget']['items'] =
array(
array(
"id" => 1
),
array(
"id" => 3
),
array(
"id" => 2
),
array(
"id" => 5,
"children" =>
array(
"id" => 4,
"children"=>
array(
array(
"id" => 6
),
array(
"id" => 7
)
)
}
)
)
);nested-sortable-widget is the default name for the parameter, but the parameter name will be the name of your widget (see name under configuration options). When pagination is enabled, the widget will only send to the server items in the pages that were loaded AND whose order was altered. There are two details your save script will need to be aware of then:
There is one trap at which you may fall when using pagination and when you save the order of your pages using numeric indexes: You will need to increment the order of all the elements following the items of the page being saved. This can be done by running a single SQL UPDATE. To make this section more colorful, I will provide a real life save method, from my WordPress implementation, that saves one of those clusters of items (the items in WordPress are "pages"). This is a recursive function, and $pages_array will initially receive the contents of $REQUEST['nested-sortable-widget']['items']. function save_page_order($pages_array, $parent_id = 0) {
global $wpdb;
$first_page = $wpdb->escape($pages_array[0]['id']);
$current_menu_order = get_post_field('menu_order', $first_page, 'db');
if(is_wp_error($current_menu_order)) return false;
if($parent_id === 0) {
//shifts the menu order for all the root pages after the ones we
//will alter
$num_root_pages = count($pages_array);
$query_ret = $wpdb->query("UPDATE $wpdb->posts SET menu_order = menu_order + $num_root_pages WHERE post_type = 'page' AND post_parent = 0 AND menu_order > $current_menu_order");
if($query_ret === false) return false;
}
foreach ($pages_array as $index => $page) {
$page_id = $wpdb->escape($page['id']);
$query_ret = $wpdb->query("UPDATE $wpdb->posts SET post_parent = '$parent_id', menu_order = '$current_menu_order' WHERE id ='$page_id'");
if($query_ret === false) return false;
$current_menu_order++;
if (is_array($page['children'])) {
//does it for the children as well
if (!$this->save_page_order($page['children'], $page_id)) return false;
}
}
return true;
}Known BugsAs of version 1.0, there are 2 minor cosmetic bugs, one for IE6 and the other in Safari. They are not show stoppers. There is another bug in Incremental mode, which is only a little annoying, so I don't recommend using incremental mode. Refer to the issues list for more details. Interface 1.2 was built for jQuery 1.1 and does have a few issues, specially with animations, when you use it with jQuery 1.2. So, with jQuery 1.2, you might encounter issues with the animation options in the NestedSortables, which are inherited from Interface. jQuery 1.2 was just released, lets hope Interface is updated to support it. The combination used in the demo is using jQuery 1.2 and Interface 1.2, and at least in those examples, doesn't generate any errors. The fadeOutHover option will be deactivated when using jQuery 1.2, due to one of those incompatibilities. TODO for next versions
|
Sign in to add a comment
What are the minor cosmetic bugs? Are they fixable with some extra css?
Thanks for great work! About widget: any plans for adding new records in the tree "on the fly"? From sub-form for example? Like it can be implemented in HTML version
.append('<li id="newitem" class=""><new item</li>') .SortableAddItem?(document.getElementById('newitem'));
Good luck!
Excellent script, is there a way we can add function to each item in the list?
i.e. make the title a link to edit the page, include a button to delete the page, or a button to suppress the page.
Tom
@thepencompany
This is how I made to trigger a function on each element. Some parts of the code were cut, the important thing is the list_loaded_callback function:
function list_loaded_callback () {
}
$('#nestedwidget').NestedSortableWidget? ({
});
shouldNestItem : function(e, precedingItem ) {
//Here is how I limit the length of items that can have a selected item to be //nested. if(jQuery(e.nestedSortCfg.nestingTag + "." + e.nestedSortCfg.nestingTagClass.split(" ").join(".") + " > ." + e.sortCfg.accept.split(" ").join(".") + ":first-child", jQuery.iDrag.dragged).length>=2)return false; ...//With this code and the rest of the function we can make a three level nested //list.
This is correct or I am doing some damage to the complete code?
Hy, thanks for this great plugin. By the way, I would need a complete (if possible) example of data source saving ? I didn't find it in the test folder for the NSW.
Thanks,
Salim,
Hi.
Thank you for your grear work. I need to disable "save order" buttons in order to assign that functionality to the onChange event (more intuitive in my opinion). How can I do disabling? I see no option in documentation.
Thank you again.
greetings,
how might you go about restricting nesting? that is, i have a list that i want to sort but do not want to represent any parent/child relationship. in the NestedSortable? package, you can limit nesting with the noNestingClass but i have not been able to suss out how to make that work with the widget. any pointers?
thanks in advance.
yours,
steve
found it! the class to restrict nesting would be nsw-item
ciao.
Bernard, thanks for creating this excellent plug-in.
I have a request for a feature I don't has been mentioned before, but might be generally useful:
I want a user to be able to choose items from a palette of options, and to able to drag them, one by one, into the main tree. The item in palette should remain, so that the same one can be used repeatedly.
Thus a user would be able to build up a tree of their design from a single root node, by dragging in items from the palette. Some of the items in the palette would be classed as "leaf nodes" (hence not accepting child nodes), others as branch nodes (hence accepting children).
If anyone has seen this functionality out there, please could you link to it?
Thanks, Stephen
Hi,
Greetings about the plugin!
I am missing here the loading posibility directly from the page. Is there a way to load the Items not only by loadUrl - some way like setting the Items directly with JSON object?
Thanks, Angel
Great plugin! would be even better with 'on the fly' editing, as was previously mentioned. Have a way to dynamically add/remove items, and option to notify the server of these events
Hi,
Thank you for your grear work. I need to add a row in the table. Is there a way? how can I send all data (not only the row ID) to saveUrl page?
I am confused. I am able to use to sucessfully however how can you refresh the widget on the page when you are coming back from your save page? I am using jsp. I thought I could just call the nested sortable again on the callback for the on save but that does not work...
Nice work! Can you add reloadTree method, for reloading on the fly?
hello,
Excellent widget ! how to do to turn it into a collaspable version ? Thank you,
Hi,
Nice work with the Widget. I just have the known problem with JQ 1.3.2 and IE. Once dragged an item cannot be dragged anymore. I wanted to make a quick-fix with the following code:
save: function() { var onSuccess = function(e, returnText) { jQuery.NestedSortableWidget.loadData(e, 0);but I cannot get it to work. Does anyone know how to reload the data from the server after it has been saved? My hope is that the dragging is possible with all items then.
i'd like to bump the "edit/save on the fly" request.
thanks for writing this. pretty stoked about using it.
Is there a way of not being able to add a folder into another folder? I would love for this to work with it only being one level deep.