简体   繁体   English

使用jquery支持重新排序html表行以及快速生成SQL后端的最佳方法?

[英]Best way to support reordering html table rows using jquery as well as make SQL backend fast?

I have an asp.net-mvc website and I have an html table of data and one of the columns is ranking which represents the priority of that row (each row represents a request). 我有一个asp.net-mvc网站,我有一个html数据表,其中一列是排名,表示该行的优先级(每行代表一个请求)。 I allow people to edit the data in a row with a new number but it doesn't actually affect any of the other rows (for example, nothing is stopping folks from entering a value that alraedy exists in another row) 我允许人们使用新数字连续编辑数据,但它实际上并不影响任何其他行(例如,没有任何东西阻止人们输入另一行中存在的alraedy值)

I am looking for a solution to have a more effective front end and a fast backend. 我正在寻找一个更有效的前端和快速后端的解决方案。 I will break down each: 我将分解每个:

  1. Front End: 前端:

I want some functionality where I can either drag and drop rows up and down or click up/down arrow or when i enter a ranking value in a row it updates the ranking of all other rows. 我想要一些功能,我可以上下拖动行或点击向上/向下箭头,或者当我连续输入排名值时,它会更新所有其他行的排名。 I basically don't want the user to have to update every row if there is a new entry that is ranked #1 (basically having to update previous 1 => 2, and previous 2=>3, and previous 3=>4 etc . . 我基本上不希望用户必须更新每一行,如果有一个排名为#1的新条目(基本上必须更新前一个1 => 2,前一个2 => 3,前三个=> 4等......

Are there any jquery libraries or useful patterns that help support this functionality (to avoid having to wire it all up from scratch) as this seems like generic functionality that i can imagine many other people using. 是否有任何jquery库或有用的模式,以帮助支持此功能(以避免必须从头开始连接),因为这看起来像我可以想象许多其他人使用的通用功能。

For reference, if anyone is familiar with the JIRA Greenhopper Planning board , this would be ideal as an example to mimic. 作为参考,如果有人熟悉JIRA Greenhopper规划板 ,这将是一个理想的模拟实例。

  1. Back End 后端

My related question is that when i go update the backend, i am trying to figure out how to avoid this being really slow operation. 我的相关问题是,当我更新后端时,我试图弄清楚如何避免这种操作真的很慢。 If there is a rank field in my database table and someone updates an item to be number one, i essentially have to run an update query on EVERY other row to change the value to the existing value + 1. Is there some pattern to do this more effectively? 如果我的数据库表中有一个排名字段,有人将项目更新为第一,我基本上必须在每一行上运行更新查询,将值更改为现有值+ 1.是否有一些模式可以执行此操作更有效?

For the frontend look at jQueryUI's Sortable feature. 对于前端看看jQueryUI的Sortable功能。 It's normally applied to <ul/> or <ol/> elements, but a quick Google Search turns up guides to adapting it for <table/> elements too, if needed. 它通常应用于<ul/><ol/>元素,但如果需要,快速Google搜索也会引导指南<table/>元素进行调整。

The backend issue of one insert cascading into many updates can be solved by treating it as a linked-list. 一个插入级联到许多更新的后端问题可以通过将其视为链接列表来解决。

I'm assuming you're storing your data as: 我假设您将数据存储为:

Rank | Key
1    | Zebra
2    | Pig
3    | Pony

So inserting a new row could require updating potentially every existing row: 因此,插入新行可能需要更新可能的每个现有行:

Rank | Key
1    | Penguin * Inserted
2    | Zebra   * Updated
3    | Pig     * Updated
4    | Pony    * Updated

Instead, store their order by relating them to another row in the table. 相反,通过将它们与表中的另一行相关联来存储它们的顺序。

Key | ComesAfter
Zebra | null
Pig   | Zebra
Pony  | Pig

So inserting a new row will require at most one update. 因此,插入新行最多只需要一次更新。

Key     | ComesAfter
Penguin | null    * Inserted
Zebra   | Penguin * Updated
Pig     | Zebra   * Unchanged
Pony    | Pig     * Unchanged

Of course you can use IDs for this 当然,您可以使用ID


However 然而

You might be prematurely optimizing; 您可能会过早地进行优化; it could be that some simple SQL work could prove to be quite efficient even while issuing many updates. 即使在发布许多更新时,一些简单的SQL工作也可能证明是非常有效的。 Consider this: 考虑一下:

Key   | Rank
Zebra | 3
Pig   | 1
Pony  | 2

Notice that the Value is acting as a natural key here, it could also have an ID (which is different from the Rank ). 请注意,Value在此处充当自然键,它也可能具有ID(与Rank不同)。

To add a new row as Rank 1 you'd need to do something like: 要将新行添加为Rank 1,您需要执行以下操作:

begin tran
  insert values ('Penguin', 1)
  update table set Rank = Rank + 1 where Rank >= 1
commit

Adding indexes and possibly partitions (depending on the size of the table) will offer great performance improvements--the only tradeoff being a bit more occasional DB administration. 添加索引和可能的分区(取决于表的大小)将提供很好的性能改进 - 唯一的权衡是更偶尔的数据库管理。

The main question here is which aspect you reuqire mose: fast reads, fast writes, or simple design? 这里的主要问题是你需要哪个方面:快速读取,快速写入或简单设计? You can have two of the three :) 你可以有三个中的两个:)

For your back end : is your question pure speculation ? 对于你的后端:你的问题纯粹是猜测吗? or have you actually tried something ? 或者你真的尝试过什么?

If your table has an index on its "rank" column, there should be no problem running queries like : 如果您的表在其“排名”列上有索引,那么运行查询应该没有问题,例如:

UDPATE table SET rank=rank+1 WHERE rank >= {begin value} AND rank <= {end value};

even if your table reaches the 1M rows range and your query affects several thousands lines. 即使您的表达到1M行范围,您的查询也会影响数千行。

You will need to adapt the index to your needs - eg if your query is actually : 您需要根据需要调整索引 - 例如,如果您的查询实际上是:

UDPATE table SET rank=rank+1 WHERE listID = {given list ID} AND rank >= {begin value} AND rank <= {end value};

your should index listID, rank (in this order). 你应该索引listID, rank (按此顺序)。

For the front end : as said in other answers, read the doc for jqueryUI Sortable widget 对于前端:如在其他答案中所述,阅读jqueryUI可排序小部件的文档

For moving items Up and Down you may use jQuery UI sortable . 对于向上和向下移动项目,您可以使用jQuery UI进行排序 The simplest solution is to add to all items a floating point number called score, possibly a double. 最简单的解决方案是在所有项目中添加一个名为score的浮点数,可能是double。 This is known to be the more efficient technique to keep a set of elements ordered when ordering may change. 众所周知,这是一种更有效的技术,可以在订购时保持一组有序元素的变化。 Thid score must be kept in all layers from the DB, to the presentation layer. 必须在从DB到表示层的所有层中保留分数。 When you need to put an element in between two other elements you just set its float to the avrerage of the two elements, so you are sure it is in between them. 当你需要在另外两个元素之间放置一个元素时,你只需将它的浮点数设置为两个元素的平均值,这样你就可以确定它位于它们之间。

You retrieve all items from the DB sorted on their score. 您可以从数据库中检索按其分数排序的所有项目。

You start the algorithm with two fake items (not rendered, and not inserted in the DB, but purely virtual) whose scores are 0. and 1. All "real" items will be inserted in between them. 您使用两个假项目(未呈现,未插入数据库,但纯虚拟)开始算法,其分数为0和1.所有“真实”项目将插入它们之间。 Thus your first element will have a 0.5 score, and so on. 因此,您的第一个元素将获得0.5分,依此类推。

You must store in the DB the minimum distance between two scores you reached. 您必须在DB中存储您达到的两个分数之间的最小距离。 When this number is becoming too small and is being close to the minimum precison you may get with a double, you must reorganize your table by assiging scores, such that all scores distances between two consecutive elements are the same and are equal to 1/(number of items). 当这个数字变得太小并且接近最小精度你可能得到一个双倍时,你必须通过分配分数来重组你的表,这样两个连续元素之间的所有分数距离是相同的并且等于1 /(东西的个数)。

If you need to show to the user a "classic rank" 1, 2, 3.... you may create it in the web page just before rendering the page, and immediately after each user modification. 如果您需要向用户显示“经典排名”1,2,3 ......您可以在呈现页面之前在网页中创建它,并在每次修改用户之后立即创建。 Updating all ranks has a cost equal to the items shown in the screen. 更新所有排名的成本等于屏幕中显示的项目。 This is not a big This wiil cause no performance decrease since all elements on the screen must be re-rendered by the browser at a cost that is higher than the cost of re-writing all ranks on the screen. 这不是一个很大的原因因为浏览器必须重新渲染屏幕上的所有元素,其成本高于重写屏幕上所有排名的成本,因此不会降低性能。 ...IMPORTANT... if you have virtual scrolling or paging ...the rank re-writing involves only the active elements on the screen since the rank has the only purpose of being shown to the user since all computations are done with the score. ......重要...如果您有虚拟滚动或分页...等级重写只涉及屏幕上的活动元素,因为等级的唯一目的是向用户显示,因为所有计算都是通过得分。

Here are couple ideas for the backend. 以下是后端的几个想法。

Make values in the backend be like: 1 – rank #1 100 – rank #2 200 – rank #3 Etc… 使后端的值如下:1 - 排名#1 100 - 排名#2 200 - 排名#3等...

This will allow you to update minimal number of rows when you add some value between other two. 这将允许您在其他两个之间添加一些值时更新最小行数。

For example if you want to insert a value at rank #3 you can just enter value of 150. 例如,如果要在排名#3处插入值,则只需输入值150即可。

When showing data to users you don't display the actual values in the database but use a ROW_NUMBER function to display it in a user friendly way. 向用户显示数据时,您不会在数据库中显示实际值,而是使用ROW_NUMBER函数以用户友好的方式显示它。

When you want to add something at place #1 you can just enter a value that's smaller than existing first place by some factor. 当您想在地点#1添加某些内容时,您可以输入一个小于现有首位的值。 It doesn't matter if actual value ends up looking like -250 as long as order is correct an ROW_NUMBER function will take care of the presentation. 只要订单正确,实际值最终看起来像-250并不重要,ROW_NUMBER函数将负责演示。

All you would have to do once in a while is to reorder values so there is enough room between the two. 您偶尔需要做的就是重新排序值,以便两者之间有足够的空间。

Extending @Dwoolk's answer, you could use floats for the ordering. 扩展@ Dwoolk的答案,您可以使用浮动进行排序。 Then you should always (at least for a long time) be able to find new values between two existing values. 那么你应该总是(至少很长一段时间)能够在两个现有值之间找到新值。

Angularjs is awesome at taking care of the "wiring" of these kinds of things. Angularjs在处理这些事情的“布线”方面非常棒。 Here's an example, of the algorithm suggested by Francesco implemented with an Angular controller handling all of the binding between the items/scores and the table and a directive handling the sorting/re-scoring: 这是一个例子,由Francesco建议的算法实现,Angular控制器处理items / score和表之间的所有绑定以及处理排序/重新评分的指令:

angular.module("listApp", []).
  controller("listController", function($scope) {
    var sortByKey = function(arr, key) {
      arr.sort(function(a, b) {
        if (a[key] > b[key]) {
          return -1;
        } else if (b[key] > a[key]) {
          return 1;
        } else {
          return 0;
        }
      });
    };
    $scope.items = [{id: 1111, name: "Item 1", score:0.2},
                    {id: 3333, name: "Item 2", score:0.5},
                    {id: 5555, name: "Item 3", score:0.7}];
    sortByKey($scope.items, "score");
  }).
  directive("sortable", function() {
    return function(scope, element) {
      var startIndex = null;
      var stopIndex = null;
      var averageOfNeighbors = function(items, index) {
        var beforeScore = index === 0 ? 1 : items[index -1].score;
        var afterScore = index === items.length - 1 ? 0 : items[index + 1].score;
        return (beforeScore + afterScore) / 2;
      }
      element.sortable( {
        start: function(e, ui) {
          startIndex = ui.item[0].sectionRowIndex;
        },
        stop: function(e, ui) {
          stopIndex = ui.item[0].sectionRowIndex;
          if (stopIndex !== startIndex) {
            var toMove = scope.items.splice(startIndex, 1)[0];
            scope.items.splice(stopIndex, 0, toMove);
            toMove.score = averageOfNeighbors(scope.items, stopIndex);
            console.log("Set item(" + toMove.id + ").score to " + toMove.score);
            scope.$apply();
          }
        }
      });
    }
  })

This can then be trivially bound to a table like this: 然后,这可以简单地绑定到这样的表:

    <tbody sortable>
      <tr ng-repeat="item in items">
        <td>{{item.name}}</td>
        <td>{{$index + 1}}</td>
        <td>{{item.score}}</td>
      </tr>
    </tbody>

Here's a Plunker. 这是一个Plunker。

Also, note that after each re-sort, an AJAX call could be made to update a single score record in the database. 另请注意,在每次重新排序后,可以进行AJAX调用以更新数据库中的单个记分记录。

I have done a similar functionality in the project that I am working on. 我在我正在进行的项目中完成了类似的功能。

  • For front end: I have used jQuery before() and after() APIs to move the rows, for example: 对于前端:我使用jQuery before()after() API来移动行,例如:

    $(prevRow).before(currentRow) $(prevRow)。之前(currentRow)

  • For back end: I have a int column in the database that stores the orders of every record. 对于后端:我在数据库中有一个int列,用于存储每条记录的订单。

Now once the rows are arranged at the front end, the new sequence of all the rows is sent to database via Ajax and all the records are updated (In my case only the filtered records on the table needs to be updated). 现在,一旦行排列在前端,所有行的新序列将通过Ajax发送到数据库,并且所有记录都会更新(在我的情况下,只需要更新表上的过滤记录)。

This is probably not the best solution but I could not find any other way to make this work. 这可能不是最好的解决方案,但我找不到任何其他方法来使这项工作。

是完整实现代码的一个很好的例子。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM