jjzjj

javascript - 结合 AngularJS、jQueryUI、Angular-Drag-Drop 排序列表

coder 2025-04-02 原文

我遇到以下情况,我需要允许用户从列表中选择对象并将它们拖/放到某个插槽中:

对象的大小可以是插槽的一到三倍。所以如果用户将 Object 1 拖到 Slot 0,那么它只占用 Slot 0(startSlot = 0 和 endSlot = 0)。但是,如果用户将对象 3 拖动到插槽 3,则它会占用插槽 3、4 和 5(startSlot = 3 和 endSlot = 5)。

将对象放入槽中后,用户可以通过在槽中单击并上下拖动对象来重新排序对象。对象不能相互重叠:

我正在使用 Angular,所以我正在从数据库中读取对象列表,并且我有一个槽数变量。我尝试了几种解决方案。我相信使用 jQuery 和 jQueryUI 可拖动、可放置和可排序是解决方案的一部分,这里是演示拖放和可排序的第一个 fiddle :

http://jsfiddle.net/mduvall216/6hfuzvws/4/

这个 fiddle 的问题是我需要一定数量的插槽。一旦一个对象被放置在插槽中,它会根据对象的大小替换 1 到 3 个插槽。下面的第二个 fiddle 集成了 AngularJS:

http://jsfiddle.net/mduvall216/zg5x4b6k/4/

这里的问题是我知道我需要某种类型的网格来捕捉从对象列表中拖出的对象。我正在寻找的结果是一个 json 列表,其中包含对象在其分配的插槽中:

[{id:obj1,startSlot:0,endSlot:0},{id:obj3,startSlot:3,endSlot:5}]

我也确信该解决方案需要位于此处的 codf0rmer 的 Angular 拖放:

https://github.com/codef0rmer/angular-dragdrop

但是我在尝试将它集成到我的 fiddle 中进行测试时遇到了问题。这是一个有趣的挑战,我已经思考了一段时间,如果有人可以提供帮助,我们将不胜感激。感谢您的宝贵时间。

最佳答案

我开始使用 HTML5 拖放 API 和 jQuery 基本实现您的要求。 API 是轻量级的,不需要任何第 3 方脚本。代码应该易于定制。提供的示例只是一个起点,绝不是生产就绪,应该进行优化,并可能在使用前将其转换为 jQuery 插件模块。这将增加模块的可重用性。

在评论中留下关于代码的任何进一步问题。

JSFiddle Example without sortable:

JSFiddle with sortable

html:

<ul class="select-list">
    <li class="header">Object List</li>
    <li data-slots="1" class="s1">Object 1</li>
    <li data-slots="2" class="s2">Object 2</li>
    <li data-slots="3" class="s3">Object 3</li>
</ul>
<ul class="drop-list" id="sortable">
    <li>Slot 1</li>
    <li>Slot 2</li>
    <li>Slot 3</li>
    <li>Slot 4</li>
    <li>Slot 5</li>
    <li>Slot 6</li>
    <li>Slot 7</li>
    <li>Slot 8</li>
    <li>Slot 9</li>
    <li>Slot 10</li>
    <li>Slot 11</li>
    <li>Slot 12</li>
    <li>Slot 13</li>
</ul>

没有排序的javascript:

(function ($, undefined) {
    // document ready function
    $(function () {
        init();

        $('ul.select-list').on({
            'dragstart': dragstart,
                'dragend': dragend
        }, 'li');

        $('ul.drop-list').on({
            'dragenter dragover': dragover,
                'dragleave': dragleave,
                'drop': drop
        }, 'li.dropzone');
    });

    // Initializes the lists
    function init() {
        $('ul.select-list').find('li').not('[class="header"]').each(function () {
            var height = getSlotHeight() * $(this).data('slots');
            $(this).height(height);
        }).attr('draggable', true);

        $('ul.drop-list').find('li').each(function () {
            $(this).height(getSlotHeight());
        }).addClass('dropzone');
    }

    // Get the height of grid slots
    function getSlotHeight() {
        return 20;
    }

    /**
    * Checks whether target is a kompatible dropzone
    * A dropzone needs the dropzone class
    * and needs to have enough consequent slots to drop the object into
    */
    function isDropZone(target, draggedObj) {
        var isDropZone = true;
        var slots = draggedObj.data('slots');
        for (var i = 1; i < slots; i++) {
            target = target.next();
            if (target.size() == 0 || !target.hasClass('dropzone')) {
                isDropZone = false;
                break;
            }
        }
        return isDropZone;
    }    

    /* 
    * The following events are executed in the order the handlers are declared
    * dragstart being first and dragend being last
    */

    // dragstart event handler
    function dragstart(e) {
        e.stopPropagation();
        var dt = e.originalEvent.dataTransfer;
        dt.effectAllowed = 'move';
        dt.setData('text/html', '');
        $('ul.select-list').data({
            draggedObj: $(this)
        });
    }

    // dragover event handler
    function dragover(e) {
        e.preventDefault();
        e.stopPropagation();
        var data = $('ul.select-list').data();
        if (!data.draggedObj || !isDropZone($(this), data.draggedObj)) {
            e.originalEvent.dataTransfer.dropEffect = 'none';
            return;
        }
        e.originalEvent.dataTransfer.dropEffect = 'move';
        var item = $(this).addClass('dragover');
        var slots = data.draggedObj.data('slots');
        for (var i = 1; i < slots; i++) {
            item = item.next('li').addClass('dragover');
        }
        return false;
    }

    // dragleave event handler
    function dragleave(e) {
        e.preventDefault();
        e.stopPropagation();
        var data = $('ul.select-list').data();
        if (!data.draggedObj || !isDropZone($(this), data.draggedObj)) {
            return;
        }
        var item = $(this).removeClass('dragover');
        var slots = data.draggedObj.data('slots');
        for (var i = 1; i < slots; i++) {
            item = item.next('li').removeClass('dragover');
        }
        return false;
    }

    // drop event handler
    function drop(e) {
        e.stopPropagation();
        e.preventDefault();
        var data = $('ul.select-list').data();
        if (data.draggedObj || !isDropZone($(this), data.draggedObj)) {
            data.target = $(this);
            data.draggedObj.trigger('dragend');
        }
        return false;
    }

    // dragend event handler
    function dragend(e) {
        var data = $('ul.select-list').data();
        if (data.draggedObj && data.target && isDropZone(data.target, data.draggedObj)) {
            var item = data.target.hide();
            var slots = data.draggedObj.data('slots');
            for (var i = 1; i < slots; i++) {
                item = item.next('li').hide();
            }
            data.target.before(data.draggedObj);
        }

        data.target = undefined;
        data.draggedObj = undefined;
        $('ul.drop-list').find('li').removeClass('dragover');
    }
}(jQuery));

CSS:

ul {
     list-style-type: none;
     margin: 0;
     padding: 0;
     float: left;
 }
 li {
     width: 150px;
 }
 li.header {
     height:20px;
     font-weight:bold;
 }
 .select-list li {
     margin-bottom: 10px;
 }
 .drop-list {
     margin-left:20px;
 }
 .drop-list li {
     background-color: #ccc;
     border-left: 1px solid black;
     border-right: 1px solid black;
     border-top: 1px solid black;
 }
 .drop-list li.dragover {
     background-color:#fff;
 }
 .drop-list li:last-child {
     border-bottom: 1px solid black;
 }
 li.s1 {
     background-color: #FFE5E5;
 }
 li.s2 {
     background-color: #C6D4FF;
 }
 li.s3 {
     background-color: #C6FFE3;
 }

编辑:以下脚本还添加了排序。我没有对这个示例进行压力测试,它在某些条件下可能无法执行。

(function ($, undefined) {
    // document ready function
    $(function () {
        init();

        $('ul.select-list,ul.drop-list').on({
            'dragstart': dragstart,
                'dragend': dragend
        }, 'li.object').on('dragenter dragover', listDragover);

        $('ul.drop-list').on({
            'dragenter dragover': dragover,
                'dragleave': dragleave,
                'drop': drop
        }, 'li.dropzone');
    });

    // Initializes the lists
    function init() {
        $('ul.select-list').find('li').not('[class="header"]').each(function () {
            var height = getSlotHeight() * $(this).data('slots');
            $(this).height(height);
        }).attr('draggable', true).addClass('object');

        $('ul.drop-list').find('li').each(function () {
            $(this).height(getSlotHeight());
        }).addClass('dropzone');
    }

    // Get the height of the grid
    function getSlotHeight() {
        return 20;
    }

    /**
     * Checks whether target is a kompatible dropzone
     * A dropzone needs the dropzone class
     * and needs to have enough consequent slots to drop the object into
     */
    function isDropZone(target, draggedObj) {
        var isDropZone = true;
        var slots = draggedObj.data('slots');
        for (var i = 1; i < slots; i++) {
            target = target.next('li');
            if (target.size() == 0 || !target.hasClass('dropzone')) {
                if (!target.is(draggedObj)) {
                    isDropZone = false;
                    break;
                } else {
                    i--;
                }
            }
        }
        return isDropZone;
    }

    // dragstart event handler
    function dragstart(e) {
        e.stopPropagation();
        var dt = e.originalEvent.dataTransfer;
        dt.effectAllowed = 'move';
        dt.setData('text/html', ''); 
        $('ul.select-list').data({
            draggedObj: $(this)
        });        
    }

    // dragover list event handler
    function listDragover(e) {
        e.preventDefault();
        e.stopPropagation();
        e.originalEvent.dataTransfer.dropEffect = 'none';
        var data = $('ul.select-list').data();
        if (data.draggedObj) {
            var item = data.draggedObj;
            item.hide();
            if (data.draggedObj.closest('ul').is('ul.drop-list')) {
                var slots = item.data('slots');
                for (var i = 0; i < slots; i++) {
                    item = item.next('li').show();
                }
            }
        }
        return false;
    }

    // dragover event handler
    function dragover(e) {
        e.preventDefault();
        e.stopPropagation();
        var data = $('ul.select-list').data();
        if (!data.draggedObj || !isDropZone($(this), data.draggedObj)) {
            e.originalEvent.dataTransfer.dropEffect = 'none';
            return;
        }
        e.originalEvent.dataTransfer.dropEffect = 'move';
        var item = $(this).addClass('dragover');
        var slots = data.draggedObj.data('slots');
        for (var i = 1; i < slots; i++) {
            item = item.next('li');
            if (!item.is(data.draggedObj)) {
                item.addClass('dragover');
            } else {
                i--;
            }
        }
        return false;
    }

    // dragleave event handler
    function dragleave(e) {
        e.preventDefault();
        e.stopPropagation();
        var data = $('ul.select-list').data();
        if (!data.draggedObj || !isDropZone($(this), data.draggedObj)) {
            return;
        }
        var item = $(this).removeClass('dragover');
        var slots = data.draggedObj.data('slots');
        for (var i = 1; i < slots; i++) {
            item = item.next('li');
            if (!item.is(data.draggedObj)) {
                item.removeClass('dragover');
            } else {
                i--;
            }
        }
        return false;
    }

    // drop event handler
    function drop(e) {
        e.stopPropagation();
        e.preventDefault();
        var data = $('ul.select-list').data();
        if (data.draggedObj || !isDropZone($(this), data.draggedObj)) {
            data.target = $(this);
            data.draggedObj.trigger('dragend');
        }
        return false;
    }

    // dragend event handler
    function dragend(e) {
        var data = $('ul.select-list').data();
        var target = data.target;
        if (data.draggedObj && !target && data.draggedObj.closest('ul').is('ul.drop-list')) {
            target = data.draggedObj.next('li');
        }
        if (data.draggedObj && target && isDropZone(target, data.draggedObj)) {
            data.draggedObj = data.draggedObj.insertBefore(target);
            var item = target.hide();
            var slots = data.draggedObj.data('slots');
            for (var i = 1; i < slots; i++) {
                item = item.next('li').hide();
            }
        }
        if (data.draggedObj) {
            data.draggedObj.show();
        }
        data.target = undefined;
        data.draggedObj = undefined;
        $('ul.drop-list').find('li').removeClass('dragover');
    }
}(jQuery));

关于javascript - 结合 AngularJS、jQueryUI、Angular-Drag-Drop 排序列表,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33504511/

有关javascript - 结合 AngularJS、jQueryUI、Angular-Drag-Drop 排序列表的更多相关文章

  1. ruby-on-rails - 结合 meta_search 与 acts_as_taggable_on - 2

    我在开发的Rails3网站的一些搜索功能上遇到了一个小问题。我有一个简单的Post模型,如下所示:classPost我正在使用acts_as_taggable_on来更轻松地向我的帖子添加标签。当我有一个标记为“rails”的帖子并执行以下操作时,一切正常:@posts=Post.tagged_with("rails")问题是,我还想搜索帖子的标题。当我有一篇标题为“Helloworld”并标记为“rails”的帖子时,我希望能够通过搜索“hello”或“rails”来找到这篇帖子。因此,我希望标题列的LIKE语句与acts_as_taggable_on提供的tagged_with方法

  2. ruby - 是否有用于序列化和反序列化各种格式的对象层次结构的模式? - 2

    给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最

  3. ruby - 在 Ruby 中比较序列 - 2

    假设我必须(小型到中型)阵列:tokens=["aaa","ccc","xxx","bbb","ccc","yyy","zzz"]template=["aaa","bbb","ccc"]如何确定tokens是否以相同的顺序包含template的所有条目?(请注意,在上面的示例中,应忽略第一个“ccc”,从而由于最后一个“ccc”而导致匹配。) 最佳答案 这适用于您的示例数据。tokens=["aaa","ccc","xxx","bbb","ccc","yyy","zzz"]template=["aaa","bbb","ccc"]po

  4. ruby-on-rails - 使用 javascript 更改数据方法不会更改 ajax 调用用户的什么方法? - 2

    我遇到了一个非常奇怪的问题,我很难解决。在我看来,我有一个与data-remote="true"和data-method="delete"的链接。当我单击该链接时,我可以看到对我的Rails服务器的DELETE请求。返回的JS代码会更改此链接的属性,其中包括href和data-method。再次单击此链接后,我的服务器收到了对新href的请求,但使用的是旧的data-method,即使我已将其从DELETE到POST(它仍然发送一个DELETE请求)。但是,如果我刷新页面,HTML与"new"HTML相同(随返回的JS发生变化),但它实际上发送了正确的请求类型。这就是这个问题令我困惑的

  5. ruby-on-rails - 将 Amazon Simple Notification service SNS 与 ruby​​ 结合使用 - 2

    很难说出这里要问什么。这个问题模棱两可、含糊不清、不完整、过于宽泛或夸夸其谈,无法以目前的形式得到合理的回答。如需帮助澄清此问题以便重新打开,visitthehelpcenter.关闭9年前。我需要从基于ruby​​的应用程序使用AmazonSimpleNotificationService,但不知道从哪里开始。您对从哪里开始有什么建议吗?

  6. ruby-on-rails - carrierwave:在序列化动态属性上安装 uploader - 2

    首先,我使用的是rails3.1.3和来自master的carrierwavegithub仓库的分支。我使用after_init钩子(Hook)来确定基于属性的字段页面模型实例并为这些字段定义属性访问器将值存储在序列化哈希中(希望它清楚我是什么谈论)。这是我正在做的事情的精简版:classPage省略mount_uploader命令让我可以访问我想要的属性。但是当我安装uploader时出现错误消息说“nil类的未定义新方法”我在源代码中读到有方法read_uploader和扩展模块中的write_uploader。我如何必须覆盖这些来制作mount_uploader命令使用我的“虚拟

  7. ruby - 在 Mechanize 中使用 JavaScript 单击链接 - 2

    我有这个:AccountSummary我想单击该链接,但在使用link_to时出现错误。我试过:bot.click(page.link_with(:href=>/menu_home/))bot.click(page.link_with(:class=>'top_level_active'))bot.click(page.link_with(:href=>/AccountSummary/))我得到的错误是:NoMethodError:nil:NilClass的未定义方法“[]” 最佳答案 那是一个javascript链接。Mechan

  8. ruby-on-rails - 获取 ActionController::RoutingError(当尝试使用 AngularJS 将数据发布到 Rails 服务器时,没有路由匹配 [OPTIONS] "/users" - 2

    尝试从我的AngularJS端将数据发布到Rails服务器时出现问题。服务器错误:ActionController::RoutingError(Noroutematches[OPTIONS]"/users"):actionpack(4.1.9)lib/action_dispatch/middleware/debug_exceptions.rb:21:in`call'actionpack(4.1.9)lib/action_dispatch/middleware/show_exceptions.rb:30:in`call'railties(4.1.9)lib/rails/rack/logg

  9. 机器学习——时间序列ARIMA模型(四):自相关函数ACF和偏自相关函数PACF用于判断ARIMA模型中p、q参数取值 - 2

    文章目录1、自相关函数ACF2、偏自相关函数PACF3、ARIMA(p,d,q)的阶数判断4、代码实现1、引入所需依赖2、数据读取与处理3、一阶差分与绘图4、ACF5、PACF1、自相关函数ACF自相关函数反映了同一序列在不同时序的取值之间的相关性。公式:ACF(k)=ρk=Cov(yt,yt−k)Var(yt)ACF(k)=\rho_{k}=\frac{Cov(y_{t},y_{t-k})}{Var(y_{t})}ACF(k)=ρk​=Var(yt​)Cov(yt​,yt−k​)​其中分子用于求协方差矩阵,分母用于计算样本方差。求出的ACF值为[-1,1]。但对于一个平稳的AR模型,求出其滞

  10. ruby-on-rails - Rails 编辑序列化的 JSON 数据 - 2

    我有一个存储JSON数据的列。当它处于编辑状态时,我不知道如何显示它。serialize:value,JSON=f.fields_for:valuedo|ff|.form-group=ff.label:short=ff.text_field:short,class:'form-control'.form-group=ff.label:long=ff.text_field:long,class:'form-control' 最佳答案 代替=f.fields_for:valuedo|ff|请使用以下代码:=f.fields_for:va

随机推荐