Sorting Listings: Difference between revisions

From Littledamien Wiki
Jump to navigation Jump to search
No edit summary
 
(26 intermediate revisions by the same user not shown)
Line 1: Line 1:
==Sections CMS==
[[Category:CMS_Documentation]] [[Category:Littled Libraries]] [[Category:JQuery/AJAX]] [[Category:Web Development]]
*Hostmgr > Sections
== Overview ==
*Is Sortable: checked
 
*Sorting URI: <code>/hostmgr/_ajax/utils/resort.php</code> (default shared AJAX handler script)
A jQuery plugin that enables drag-n-drop resorting of listings displayed in the browser.
==JavaScript==
 
Shared JS library must be included on the page:
=== Future development ===
 
Currently the library works with the Littledamien and Damien Jay sites. It works with different content types, but is hard-coded to expect certain behaviors from the AJAX script that commits the changes.
 
The goal is to refactor the `resort.js` plugin to be more generic and customizable.
 
The goal on the AJAX handler side is to create a reusable Symfony bundle.
 
== Content Properties CMS ==
 
'''CMS''' > '''Content Properties'''
 
* '''Is Sortable''': checked
* '''Sorting URI''': <code>/hostmgr/_ajax/utils/resort.php</code> (default shared AJAX handler script)
 
== JavaScript ==
 
Sorting logic is defined in `littled/resort.js` which is wrapped up in [https://github.com/dbarchowsky/littled.js.git `littled.js`].
 
Example:
<syntaxhighlight lang="javascript">
<syntaxhighlight lang="javascript">
/scripts/littled/listings.js
$(document).ready(function() {
/* options can be passed as first argument to resort() */
$('.listings table:first').resort();
});
</syntaxhighlight>
</syntaxhighlight>
Assign the sorting function to the <code>LITTLED.setSortables</code> property:
 
<syntaxhighlight lang="javascript">
=== Settings ===
LITTLED.init({ setSortables: [SORTING_FUNCTION]});
 
LITTLED.setSortables();
* `uris`
** `resort`: URL of the AJAX script that will commit the resorted listings. (default: `/_ajax/utils/resort.php`)
* `dom`
** `listings_container`: The listings container selector. (default: `'.listings'`)
** `sortable_selector`: Selector used to define sortable elements. (default: `tr.rec_row`)
* `keys`
** `record_id`: Name of the key storing the id of the record(s) being repositioned. (default: `'id'`) 
** `position_offset`: Name of the key that stores the position of the first record currently being displayed relative to the first record in the entire set of matching listings. (default: `'po'`)
** `content_type`: Name of the entity represented in the listings. (default: `'type'`)
** `filter`: (Optional) name of the key use to pass filters to apply to the entities, e.g. if they belong to a group. (default: `'filter'`) ''(Note that it's singular.)''
** `filters`: (Optional) list of keys holding filter values applied to the list of sortable items. These keys correspond to `data-` attributes of the sortable container element. (default: `[]`) ''(Note that it's plural.)''
 
=== CSRF ===
 
Passing a CSRF token isn't handled by the plugin directly.
 
See [[CSRF_Validation_in_AJAX_With_Symfony#JavaScript.2FjQuery|CSRF Validation in AJAX With Symfony]]
 
== Markup ==
 
When loading modal dialogs and refreshing listings content, the library looks to the `data-po` attribute of `.listings table:first` by default.
 
=== Default markup ===
 
<syntaxhighlight lang="text">
|
+-- <div class="listings">
    |
    +-- <table data-po="n">
          |
          +-- <tr>
          |    |
          |    +-- <th>
          |    |
          |    (...)
          |
          +-- <tr class="rec-row">
          |
          (...)
</syntaxhighlight>
</syntaxhighlight>
A JQuery sorting initialization routine would be something like this:
 
<syntaxhighlight lang="javascript">
=== Example of alternate markup ===
$('#listings-table').sortable({
 
    items: 'tr.rec-row',
<syntaxhighlight lang="text" enclose="div">
    start: function(event, ui) {
|
        var id = ui.helper.attr('data-rid');
+-- <ul class="list-group" data-po="n" data-type="Foo" data-gid="filter_value">
        if (!id) {return;} /* parent record id not set */
|    |
|    +-- <li class="list-group-item" data-id="n">
        var $c = $('#pages-row-' + id);
|    |    |
        if ($c.length < 1) {return;} /* child row not available */
|    |    +-- <div>
|    |    |
        /* hide children as the parent is dragged */
|    |    (...)
        if ($c.is('visible')) {
|    |
            $c.addClass('reopen');
|    (...)
            $c.hide();
|
        }
+-- (...)
    },
|
    update: LITTLED.Listings.resort
+-- <div id="csrf-token" data-token="token_string"></div>
});
|
(...)
</syntaxhighlight>
</syntaxhighlight>
A default resort initialization is defined within the LITTLED.Listings library:
 
<syntaxhighlight lang="javascript">
Configure sorting for the example above with:
LITTLED.Listings.bindResort()
 
</syntaxhighlight>
To use a default resort (the selectors referenced above would need to be present with the page’s markup):
<syntaxhighlight lang="javascript">
<syntaxhighlight lang="javascript">
$(document).ready(function() {
$(document).ready(function() {
    LITTLED.init({ setSortables: LITTLED.Listings.bindResort });
return this.resort({
    LITTLED.setSortables();
uris: { resort: '/resort' },
dom: {
listings_container: '.tutorial-group',
sortable_selector: 'li'
},
keys: {
filters: ['gid']
}
});
});
});
</syntaxhighlight>
</syntaxhighlight>
For albums, LITTLED.Gallery.bindAlbumListingsHandlers will initialize resorting for the listings.
 
==PHP==
== PHP ==
<code>/_classes/site_content/cache_class.php</code>
 
=== Input (proposed) ===
 
* `po` Position offset of the first record in the listings from the first record in the entire unfiltered collection.
* `id` Array of record ids resorted to the desired arrangement.
* `type` Name of the entity represented in the listings.
* `filters` Associative array of filters to apply, for example if resorting only those items in a certain group.
 
=== Output ===
 
* Output as JSON by default.
* `status` Simple string containing a plain English result to be displayed in the browser as confirmation of the operation.
* `error` String containing error message if any errors were encountered.
 
=== CSRF ===
 
See [[CSRF_Validation_in_AJAX_With_Symfony#Symfony_controller|CSRF Validation in AJAX With Symfony]] for a method for checking a CSRF token passed to the AJAX script in the request headers.
 
=== Littledamien configuration ===
 
`/_classes/site_content/cache_class.php`


Add include directives for both the parent page’s content and content filter classes:
Add include directives for both the parent page’s content and content filter classes:
Line 54: Line 141:
require_once (APP_CLASS_DIR."site_filters/press_filters_class.php");
require_once (APP_CLASS_DIR."site_filters/press_filters_class.php");
</syntaxhighlight>
</syntaxhighlight>
Update the set_filters() routine to include the filter class representing the page’s content type.
Update the set_filters() routine to include the filter class representing the page’s content type.


<code>/_classes/site_content/cache_class.php</code>
`/_classes/site_content/cache_class.php`


Potentially no changes need to be made to this class on a content-type-by-content-type basis as long as the default action for all content types is
Potentially no changes need to be made to this class on a content-type-by-content-type basis as long as the default action for all content types is


<syntaxhighlight lang="php">
<syntaxhighlight lang="php">
if ($this->filters instanceof filters_collection_class)
if ($this->filters instanceof filters_collection_class) {
{
     $this->filters->parse_filters();
     $this->filters->parse_filters();
     $this->filters->page_len->value = null; /* get all available records */
     $this->filters->page_len->value = null; /* get all available records */
}
}
</syntaxhighlight>
</syntaxhighlight>
If some other action needs to be taken for a specific content type then a new case would need to be created in the retrieve_section_properties() routine.
If some other action needs to be taken for a specific content type then a new case would need to be created in the retrieve_section_properties() routine.


Line 75: Line 163:
</syntaxhighlight>
</syntaxhighlight>


==HTML/CSS==
== MySQL ==
List selectors, CSS classes, and HTML element attributes necessary for resorting.
 
===Sortable container attributes===
(either TABLE or DIV)
*Selector used to identify the container element holding the sortable elements.
**<code>id="listings-table"</code> for a TABLE element
**<code>id="listings-container"</code> for a DIV element
*<code>data-tid</code> Value is the content type of the records contained in the table that can be sorted.
*<code>data-p</code> Index of the first record displayed on the page. Starts with 1. (For page 1 the value is 1. For page 10 with 5 records per page the <code>data-p</code> value is 51.)
tr element attributes


===Sortable element attributes===
Required resortable entity properties (table columns):
*<code>id="#rr-[SECTION_ID]-[RECORD_ID]"</code> For each sortable row.
*<code>class="rec-row"</code> For each row that is sortable.
*<code>data-rid</code> Record id value.
*<code>id="pages-row-[XXX]"</code> (Albums only) ID of <tr> elements where [XXX] is the id of the album.
===Gallery listings===
*Sortable element selector is <code>tr.page-row</code> for listings within tables, and <code>.page-cell</code> for listings within DIV containers.
*The parent container attributes are rolled into
<syntaxhighlight lang="php">
<? include (COMMON_TEMPLATE_DIR."forms/images/gallery_listings_container.php"); ?>
</syntaxhighlight>
*When using the <code>gallery_listings_container.php</code> template it's necessary to load the gallery listings content on page load:
<syntaxhighlight lang="javascript">
$(document).ready(function(){
LITTLED.Gallery.retrieveGallery({pid: <?=$input->id->value?>});
});
</syntaxhighlight>
*<code>LITTLED.Gallery.retrieveGallery()</code> in turn calls <code>LITTLED.Gallery.bindGalleryHandlers()</code> which in turn calls <code>LITTLED.Gallery.configureSorting()</code>.


==MySQL==
* `id` column containing a unique id for the record.
Tables with sortable records must contain
* `slot` column holding the value of the record’s position in the listings relative to all other records.
*An id column containing a unique id for the record.
*A slot column holding the value of the record’s position in the listings relative to all other records.
[[Category:JQuery/AJAX]]
[[Category:CMS_Documentation]]

Latest revision as of 13:37, 2 March 2015

Overview[edit]

A jQuery plugin that enables drag-n-drop resorting of listings displayed in the browser.

Future development[edit]

Currently the library works with the Littledamien and Damien Jay sites. It works with different content types, but is hard-coded to expect certain behaviors from the AJAX script that commits the changes.

The goal is to refactor the resort.js plugin to be more generic and customizable.

The goal on the AJAX handler side is to create a reusable Symfony bundle.

Content Properties CMS[edit]

CMS > Content Properties

  • Is Sortable: checked
  • Sorting URI: /hostmgr/_ajax/utils/resort.php (default shared AJAX handler script)

JavaScript[edit]

Sorting logic is defined in littled/resort.js which is wrapped up in littled.js.

Example:

$(document).ready(function() {
	/* options can be passed as first argument to resort() */
	$('.listings table:first').resort();
});

Settings[edit]

  • uris
    • resort: URL of the AJAX script that will commit the resorted listings. (default: /_ajax/utils/resort.php)
  • dom
    • listings_container: The listings container selector. (default: '.listings')
    • sortable_selector: Selector used to define sortable elements. (default: tr.rec_row)
  • keys
    • record_id: Name of the key storing the id of the record(s) being repositioned. (default: 'id')
    • position_offset: Name of the key that stores the position of the first record currently being displayed relative to the first record in the entire set of matching listings. (default: 'po')
    • content_type: Name of the entity represented in the listings. (default: 'type')
    • filter: (Optional) name of the key use to pass filters to apply to the entities, e.g. if they belong to a group. (default: 'filter') (Note that it's singular.)
    • filters: (Optional) list of keys holding filter values applied to the list of sortable items. These keys correspond to data- attributes of the sortable container element. (default: []) (Note that it's plural.)

CSRF[edit]

Passing a CSRF token isn't handled by the plugin directly.

See CSRF Validation in AJAX With Symfony

Markup[edit]

When loading modal dialogs and refreshing listings content, the library looks to the data-po attribute of .listings table:first by default.

Default markup[edit]

|
+-- <div class="listings">
     |
     +-- <table data-po="n">
          |
          +-- <tr>
          |    |
          |    +-- <th>
          |    |
          |    (...)
          |
          +-- <tr class="rec-row">
          |
          (...)

Example of alternate markup[edit]

|
+-- <ul class="list-group" data-po="n" data-type="Foo" data-gid="filter_value">
|    |
|    +-- <li class="list-group-item" data-id="n">
|    |    |
|    |    +-- <div>
|    |    |
|    |    (...)
|    |
|    (...)
|
+-- (...)
|
+-- <div id="csrf-token" data-token="token_string"></div>
|
(...)

Configure sorting for the example above with:

$(document).ready(function() {
	return this.resort({
		uris: { resort: '/resort' },
		dom: {
			listings_container: '.tutorial-group',
			sortable_selector: 'li'
		},
		keys: {
			filters: ['gid']
		}
	});
});

PHP[edit]

Input (proposed)[edit]

  • po Position offset of the first record in the listings from the first record in the entire unfiltered collection.
  • id Array of record ids resorted to the desired arrangement.
  • type Name of the entity represented in the listings.
  • filters Associative array of filters to apply, for example if resorting only those items in a certain group.

Output[edit]

  • Output as JSON by default.
  • status Simple string containing a plain English result to be displayed in the browser as confirmation of the operation.
  • error String containing error message if any errors were encountered.

CSRF[edit]

See CSRF Validation in AJAX With Symfony for a method for checking a CSRF token passed to the AJAX script in the request headers.

Littledamien configuration[edit]

/_classes/site_content/cache_class.php

Add include directives for both the parent page’s content and content filter classes:

require_once (APP_CLASS_DIR."site_content/press_class.php");
require_once (APP_CLASS_DIR."site_filters/press_filters_class.php");

Update the set_filters() routine to include the filter class representing the page’s content type.

/_classes/site_content/cache_class.php

Potentially no changes need to be made to this class on a content-type-by-content-type basis as long as the default action for all content types is

if ($this->filters instanceof filters_collection_class) {
    $this->filters->parse_filters();
    $this->filters->page_len->value = null; /* get all available records */
}

If some other action needs to be taken for a specific content type then a new case would need to be created in the retrieve_section_properties() routine.

Paging is handled with

include (COMMON_TEMPLATE_DIR."framework/navigation/listings_page_links.php");

MySQL[edit]

Required resortable entity properties (table columns):

  • id column containing a unique id for the record.
  • slot column holding the value of the record’s position in the listings relative to all other records.