diff --git a/.buildpath b/.buildpath
new file mode 100644
index 0000000..c1ec64b
--- /dev/null
+++ b/.buildpath
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/.project b/.project
new file mode 100644
index 0000000..a9dc0b2
--- /dev/null
+++ b/.project
@@ -0,0 +1,29 @@
+
+
+ SortableGridField
+
+
+
+
+
+ org.eclipse.wst.jsdt.core.javascriptValidator
+
+
+
+
+ org.eclipse.wst.validation.validationbuilder
+
+
+
+
+ org.eclipse.dltk.core.scriptbuilder
+
+
+
+
+
+ ca.edchipman.silverstripepdt.SilverStripeNature
+ org.eclipse.php.core.PHPNature
+ org.eclipse.wst.jsdt.core.jsNature
+
+
diff --git a/.settings/.jsdtscope b/.settings/.jsdtscope
new file mode 100644
index 0000000..2f6f983
--- /dev/null
+++ b/.settings/.jsdtscope
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.settings/org.eclipse.php.core.prefs b/.settings/org.eclipse.php.core.prefs
new file mode 100644
index 0000000..f39b247
--- /dev/null
+++ b/.settings/org.eclipse.php.core.prefs
@@ -0,0 +1,3 @@
+eclipse.preferences.version=1
+include_path=0;/SortableGridField\u00055;ca.edchipman.silverstripepdt.LANGUAGE
+silverstripe_version=SS2.4
diff --git a/.settings/org.eclipse.wst.jsdt.ui.superType.container b/.settings/org.eclipse.wst.jsdt.ui.superType.container
new file mode 100644
index 0000000..3bd5d0a
--- /dev/null
+++ b/.settings/org.eclipse.wst.jsdt.ui.superType.container
@@ -0,0 +1 @@
+org.eclipse.wst.jsdt.launching.baseBrowserLibrary
\ No newline at end of file
diff --git a/.settings/org.eclipse.wst.jsdt.ui.superType.name b/.settings/org.eclipse.wst.jsdt.ui.superType.name
new file mode 100644
index 0000000..05bd71b
--- /dev/null
+++ b/.settings/org.eclipse.wst.jsdt.ui.superType.name
@@ -0,0 +1 @@
+Window
\ No newline at end of file
diff --git a/_config.php b/_config.php
new file mode 100644
index 0000000..15c5adc
--- /dev/null
+++ b/_config.php
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/code/extensions/GridFieldSortableObject.php b/code/extensions/GridFieldSortableObject.php
new file mode 100644
index 0000000..a560f8b
--- /dev/null
+++ b/code/extensions/GridFieldSortableObject.php
@@ -0,0 +1,135 @@
+'Int'
+ );
+
+ protected static $sortable_classes = array();
+ protected static $many_many_sortable_relations = array();
+ protected static $sort_dir = "ASC";
+
+
+ public static function set_sort_dir($dir) {
+ self::$sort_dir=$dir;
+ }
+
+
+ public static function add_sortable_class($className) {
+ if(!self::is_sortable_class($className)) {
+ Object::add_extension($className, 'GridFieldSortableObject');
+
+ self::$sortable_classes[]=$className;
+ }
+ }
+
+ public static function add_sortable_classes(array $classes) {
+ foreach($classes as $class) {
+ self::add_sortable_class($class);
+ }
+ }
+
+ public static function add_sortable_many_many_relation($ownerClass, $componentName) {
+ list($parentClass, $componentClass, $parentField, $componentField, $table)=singleton($ownerClass)->many_many($componentName);
+
+ Object::add_static_var($ownerClass, 'many_many_extraFields', array(
+ $componentName=>array(
+ 'SortOrder'=>'Int'
+ )));
+
+
+ if(!isset(self::$many_many_sortable_relations[$componentClass])) {
+ self::$many_many_sortable_relations[$componentClass] = array();
+ }
+
+
+ self::$many_many_sortable_relations[$componentClass][$parentClass]=$table;
+ self::add_sortable_class($componentClass);
+ }
+
+ public static function remove_sortable_class($class) {
+ Object::remove_extension($class, 'GridFieldSortableObject');
+ }
+
+ public static function is_sortable_class($classname) {
+ if(in_array($classname, self::$sortable_classes)) {
+ return true;
+ }
+
+ foreach(self::$sortable_classes as $class) {
+ if(is_subclass_of($classname, $class)) {
+ return true;
+ }
+ }
+
+
+ return Object::has_extension($classname, 'GridFieldSortableObject');
+ }
+
+ public static function is_sortable_many_many($componentClass, $parentClass=null) {
+ $map=self::$many_many_sortable_relations;
+ if($parentClass===null) {
+ return isset($map[$componentClass]);
+ }else {
+ if(isset($map[$componentClass])) {
+ return isset($map[$componentClass][$parentClass]);
+ }
+
+ return false;
+ }
+
+ }
+
+ public static function get_join_tables($classname) {
+ if(isset(self::$many_many_sortable_relations[$classname])) {
+ return self::$many_many_sortable_relations[$classname];
+ }
+
+ return false;
+ }
+
+ public function augmentSQL(SQLQuery &$query) {
+ if(empty($query->select) || $query->delete || in_array("COUNT(*)", $query->select) || in_array("count(*)", $query->select)) {
+ return;
+ }
+
+
+ $sort_field=false;
+ if($join_tables=self::get_join_tables($this->owner->class)) {
+ foreach($query->from as $from) {
+ if($sort_field) {
+ break;
+ }
+
+ foreach($join_tables as $join_table) {
+ if(stristr($from,$join_table)) {
+ $sort_field="\"$join_table\".\"SortOrder\"";
+
+ if(isset($query->select['SortOrder'])) {
+ $query->select['SortOrder']="\"{$this->owner->class}\".SortOrder AS LocalSort";
+ }
+
+ break;
+ }
+ }
+ }
+ }
+
+
+ if(!$sort_field) {
+ $sort_field="\"SortOrder\"";
+ }
+
+ if(!$query->orderby || ($query->orderby==$this->owner->stat('default_sort'))) {
+ $query->orderby="$sort_field ".self::$sort_dir;
+ }
+ }
+
+ public function onBeforeWrite() {
+ if(!$this->owner->ID) {
+ if($peers=DataList::create($this->owner->class)) {
+ $this->owner->SortOrder=$peers->Count()+1;
+ }
+ }
+ }
+}
+?>
\ No newline at end of file
diff --git a/code/forms/GridFieldSortableRows.php b/code/forms/GridFieldSortableRows.php
new file mode 100644
index 0000000..51e7f61
--- /dev/null
+++ b/code/forms/GridFieldSortableRows.php
@@ -0,0 +1,143 @@
+State->GridFieldSortableRows;
+ if(!is_bool($state->sortableToggle)) {
+ $state->sortableToggle=false;
+ }
+
+
+
+ if(Object::has_extension($gridField->getModelClass(), 'GridFieldSortableObject')) {
+ //Sort order toggle
+ $sortOrderToggle=new GridField_FormAction($gridField, 'sortablerows_toggle', 'Allow drag and drop re-ordering', 'saveGridRowSort', null);
+ $sortOrderToggle->addExtraClass('sortablerows_toggle');
+
+ //Disable Pagenator
+ if($gridField->getConfig()->getComponentByType('GridFieldPaginator')) {
+ $disablePagenator=new GridField_FormAction($gridField, 'sortablerows_disablepagenator', 'Disable Pagenator', 'sortableRowsDisablePaginator', null);
+ $disablePagenator->addExtraClass('sortablerows_disablepagenator');
+ }else {
+ $disablePagenator=null;
+ }
+
+ $forTemplate=new ArrayData(array(
+ 'SortableToggle'=>$sortOrderToggle,
+ 'PagenatorToggle'=>$disablePagenator,
+ 'Checked'=>($state->sortableToggle==true ? ' checked="checked"':'')
+ ));
+
+
+ //Inject Requirements
+ Requirements::css('SortableGridField/css/GridFieldSortableRows.css');
+ Requirements::javascript('SortableGridField/javascript/GridFieldSortableRows.js');
+
+
+ return array(
+ 'header'=>$forTemplate->renderWith('GridFieldSortableRows', array('Colspan'=>count($gridField->getColumns())))
+ );
+ }
+
+ return array();
+ }
+
+ /**
+ * Return a list of the actions handled by this action provider.
+ * @param GridField $gridField Grid Field Reference
+ * @return {array} Array with action identifier strings.
+ */
+ public function getActions($gridField) {
+ if(Object::has_extension($gridField->getModelClass(), 'GridFieldSortableObject')) {
+ if($gridField->getConfig()->getComponentByType('GridFieldPaginator')) {
+ return array('saveGridRowSort', 'sortableRowsDisablePaginator');
+ }else {
+ return array('saveGridRowSort');
+ }
+ }
+
+ return array();
+ }
+
+ /**
+ * Handle an action on the given grid field.
+ * @param {GridField} $gridField Grid Field Reference
+ * @param {string} $actionName Action identifier, see {@link getActions()}.
+ * @param {array} $arguments Arguments relevant for this
+ * @param {array} $data All form data
+ */
+ public function handleAction(GridField $gridField, $actionName, $arguments, $data) {
+ $state=$gridField->State->GridFieldSortableRows;
+ if(!is_bool($state->sortableToggle)) {
+ $state->sortableToggle=false;
+ }else if($state->sortableToggle==true) {
+ if($gridField->getConfig()->getComponentsByType('GridFieldPaginator')) {
+ $gridField->getConfig()->removeComponentsByType('GridFieldPaginator');
+ $gridField->getConfig()->addComponent(new GridFieldFooter());
+ }
+
+ $gridField->getConfig()->removeComponentsByType('GridFieldFilterHeader');
+ $gridField->getConfig()->removeComponentsByType('GridFieldSortableHeader');
+ }
+
+
+ if(Object::has_extension($gridField->getModelClass(), 'GridFieldSortableObject')) {
+ if($actionName=='savegridrowsort') {
+ return $this->saveGridRowSort($gridField, $data);
+ }
+ }
+ }
+
+ /**
+ * Handles saving of the row sort order
+ * @param {GridField} $gridField Grid Field Reference
+ * @param {array} $data Data submitted in the request
+ */
+ private function saveGridRowSort(GridField $gridField, $data) {
+ if(empty($data['Items'])) {
+ user_error('No items to sort', E_USER_ERROR);
+ }
+
+ $className=$gridField->getModelClass();
+ $ownerClass=$gridField->Form->Controller()->class;
+
+ $many_many=GridFieldSortableObject::is_sortable_many_many($className);
+ if($many_many) {
+ $candidates=singleton($ownerClass)->many_many();
+ if(is_array($candidates)) {
+ foreach($candidates as $name => $class)
+ if($class==$className) {
+ $relationName=$name;
+ break;
+ }
+ }
+
+ if(!isset($relationName)) {
+ return false;
+ }
+
+ list($parentClass, $componentClass, $parentField, $componentField, $table)=singleton($ownerClass)->many_many($relationName);
+ }
+
+
+ $data['Items']=explode(',', $data['Items']);
+ for($sort=0;$sortSortOrder=$sort;
+ $obj->write();
+ }
+ }
+ }
+}
+?>
\ No newline at end of file
diff --git a/css/GridFieldSortableRows.css b/css/GridFieldSortableRows.css
new file mode 100644
index 0000000..3141843
--- /dev/null
+++ b/css/GridFieldSortableRows.css
@@ -0,0 +1,7 @@
+.cms table.ss-gridfield-table thead tr th.sortablerowsheading {
+ border-bottom: 1px solid rgba(0, 0, 0, 0.1);
+}
+
+.cms table.ss-gridfield-table thead tr th.sortablerowsheading .gridfield-sortablerows button {
+ display: none;
+}
\ No newline at end of file
diff --git a/javascript/GridFieldSortableRows.js b/javascript/GridFieldSortableRows.js
new file mode 100644
index 0000000..da1ef28
--- /dev/null
+++ b/javascript/GridFieldSortableRows.js
@@ -0,0 +1,43 @@
+(function($) {
+ $('.ss-gridfield .gridfield-sortablerows input').entwine({
+ onmatch: function() {
+ var refCheckbox=$(this);
+
+ var gridField=$(this).getGridField();
+ gridField.find('tbody').sortable({
+ disabled: ($(this).is(':checked')==false),
+ helper: function(e, ui) {
+ //Maintains width of the columns
+ ui.children().each(function() {
+ $(this).width($(this).width());
+ });
+
+ return ui;
+ },
+ update: function(event, ui) {
+ var dataRows=[];
+ var gridItems=gridField.getItems();
+ var button=refCheckbox.parent().find('.sortablerows_toggle');
+
+
+ for(var i=0;i
+
+
\ No newline at end of file