65.9K
CodeProject 正在变化。 阅读更多。
Home

jQueryUI 排序示例

starIconstarIconstarIconstarIconstarIcon

5.00/5 (4投票s)

2014年1月10日

CPOL

7分钟阅读

viewsIcon

23889

downloadIcon

390

一个非常完整的 PHP 示例,演示了如何使用 jQuery 的“sortable”方法以及单个 DB 更新。

jQuerySort Demo Screen

引言

jQuery UI 使得在网页上实现可排序元素变得非常容易,获取排序数据的结果也很简单,但要弄清楚哪个元素被重新定位到哪里,则需要一些工作。如果能有一种方法来执行单个数据库 UPDATE 来记录新元素顺序,而不是遍历一系列记录并单独更新每个记录,那也会很好。

本文(及随附代码)演示了如何创建可排序元素,在它们排序时对事件做出反应,通过 AJAX 将数据发送到您的服务器,并使用算法处理数据以找出哪个元素被移动……以及移动到哪里。

它还向您展示了一种配置存储元素数据的表并计算元素索引的方法,以便可以通过单个查询更新排序顺序。

使用代码

要运行代码,您需要一台带有 PHP5.4 的服务器或配置正确的内置 PHP 服务器的 IDE(源代码中包含 NetBeans 项目文件,但您可以将代码导入到其他 IDE 中)。它尚未达到生产就绪状态……将其视为您自己项目的模板。Profile.phpSortableElement.php 类将被您自己的应用程序类替换。

随附代码包含一个初始化模块(index.php),它只是为演示创建数据并将其放入会话中。文件 sortExample.html 是主页面,由 style.css 进行样式设置。

重要的文件是 sortable.jsSortController.php。这些文件处理浏览器和服务器之间的通信。控制器类包含用于处理组件排序时 jQuery 提供的序列化数据的逻辑。

首先。这个 javascript 函数初始化包含您的可排序元素的容器(在本例中是一个 div)。它将在文档加载时调用。

function initSortableContainer()
   {
   $(function()
      {
      $("#SortableElements").sortable(
         {
         cursor: "move",
         stop: handleReorderElements
         });
      });
   }

 

函数 handleReorderElements() 向服务器发出 AJAX 调用。initSortableContainer() 函数(上方)将“stop”事件绑定到包含可排序元素的 div,指定在排序操作完成时调用此函数。使用 jQuery 函数 sortable( "serialize") 获取所有可排序元素 ID 及其新顺序的参数/值字符串。请参阅以下部分,了解如何创建可排序元素的 ID,以便其正常工作。

function handleReorderElements()
   {
   var url = 'controllers/SortController.php';
   var fieldData = $( "#SortableElements" ).sortable( "serialize" );
   fieldData += "&action=reorderElements";

   var posting = $.post( url, fieldData);
   posting.done( function( data)
      {
      reorderElementsResponse( data );
      });
   }

 

此函数是 AJAX 回调处理程序。在大多数应用程序中,您无需在此处执行任何操作,只需处理错误即可。对于演示,控制器返回排序处理的结果。如果没有错误,该函数将在页面上显示此结果。

function reorderElementsResponse( data )
   {
   if (data.FAIL === undefined) // Everything's cool!
      {
      $("#Message").html( data.resultString + data.itemIndexString );
      }
   else
      {
      alert( "Bad Clams!" );
      }
   }

 

重要提示:格式化 HTML 元素 ID

可排序元素的 ID 必须使用通用名称、破折号或下划线以及 ID 号来创建。如果您查看文件 index.php,您会看到每个可排序元素都使用 ID id='SortableElement_" . $element->getId() 构造,以生成编号的 ID 标签,例如 SortableElement_1、SortableElement_2、SortableElement_3 等。传递给服务器的字符串将如下所示:SortableElement[]=1&SortableElement[]=2&SortableElement[]=3 等,并实际上将被 PHP 解释为数组。

SortController.php 中,我们可以使用 $sortIdArray = $_POST["SortableElement"]; 获取此数组
相当容易!

 

处理数据

SortController.php 接收来自页面的 AJAX 请求。它检索名称/值对并将其存储在变量 $sortIdArray 中。然后它从会话中拉出原始数组,并将键(即元素 ID)提取到一个单独的数组中

$sortList = $profile->getSortableElementList();
$originalIdList = array_keys( $sortList );

接下来,调用一个函数来比较数组以确定哪个元素被移动。函数 getArrayDiff( $originalKeys, $newKeys) 被传递原始 ID 数组和修改后的 ID 数组。它比较每个原始 ID 和修改后的 ID,直到找到一对不匹配的。其中一个 ID 将是被移动的 ID。为了弄清楚是哪个,该函数从两个数组中删除原始 ID,然后再次比较这两个数组。有趣的是,当移动的 ID 被删除时,两个数组将变得相同。由于第一次测试正确的可能性为 50%,因此只需执行一次删除/比较。如果第一次比较时数组不相同,那么另一个 ID 就是被移动的 ID。所以……当函数结束时,它返回被移动的 ID(如果没有 ID 被移动,则返回 -1)。跟踪函数以了解其工作原理,如果交换了两个相邻元素,请注意下面的警告……

一旦确定了被移动的 ID,我们需要找出它被移动到了哪里。在初始化几个变量以处理移动到数组的开头或结尾之后,代码遍历修改后的数组以找到被移动的 ID,存储**前一个**和**后一个** ID 值,然后……

 

表结构

是时候回顾一下并讨论允许单个更新存储修改后的 ID 顺序的表结构了。秘诀是将您的索引字段(在本例中为 ItemIndex)定义为 DOUBLE 类型。这样,我们就不必强制迭代对象家族的所有记录并更新索引以反映新顺序,我们只需将 ItemIndex 更改为**前一个**和**后一个** ItemIndex 值之间的一个(可能)小数。例如,如果我们将 ItemIndex 为 8 的记录移动到 ItemIndex 为 3 和 4 的记录之间,我们只需将移动项的索引更新为 3.5。您可能会认为最终会失去细分索引的能力;精度会耗尽,这是正确的。然而,在实际中,您可以在精度耗尽之前细分 DOUBLE 超过 1,000 次。在实际使用中,这可能永远不会发生,但请参阅下面的注释以了解如何处理这种可能性。

您需要确保在向表中插入新记录时,将 ItemIndex 字段设置为高于当前最大 ItemIndex 的值(且大于 0)。类似于以下内容

public function insert( SortableElement $sortableElement )
   {
   $sql = "SELECT MAX(ItemIndex) as Result
           FROM sortable_elements
           WHERE OrganizationId = " . $sortableElement->getOrganizationId();

   $itemIndex = $this->m_FSPDO->executeFunction( $sql );
   if ($itemIndex === null)
      {
      $itemIndex = 10;
      }

   $itemIndex += 10;

   $sql = "INSERT INTO sortable_elements ( OrganizationId, Status, Title, HolderId, HolderAccountType, ItemIndex)
           VALUES ( :organizationId, :status, :title, :holderId, :holderAccountType, :itemIndex)";

   $sortableElement->setId( null );
   $parameters = $sortableElement->getAllParameters( true );
   $parameters[':itemIndex'] = $itemIndex;

   $sortableElement->setId( $this->m_FSPDO->executeInsert( $sql, $parameters ) );
   }

*这使用两步法,但根据您的数据库和偏好,您可以使用存储过程。


通过这种方式配置表,剩下的就是计算前一个和后一个 ItemIndex 之间的值,同时处理将元素移动到列表开头(因此需要将 ItemIndex 字段设置为大于零的值)或列表末尾的情况

if ($previousKey === 0)
   {
   $itemIndex = (double)((double)(reset( $sortList )->getItemIndex() / 2));
   }
else if ($nextKey === INF)
   {
   $itemIndex = (double)((double)(end( $sortList )->getItemIndex() + 10));
   }
else
   {
   $lowIndex = (double)$sortList[(int)$previousKey]->getItemIndex();
   $highIndex = (double)$sortList[(int)$nextKey]->getItemIndex();
   $itemIndex = (double)($lowIndex + ($highIndex - $lowIndex) / 2);
   }

 

关注点

精度损失

尽管重新计算新索引的中间值时不太可能出现精度耗尽,但这是有可能的。有两种主要方法可以防止这种情况变成错误。第一种方法是明确检查计算值,看它是否等于周围的任何一个值。如果是,则表示精度已耗尽,您需要调用一个函数(最好是快速、异步的函数)来重新索引相关组中的记录。其次,如果您认为这种情况在您的应用程序中可能发生,则更可取的是创建一个 chron 任务,定期重新索引所有使用此排序方法的表。具体的实现超出了本文的范围……目前:)

此演示中使用的“MovedKey”变量可能不准确

作为演示的一部分,会返回被移动的 ID,但您可能会注意到在一种特殊情况下它是不正确的。交换两个相邻的面板只有在将**较低**的项移动到**较高**的位置时才会报告正确的被移动 ID。这是因为当相邻项被切换时,数组是相同的。

例如,如果原始 ID 是 1、2、3、4、5,并且 ID '3' 被移动到 '1' 和 '2' 之间,结果将是

1, 3, 2, 4, 5.

如果 ID '2' 被移动到 '3' 和 '4' 之间,数组将变为

1, 3, 2, 4, 5

嗯……相同的数组……


在这种情况下,当 `getArrayDiff` 函数比较数组时,它总是第一次尝试就成功,因为当 ID 相邻时,无法判断哪个 ID 被移动了;结果数组是相同的。跟踪代码是了解发生了什么的最佳方式。

幸运的是,这完全不影响排序结果。以任何方式交换相邻项目都会产生正确的排序顺序。这仅在您出于某种原因需要明确知道哪个项目被移动时才是一个问题。您应该不会有很多需要知道的情况(jQuery 处理 UI 更改),但是如果您需要,则需要在客户端编写大量额外的代码。

历史

  版本 1.0

© . All rights reserved.