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

Posted

技术标签:

【中文标题】如何使用 PHP/Laravel 创建、重命名、删除、拖放的上下文菜单创建 JSTree 视图?这是完整的解决方案【英文标题】:How to create a JSTree view with context menu with create, rename, delete, drag'n'drop with PHP/Laravel? This is the Full solution 【发布时间】:2021-06-16 19:48:28 【问题描述】:

我的项目需要一个文件树视图来上传文档,而我正在使用一个页面中包含 jstree 的主题森林模板。

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

我花了几个小时试图找出如何使用 Laravel 创建一个 JSTree 结构,具有拖放、移动、创建、重命名和排序功能。

【问题讨论】:

【参考方案1】:

在深入研究 jstree 文档和 Stack Overflow 之后,这是我的工作解决方案编译的一切,一步一步。

我正在使用 JSTree 版本 3.3.11 和 Laravel 8。

步骤:

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) 目录.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) 播种机(可选) 我使用 Seeder 来包含一些要测试的项目

<?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="***tree" class="tree-demo"></div>

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

@section('scripts')

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

        var nodeSelected = undefined;
        
        var KTTreeview = function () 
            var _demo*** = 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 parentNodeResult = null;
                    if (data.parent != '#') 
                        var aux = treeInst.get_node(data.parent);
                        parentNodeResult = aux.original.dbid;
                     else 
                        parentNodeResult = '#';
                    
                
                    $.ajax(
                        url: " route('api.treeviewdnd') ",
                        type:'POST',
                        data: 
                            "_token" : " csrf_token() ", 
                            "source": data.node.original.dbid, 
                            "destination": parentNodeResult,
                        ,
                        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 () 
                    _demo***();
                
            ;
        ();        
        
        jQuery(document).ready(function() 
            KTTreeview.init();
        );
     
    </script>
@endsection

H) 我差点忘了。在服务器端创建树形结构,页面Controller:

<?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 的额外变量。

使用该数据库 ID,我可以通过使用 jstree 'get_node' 找到确切的节点来在每个操作中使用它。

我刚刚开始学习 Laravel,这不是完美的解决方案,但它是我处理我的要求的方法。随意使用它并按照自己的方式进行更改。

这是我的图片:

【讨论】:

以上是关于如何使用 PHP/Laravel 创建、重命名、删除、拖放的上下文菜单创建 JSTree 视图?这是完整的解决方案的主要内容,如果未能解决你的问题,请参考以下文章

Mysql---4 修改表字段操作(增,删,改,重命名)

如何使用命令重命名@Angular/cli 创建的服务

如何使用 UICollectionView 在 Pages 中重新创建重命名动画?

如何重命名 AWS 客户 IAM 策略?

python如何创建日期命名文件?

如何重命名用户创建的 SYSUSERS 表?