Menu Close

Create Sortable, Drag And Drop Multi Level Menu With Jquery

Create Sortable, Drag And Drop Multi Level Menu With Jquery

In this tutorial, we will learn how to create sortable, drag and drop multi level using jquery like wordpress menu page. The following code shows two input fields so we can enter the data and save it to the database. It is a manageable list and you can edit these fields also.

Follow the below steps to create this,

Create a database and add the below menu table

CREATE TABLE IF NOT EXISTS `menu` (
  `id_menu` int(11) NOT NULL AUTO_INCREMENT primary key,
  `label_menu` varchar(100) NOT NULL,
  `url_menu` varchar(300) NOT NULL,
  `parent_id` int(11) NOT NULL
)
 

Create a new project folder and add db.php file and insert the below

<?php

define('DB_SERVER', 'localhost');
define('DB_USERNAME', 'root');
define('DB_PASSWORD', '');
define('DB_DATABASE', 'jquery-nestable');

$db = new db(DB_SERVER, DB_USERNAME, DB_PASSWORD,DB_DATABASE);


class db
{

    protected $connection;
    protected $query;
    public $query_count = 0;

    public function __construct($dbhost = 'localhost', $dbuser = 'root', $dbpass = '', $dbname = '', $charset = 'utf8')
    {
        $this->connection = new mysqli($dbhost, $dbuser, $dbpass, $dbname);
        if ($this->connection->connect_error) {
            die('Failed to connect to MySQL - ' . $this->connection->connect_error);
        }
        $this->connection->set_charset($charset);
    }

    public function query($query)
    {
        if ($this->query = $this->connection->prepare($query)) {
            if (func_num_args() > 1) {
                $x = func_get_args();
                $args = array_slice($x, 1);
                $types = '';
                $args_ref = array();
                foreach ($args as $k => &$arg) {
                    if (is_array($args[$k])) {
                        foreach ($args[$k] as $j => &$a) {
                            $types .= $this->_gettype($args[$k][$j]);
                            $args_ref[] = &$a;
                        }
                    } else {
                        $types .= $this->_gettype($args[$k]);
                        $args_ref[] = &$arg;
                    }
                }
                array_unshift($args_ref, $types);
                call_user_func_array(array($this->query, 'bind_param'), $args_ref);
            }
            $this->query->execute();
            if ($this->query->errno) {
                die('Unable to process MySQL query (check your params) - ' . $this->query->error);
            }
            $this->query_count++;
        } else {
            die('Unable to prepare statement (check your syntax) - ' . $this->connection->error);
        }
        return $this;
    }

    public function fetchAll()
    {
        $params = array();
        $meta = $this->query->result_metadata();
        while ($field = $meta->fetch_field()) {
            $params[] = &$row[$field->name];
        }
        call_user_func_array(array($this->query, 'bind_result'), $params);
        $result = array();
        while ($this->query->fetch()) {
            $r = array();
            foreach ($row as $key => $val) {
                $r[$key] = $val;
            }
            $result[] = $r;
        }
        $this->query->close();
        return $result;
    }

    public function fetchArray()
    {
        $params = array();
        $meta = $this->query->result_metadata();
        while ($field = $meta->fetch_field()) {
            $params[] = &$row[$field->name];
        }
        call_user_func_array(array($this->query, 'bind_result'), $params);
        $result = array();
        while ($this->query->fetch()) {
            foreach ($row as $key => $val) {
                $result[$key] = $val;
            }
        }
        $this->query->close();
        return $result;
    }

    public function numRows()
    {
        $this->query->store_result();
        return $this->query->num_rows;
    }

    public function insertedId()
    {
        return $this->query->insert_id;
    }

    public function close()
    {
        return $this->connection->close();
    }

    public function affectedRows()
    {
        return $this->query->affected_rows;
    }

    private function _gettype($var)
    {
        if (is_string($var)) {
            return 's';
        }

        if (is_float($var)) {
            return 'd';
        }

        if (is_int($var)) {
            return 'i';
        }

        return 'b';
    }

}
?>
 

Create index.php file and insert the below code

<?php
include 'db.php';

function renderMenuItem($id, $label, $url)
{
    return '<li class="dd-item dd3-item" data-id="' . $id . '" data-label="' . $label . '" data-url="' . $url . '">' .
        '<div class="dd-handle dd3-handle" > Drag</div>' .
        '<div class="dd3-content"><span>' . $label . '</span>' .
        '<div class="item-edit">Edit</div>' .
        '</div>' .
        '<div class="item-settings d-none">' .
        '<p><label for="">Navigation Label<br><input type="text" name="navigation_label" value="' . $label . '"></label></p>' .
        '<p><label for="">Navigation Url<br><input type="text" name="navigation_url" value="' . $url . '"></label></p>' .
        '<p><a class="item-delete" href="javascript:;">Remove</a> |' .
        '<a class="item-close" href="javascript:;">Close</a></p>' .
        '</div>';

}

function menuTree($parent_id = 0)
{
    global $db;
    $items = '';
    $query = $db->query("SELECT * FROM menu WHERE parent_id = ? ORDER BY id_menu ASC", $parent_id);
    if ($query->numRows() > 0) {
        $items .= '<ol class="dd-list">';
        $result = $query->fetchAll();
        foreach ($result as $row) {
            $items .= renderMenuItem($row['id_menu'], $row['label_menu'], $row['url_menu']);
            $items .= menuTree($row['id_menu']);
            $items .= '</li>';
        }
        $items .= '</ol>';
    }
    return $items;
}

?>

<!doctype html>

<html lang="en">

<head>
    <meta charset="utf-8">

    <title>Nestable</title>
    <meta name="description" content="Nestable">
    <meta name="author" content="codeamend">
    <link rel="stylesheet" href="css/nestable.css">

    <link rel="stylesheet" href="css/style.css">

</head>

<body>



    <form id="add-item">
        <input type="text" name="name" placeholder="Name">
        <input type="text" name="url" placeholder="Url">
        <button type="submit">Add</button>
    </form>

    <hr />

    <div class="dd" id="nestable">
        <?php
            $html_menu = menuTree();
            echo (empty($html_menu)) ? '<ol class="dd-list"></ol>' : $html_menu;
        ?>
    </div>


    <hr />
    <form action="menu.php" method="post">
        <input type="hidden" id="nestable-output" name="menu">
        <button type="submit">Save</button>
    </form>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/Nestable/2012-10-15/jquery.nestable.min.js"></script>
    <script src="js/script.js"></script>
</body>

</html> 

Create script.js file and insert the below code

$(document).ready(function () {

    var updateOutput = function () {
        $('#nestable-output').val(JSON.stringify($('#nestable').nestable('serialize')));
    };

    $('#nestable').nestable().on('change', updateOutput);

    updateOutput();

    $("#add-item").submit(function (e) {
        e.preventDefault();
        id = Date.now();
        var label = $("#add-item > [name='name']").val();
        var url = $("#add-item > [name='url']").val();
        if ((url == "") || (label == "")) return;
        var item =
            '<li class="dd-item dd3-item" data-id="' + id + '" data-label="' + label + '" data-url="' + url + '">' +
            '<div class="dd-handle dd3-handle" > Drag</div>' +
            '<div class="dd3-content"><span>' + label + '</span>' +
            '<div class="item-edit">Edit</div>' +
            '</div>' +
            '<div class="item-settings d-none">' +
            '<p><label for="">Navigation Label<br><input type="text" name="navigation_label" value="' + label + '"></label></p>' +
            '<p><label for="">Navigation Url<br><input type="text" name="navigation_url" value="' + url + '"></label></p>' +
            '<p><a class="item-delete" href="javascript:;">Remove</a> |' +
            '<a class="item-close" href="javascript:;">Close</a></p>' +
            '</div>' +
            '</li>';

        $("#nestable > .dd-list").append(item);
        $("#nestable").find('.dd-empty').remove();
        $("#add-item > [name='name']").val('');
        $("#add-item > [name='url']").val('');
        updateOutput();
    });

    $("body").delegate(".item-delete", "click", function (e) {
        $(this).closest(".dd-item").remove();
        updateOutput();
    });


    $("body").delegate(".item-edit, .item-close", "click", function (e) {
        var item_setting = $(this).closest(".dd-item").find(".item-settings");
        if (item_setting.hasClass("d-none")) {
            item_setting.removeClass("d-none");
        } else {
            item_setting.addClass("d-none");
        }
    });

    $("body").delegate("input[name='navigation_label']", "change paste keyup", function (e) {
        $(this).closest(".dd-item").data("label", $(this).val());
        $(this).closest(".dd-item").find(".dd3-content span").text($(this).val());
    });

    $("body").delegate("input[name='navigation_url']", "change paste keyup", function (e) {
        $(this).closest(".dd-item").data("url", $(this).val());
    });

}); 

Create and insert the below code into the menu.php file

<?php

include 'db.php';

$menu = $_POST['menu'];
$array_menu = json_decode($menu, true);

$db->query('TRUNCATE TABLE menu');

function update_menu($menu,$parent = 0)
{
    global $db;

    

    if (!empty($menu)) {
        

        foreach ($menu as $value) {
            
            $label = $value['label'];
            $url = (empty($value['url'])) ? '#' : $value['url'];

            $sql = "INSERT INTO menu (label_menu, url_menu, parent_id) VALUES ('$label', '$url', $parent)";

            $db->query($sql);
            $id = $db->insertedId();

            if (array_key_exists('children', $value))
                update_menu($value['children'],$id);
        }

    }
}


update_menu($array_menu);

header("Location: index.php")
?> 

Add the below css files on index.php files to view the result

Create the nestable.css file and add it to the index.php..

.dd {
  position: relative;
  display: block;
  margin: 0;
  padding: 0;
  max-width: 600px;
  list-style: none;
  font-size: 13px;
  line-height: 20px; }

.dd-list {
  display: block;
  position: relative;
  margin: 0;
  padding: 0;
  list-style: none; }
  .dd-list .dd-list {
    padding-left: 30px; }

.dd-item,
.dd-empty,
.dd-placeholder {
  display: block;
  position: relative;
  margin: 0;
  padding: 0;
  min-height: 20px;
  font-size: 13px;
  line-height: 20px; }

.dd-handle {
  display: block;
  height: 30px;
  margin: 5px 0;
  padding: 5px 10px;
  color: #333;
  text-decoration: none;
  font-weight: bold;
  border: 1px solid #ccc;
  background: #fafafa;
  border-radius: 3px;
  box-sizing: border-box; }
  .dd-handle:hover {
    color: #2ea8e5;
    background: #fff; }

.dd-item > button {
  position: relative;
  cursor: pointer;
  float: left;
  width: 25px;
  height: 20px;
  margin: 5px 0;
  padding: 0;
  text-indent: 100%;
  white-space: nowrap;
  overflow: hidden;
  border: 0;
  background: transparent;
  font-size: 12px;
  line-height: 1;
  text-align: center;
  font-weight: bold; }
  .dd-item > button:before {
    display: block;
    position: absolute;
    width: 100%;
    text-align: center;
    text-indent: 0; }
  .dd-item > button.dd-expand:before {
    content: '+'; }
  .dd-item > button.dd-collapse:before {
    content: '-'; }

.dd-expand {
  display: none; }

.dd-collapsed .dd-list,
.dd-collapsed .dd-collapse {
  display: none; }

.dd-collapsed .dd-expand {
  display: block; }

.dd-empty,
.dd-placeholder {
  margin: 5px 0;
  padding: 0;
  min-height: 30px;
  background: #f2fbff;
  border: 1px dashed #b6bcbf;
  box-sizing: border-box;
  -moz-box-sizing: border-box; }

.dd-empty {
  border: 1px dashed #bbb;
  min-height: 100px;
  background-color: #e5e5e5;
  background-size: 60px 60px;
  background-position: 0 0, 30px 30px; }

.dd-dragel {
  position: absolute;
  pointer-events: none;
  z-index: 9999; }
  .dd-dragel > .dd-item .dd-handle {
    margin-top: 0; }
  .dd-dragel .dd-handle {
    box-shadow: 2px 4px 6px 0 rgba(0, 0, 0, 0.1); }

.dd-nochildren .dd-placeholder {
  display: none; }
 

Add style.css to the index.php file.

body {
    font-family: sans-serif;
    background: #bdc3c7;
}
input, button {
    font-size: 15px;
    padding: 5px;
}
button {
    background: #333;
    color: #fff;
}

/**
* Nestable Draggable Handles
*/
.dd3-content {
    display: block;
    height: 45px;
    padding: 5px 10px 5px 54px;
    color: #333;
    text-decoration: none;
    font-weight: bold;
    line-height: 32px;
    border: 1px solid #ccc;
    background: #fafafa;
    background: -webkit-linear-gradient(top, #fafafa 0%, #eee 100%);
    background: -moz-linear-gradient(top, #fafafa 0%, #eee 100%);
    background: linear-gradient(top, #fafafa 0%, #eee 100%);
    -webkit-border-radius: 3px;
    border-radius: 0;
    box-sizing: border-box;
    -moz-box-sizing: border-box;
}

.dd3-content:hover {
    color: #2ea8e5;
    background: #fff;
}

.dd-dragel > .dd3-item > .dd3-content {
    margin: 0;
}

    
.dd3-item {
    margin: 10px 0;
}
.dd3-item > button {
    margin-left: 30px;
}

.dd3-handle {
    position: absolute;
    margin: 0;
    left: 0;
    top: 0;
    cursor: move;
    width: 45px;
    height: 45px;
    text-indent: 70px;
    white-space: nowrap;
    overflow: hidden;
    border: 1px solid #aaa;
    background: #ddd;
    background: -webkit-linear-gradient(top, #ddd 0%, #bbb 100%);
    background: -moz-linear-gradient(top, #ddd 0%, #bbb 100%);
    background: linear-gradient(top, #ddd 0%, #bbb 100%);
    border-radius: 0;
}

.dd3-handle:before {
    content: '≡';
    display: block;
    position: absolute;
    left: 0;
    top: 12px;
    width: 100%;
    text-align: center;
    text-indent: 0;
    color: #fff;
    font-size: 25px;
    font-weight: bolder;
}

.dd3-handle:hover {
    background: #ddd;
}

.item-edit {
    font-size: 13px;
    float: right;
    color: rgb(49, 35, 248);
    cursor: pointer;
        border: 1px solid;
    padding-right: 10px;
    padding-left: 10px;
    color: black;
   
}
.item-edit:hover {
    text-decoration: underline;
}

.item-settings.d-none {
    display: none!important;
}
.item-settings {
    display: block;
    padding: 10px;
    position: relative;
    z-index: 10;
    border: 1px solid #e5e5e5;
    background: #fff;
    border-top: none;
    box-shadow: 0 1px 1px rgba(0,0,0,.04);
}
.item-settings p {
    margin-top: 0;
}

.item-settings p label {
    font-size: 13px;
    color: #666;
    line-height: 1.5;
}

.item-settings p label input {
    border: 1px solid #ddd;
    box-shadow: inset 0 1px 2px rgba(0,0,0,.07);
    background-color: #fff;
    color: #32373c;
    outline: 0;
    border-spacing: 0;
    width: -webkit-fill-available;
    clear: both;
    margin: 0;
    font-size: 14px;
    padding: 5px;
    border-radius: 0;
}

.item-settings .item-delete {
    color: #a00;
}

 
Posted in jQuery, PHP

You can also read...