当前位置: 首页 > 知识库问答 >
问题:

如何用PHP/Laravel创建一个带有上下文菜单的JSTree视图,包括创建、重命名、删除、拖放?这是完整的解决方案

朱渝
2023-03-14

我的项目需要一个文件treeview来上传文档,我使用的是一个主题森林模板,页面中包含了jstree。

所以我决定使用它,但必须将它连接到数据库。它需要一个数据库,一个api和所有的代码。

我花了几个小时试图弄清楚如何使用Laravel创建一个JSTree结构,包括拖放、移动、创建、重命名和排序功能

共有1个答案

白坚壁
2023-03-14

在深入研究了jstree文档和Stackoverflow社区之后,下面是我对所有内容的工作解决方案编译,一步一步。希望你喜欢。随便问吧。

这里是我的全部解决方案。如果有用的话别忘了投票。如果有什么不对的地方,请不要投票,或者告诉我,这样我就可以更新我的答案,帮助我们中的更多人。

我使用的是JSTree版本3.3.11和Laravel8。

步骤:

A)创建数据库。该表为“目录”。

    class CreateDirectoriesTable extends Migration
    {
        public function up()
        {
            Schema::create('directories', function (Blueprint $table) {
                $table->id();
                $table->unsignedBigInteger('parent_id')->nullable();
                $table->string('name');
                $table->text('observations')->nullable();
                $table->timestamps();
    
                $table->foreign('parent_id')
                    ->references('id')
                    ->on('directories')
                    ->cascadeOnDelete();
    
            });
        }
    
        /**
         * Reverse the migrations.
         *
         * @return void
         */
        public function down()
        {
            Schema::dropIfExists('directories');
        }
    }

B)directory.model使用它来定义哪些字段是可更新的,并定义递归关系。<?php

    namespace App\Models;
    
    use Illuminate\Database\Eloquent\Factories\HasFactory;
    use Illuminate\Database\Eloquent\Model;
    
    class Directory extends Model
    {
        use HasFactory;
        
        protected $fillable = [
            'parent_id',
            'name',
            'observations',
        ];
    
        public function children()
        {
            return $this->hasMany(Directory::class, 'parent_id');
        }
    
    }

C)播种机(可选)我已经使用播种机来包含一些要测试的项目

<?php

namespace Database\Seeders;

use Faker\Factory;
use App\Models\Directory;
use Illuminate\Database\Seeder;

class DirectorySeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        $faker = Factory::create();

        $items = array(
            [
                'name' => $faker->lexify('???????????????'),
                'parent_id' => null,
                'observations' => $faker->optional()->paragraph(3),
            ],
            
            [
                'name' => $faker->lexify('???????????????'),
                'parent_id' => null,
                'observations' => $faker->optional()->paragraph(3),
            ],
            [
                'name' => $faker->lexify('???????????????'),
                'parent_id' => null,
                'observations' => $faker->optional()->paragraph(3),
            ],
            [
                'name' => $faker->lexify('???????????????'),
                'parent_id' => null,
                'observations' => $faker->optional()->paragraph(3),
            ],
            [
                'name' => $faker->lexify('???????????????'),
                'parent_id' => 1,
                'observations' => $faker->optional()->paragraph(3),
            ],
            [
                'name' => $faker->lexify('???????????????'),
                'parent_id' => 2,
                'observations' => $faker->optional()->paragraph(3),
            ],
            [
                'name' => $faker->lexify('???????????????'),
                'parent_id' => 3,
                'observations' => $faker->optional()->paragraph(3),
            ],
            [
                'name' => $faker->lexify('???????????????'),
                'parent_id' => 5,
                'observations' => $faker->optional()->paragraph(3),
            ],
            [
                'name' => $faker->lexify('???????????????'),
                'parent_id' => 5,
                'observations' => $faker->optional()->paragraph(3),
            ],
            [
                'name' => $faker->lexify('???????????????'),
                'parent_id' => 7,
                'observations' => $faker->optional()->paragraph(3),
            ],
            [
                'name' => $faker->lexify('???????????????'),
                'parent_id' => 7,
                'observations' => $faker->optional()->paragraph(3),
            ],
            [
                'name' => $faker->lexify('???????????????'),
                'parent_id' => 8,
                'observations' => $faker->optional()->paragraph(3),
            ],
            [
                'name' => $faker->lexify('???????????????'),
                'parent_id' => 8,
                'observations' => $faker->optional()->paragraph(3),
            ],
            

        );
        
        foreach($items as $item) { 
            Directory::factory()->create($item);         
        }    
    }
}

D)web.php上的路由我已经定义了4个函数来处理DragNDrop、Rename、Delete和create。

Route::name('api.')->prefix('api/')->group(function() {
    Route::post('/treeview/dnd', 'ApiController@treeviewDnd')->name('treeviewdnd');
    Route::post('/treeview/rename', 'ApiController@treeviewRename')->name('treeviewrename');
    Route::post('/treeview/delete', 'ApiController@treeviewDelete')->name('treeviewdelete');
    Route::post('/treeview/create', 'ApiController@treeviewCreate')->name('treeviewcreate');
});

E)API控制器

class ApiController extends Controller
{
    // Move Node on Directory Tree
    public function treeviewDnd()
    {
        $directory = Directory::find(request()->source);
        if ($directory) {
            if (request()->destination) {
                if (request()->destination == '#') {
                    $directory->parent_id = null;
                } else {
                    $directory->parent_id = request()->destination;
                }
            } 
            $directory->update();
        }
    }

    // Rename Node on Directory Tree
    public function treeviewRename()
    {
        $directory = Directory::find(request()->dbid);
    
        if ($directory) {
            $name = request()->name;
            if ($name) {            
                $directory->name = $name;
                $directory->update();
            }    
        }
    }

    // Delete Node on Directory Tree
    public function treeviewDelete()
    {        
        $directory = Directory::find(request()->id);
        
        if ($directory) {
            $directory->delete();
        }

    }

    // Create Node on Directory Tree
    public function treeviewCreate()
    {
        $directory = [
            "name" => request()->name,
            "parent_id" => request()->parentid,
        ];
        $result = Directory::create($directory);
        
        return $result;
    }
}

F)在blade.php上包含树

<div id="stackoverflowtree" class="tree-demo"></div>

G)我在基本刀片上创建了一个“脚本”部分,因此我可以使用section标记在页面末尾包含脚本。

@section('scripts')

    <script>
        "use strict";
        var tree = {!! $treeJS !!};
        var treeId = '#stackoverflowtree';

        var nodeSelected = undefined;
        
        var KTTreeview = function () {
            var _demostackoverflow = function() {
                $(treeId).jstree({
                    "core" : {
                        "themes" : {
                            "responsive": false
                        },
                        // so that create works
                        "check_callback" : function (operation, node, node_parent, node_position, more) {
                            if (operation === 'delete_node') {
                                if (confirm('@lang("global.confirmation_title")') == true) {
                                    return true;
                                }
                                else {
                                    return false;
                                }
                            } else {
                                return true;
                            }
                        },
                        'data': tree,
                    },
                    "types" : {
                        "default" : {
                            "icon" : "fa fa-folder text-primary"
                        },
                        "file" : {
                            "icon" : "fa fa-file text-primary"
                        }
                    },
                    "state" : { "key" : "demo2" },
                    "plugins" : [ "dnd", "state", "types", "sort", "contextmenu" ],
                    "sort" : function(a, b) {                
                        if (a && b && this) {
                            var a1 = this.get_node(a);
                            var b1 = this.get_node(b);
                            
                            if (a1.icon == b1.icon){
                                return a1.text.toLowerCase().localeCompare(b1.text.toLowerCase());
                            } else {
                                return a1.icon.toLowerCase().localeCompare(b1.icon.toLowerCase());
                            }
                        }
                    },
                    "contextmenu": {
                        "items": function ($node) {
                            var tree = $(treeId).jstree(true);
                            return {
                                "Rename": {
                                    "label": "@lang('global.directory_rename')",
                                    "action": function (obj) { 
                                        tree.edit($node);
                                    }
                                },
                                "Create": {
                                    "label": "@lang('global.directory_create')",
                                    "action": function (obj) { 
                                        $node = tree.create_node($node);
                                        tree.edit($node); 
                                    }
                                },
                                "Delete": {
                                    "label" : "@lang('global.directory_delete')",
                                    "action" : function(obj) { 
                                        tree.delete_node($node);
                                    }
                                }
                            };
                        }
                    }
                })
                .bind("move_node.jstree", function(e, data) {
                    var treeInst = $(treeId).jstree(true)
                    var parentNode = treeInst.get_node(data.parent)

                    $.ajax({
                        url: "{{ route('api.treeviewdnd') }}",
                        type:'POST',
                        data: {
                            "_token" : "{{ csrf_token() }}", 
                            "source": data.node.original.dbid, 
                            "destination": parentNode.original.dbid,
                        },
                        success: function(data) {
                            console.log(data);
                        }
                    });

                })
                .bind("select_node.jstree", function(evt, data){
                    console.log("select");
                    nodeSelected = data.node;

                    $("#tree-subtitle").html(data.node.text)
                    
                })
                .bind("rename_node.jstree", function (e, data) {    
                    if (data.node.text && data.text != data.old) {    
                        
                        $.ajax({
                            url: "{{ route('api.treeviewrename') }}",
                            type:'POST',
                            data: {
                                "_token" : "{{ csrf_token() }}", 
                                "dbid": data.node.original.dbid, 
                                "name": data.text,
                            },
                            success: function(data) {
                                toastr.success('@lang("global.success_message")', '@lang("global.success_title")');
                            },
                            error: function(data) {
                                toastr.error('@lang("global.error_required")', '@lang("global.error_title")');
                            }
                        });
                    }    
                })
                .bind("create_node.jstree", function (e, data) {    

                    var treeInst = $(treeId).jstree(true)
                    var parentNode = treeInst.get_node(data.parent)
                    
                    $.ajax({
                        url: "{{ route('api.treeviewcreate') }}",
                        type:'POST',
                        data: {
                            "_token" : "{{ csrf_token() }}", 
                            "entityid": {{ $entity->id }}, 
                            "parentid": parentNode.original.dbid, 
                            "name": data.node.text,
                        },
                        success: function(response) {
                            data.node.original = { "dbid" : response.id };
                        },
                        error: function(response) {
                            toastr.error('@lang("global.error_message")', '@lang("global.error_title")');
                        }
                    });
                    
                })
                .bind("delete_node.jstree", function (e, data) {
                    $.ajax({
                        url: "{{ route('api.treeviewdelete') }}",
                        type:'POST',
                        data: {
                            "_token" : "{{ csrf_token() }}", 
                            "id": data.node.original.dbid, 
                        },
                        success: function(data) {
                            toastr.success('@lang("global.success_message")', '@lang("global.success_title")');
                        },
                        error: function(data) {
                            toastr.error('@lang("global.error_message")', '@lang("global.error_title")');
                        }
                    });
                
                });
            }

            
            return {
                //main function to initiate the module
                init: function () {
                    _demostackoverflow();
                }
            };
        }();        
        
        jQuery(document).ready(function() {
            KTTreeview.init();
        });
     
    </script>
@endsection

H)差点忘了,服务器端树形结构的创建,页面控制器:

<?php

namespace App\Http\Controllers;

use App\Models\Directory;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class EntityController extends Controller
{

    private function getTreeJS($entity, $path_id, &$treejs)
    {
        
        $directories = Directory::where('entity_id', $entity->id)->where('parent_id', $path_id)->get();
        
        $treejs .= '[';
        foreach($directories as $directory) {
            $treejs .= '{';
                $treejs .= '    "dbid" : "' . $directory->id . '", ';
                $treejs .= '    "text" : "' . $directory->name . '", ';
                
                $treejs .= '"children" : ';
                
            $treejs .= $this->getTreeJS($entity, $directory->id, $treejs);
            
        
            $treejs .= '}, ';
        }
        $treejs .= ']';
    }

    public function details(Entity $entity, Property $property = null)
    {
        // Create Tree JS
        $treejs = '';
        $this->getTreeJS($entity, null, $treejs);
        
        return view('admin.entities.details', [
            'treeJS' => $treejs,    
        ]);
    }
}

I)我用来向用户显示某些输出的消息是Laravel中的语言文件/resources/lang/en/:

<?php

return [
    // Success
    'success_title' => 'Success!',
    'success_message' => 'Operation successfully.',
    
    // Errors
    'error_title' => 'Ups! There was an error.',
    'error_required' => 'You must fill the information.',
    'error_message' => 'It was not possible to finish the operation.',
    
    'confirmation_title' => 'Do you confirm?',
    'confirmation_success' => 'Operation successfully.',

    'directory_rename' => 'Rename',
    'directory_create' => 'Create folder',
    'directory_delete' => 'Delete folder'
];

结论:我为数据库表中对应于ID的每个树文件夹使用了一个额外的变量dbid。

使用这个DB Id,我可以通过使用jstree'get_node'找到确切的节点,在每个操作中使用它。

我刚刚开始学习Laravel,这不是完美的解决方案,但它是我处理我的需求的方法。你可以随意使用它,用你自己的方式改变它。

这是我的形象:

玩得开心^..^

 类似资料:
  • 问题内容: 我的应用程序处于测试阶段,并且我一直在对涉及新模型的功能进行有限的测试。经过大量测试后,我不得不进行结构更改,使旧数据无法正常运行。 我需要做的就是删除并重新创建一个表。我知道我可以在迁移中做到这一点,但这似乎是一个hack。在本地开发人员副本中,我只会使用,但在beta应用程序中,我不想丢失除此表以外的任何表中的数据。 这是指示生产应用程序删除并重新创建单个表的简单方法。就我而言,我

  • 在我的应用程序中,我为信封创建了一个仪表板,其中包含一个表单,用户可以在其中编辑基本的信封收件人信息,表单的操作捕获更改并发出PUT请求以更新DocuSign上的信息。此仪表板上还有发件人视图,它在后台加载,当用户单击Bootstrap时可见。 在通过表单将更改保存给收件人时,我遇到一个错误: 我认为这是由于发送者视图在后台加载,因此在没有首先单击发送者视图iFrame中的“放弃更改”的情况下阻止

  • 如何创建合并来自两个不同表的不同所有列的视图。 这给了我一个错误: 重复的列名“tID” 有没有一种方法可以连接两个表,而不需要列出所有要选择的值?

  • 在前四十来章中,我们讲解了许多基础方面的内容。 在本书的最后部分,我们将尝试从零开始为一门语言创造Vim插件。 这不是个适合懦夫的游戏。这将需要你竭尽全力。 如果你现在就想退出,那确实也不坏!你已经学到了如何在~/.vimrc里改善你的生活, 还有如果修复别人的插件里的bugs。 有"这就够了,我不想虚掷光阴于创造一个我将不会使用的插件"这种想法并不可耻。 现实一点。如果你不想创造一个自己想用的插

  • 本文向大家介绍php文件夹的创建与删除方法,包括了php文件夹的创建与删除方法的使用技巧和注意事项,需要的朋友参考一下 本文实例讲述了php文件夹的创建与删除方法。分享给大家供大家参考。具体如下: 1、创建文件夹 2、创建文件夹,递归式创建 3、删除文件夹 希望本文所述对大家的php程序设计有所帮助。

  • 我编写了上面的代码来动态创建文本字段和按钮;但现在我需要删除两个文本字段和一个按钮时,按钮被点击。我该怎么做?