如何使用 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 视图?这是完整的解决方案的主要内容,如果未能解决你的问题,请参考以下文章