Apakah mungkin membuat Tampilan Pohon dengan Angular?

177

Saya ingin menampilkan data dalam struktur pohon di aplikasi web. Saya berharap menggunakan Angular untuk tugas ini.

Sepertinya ng-repeat akan memungkinkan saya untuk beralih melalui daftar node, tetapi bagaimana saya bisa melakukan bersarang ketika kedalaman node yang diberikan meningkat?

Saya mencoba kode berikut , tetapi pelolosan otomatis dari HTML mencegahnya bekerja. Plus, tag ul akhir berada di tempat yang salah.

Saya cukup yakin bahwa saya akan menyelesaikan masalah ini dengan cara yang salah.

Ada ide?

Jon Abrams
sumber
Saya baru saja menjawab ini dengan cara yang cukup umum pada pertanyaan yang berbeda: stackoverflow.com/questions/14430655/…
tilgovi

Jawaban:

231

Lihatlah biola ini

Asli: http://jsfiddle.net/brendanowen/uXbn6/8/

Diperbarui: http://jsfiddle.net/animaxf/uXbn6/4779/

Ini akan memberi Anda ide yang baik tentang cara menampilkan tree like structuresudut menggunakan. Ini semacam menggunakan rekursi dalam html!

Ganaraj
sumber
94
mengapa tidak menyebutkan sumber Anda ? Anda menulis posting di utas itu, dan sekarang Anda memposting url di sini dengan nama Anda sendiri di dalamnya?
Janus Troelsen
5
Ini adalah versi yang identik (saya pikir), kecuali bahwa memuat jauh lebih cepat (setidaknya bagi saya), karena tidak memiliki Twitter Bootstrap yang diuraikan di bagian CSS. jsfiddle.net/brendanowen/uXbn6/8
KajMagnus
10
Bung Anda harus menyatakan sumber Anda.
Ajax3.14
46
Saya benar-benar bosan dengan orang-orang yang terus-menerus berkomentar tentang hal ini bahwa URL memiliki nama saya di dalamnya (dan karena itu plagiarisme!). Sayangnya itulah cara jsfiddle bekerja. Jika Anda melakukan fork sesuatu saat Anda login itu tetap mempertahankan nama pengguna Anda. Setelah mengatakan bahwa saya sekarang telah ditautkan ke URL asli. Turunkan jawaban jika salah - Jawabannya benar dalam skenario ini dengan satu hal yang sepertinya cadangan URL saya mengandung nama saya di dalamnya.
ganaraj
5
Saya baru saja menambahkan tombol runtuh dan perluas ke versi Anda: jsfiddle.net/uXbn6/639
jbaylina
77

Jika Anda menggunakan Bootstrap CSS ...

Saya telah membuat kontrol pohon sederhana yang dapat digunakan kembali (direktif) untuk AngularJS berdasarkan daftar Bootstrap "nav". Saya menambahkan lekukan ekstra, ikon, dan animasi. Atribut HTML digunakan untuk konfigurasi.

Itu tidak menggunakan rekursi.

Saya menyebutnya angular-bootstrap-nav-tree (nama yang mudah diingat, bukan?)

Ada contoh di sini , dan sumbernya ada di sini .

Nick Perkins
sumber
1
Ini indah, tetapi berhati-hatilah karena itu tidak bekerja pada cabang Angular 1.0.x.
Danita
3
Ya, ini menggunakan animasi baru ... memerlukan Angular 1.1.5 (saya pikir?)
Nick Perkins
3
UPDATE: sekarang bekerja dengan Angular 1.1.5 atau Angular 1.2.0, dan juga bekerja dengan Bootsrap 2 atau Bootstrap 3
Nick Perkins
1
FYI saja, jika menggunakan Bower, Nick sekarang membuat ini tersedia untuk instalasi mudah - "pencarian bower angular-bootstrap-nav-tree", dan "bower instal angular-bootstrap-nav-tree --save" dan Anda selesai.
arcseldon
2
@Nick Perkins - tolong jelaskan mengapa angular-bootstrap-nav-tree tidak memiliki API untuk menghapus Branch / Node. Setidaknya, dari pemeriksaan cepat terhadap sumber, dan memeriksa tes / contoh Anda tampaknya tidak ada pilihan itu. Ini benar-benar kelalaian?
arcseldon
35

Ketika membuat sesuatu seperti ini, solusi terbaik adalah arahan rekursif. Namun, ketika Anda membuat arahan Anda menemukan bahwa AngularJS masuk ke loop tanpa akhir.

Solusi untuk ini adalah membiarkan direktif menghapus elemen selama acara kompilasi, dan secara manual mengkompilasi dan menambahkannya di acara tautan.

Saya menemukan ini di utas ini , dan mengabstraksikan fungsi ini menjadi sebuah layanan .

module.factory('RecursionHelper', ['$compile', function($compile){
    return {
        /**
         * Manually compiles the element, fixing the recursion loop.
         * @param element
         * @param [link] A post-link function, or an object with function(s) registered via pre and post properties.
         * @returns An object containing the linking functions.
         */
        compile: function(element, link){
            // Normalize the link parameter
            if(angular.isFunction(link)){
                link = { post: link };
            }

            // Break the recursion loop by removing the contents
            var contents = element.contents().remove();
            var compiledContents;
            return {
                pre: (link && link.pre) ? link.pre : null,
                /**
                 * Compiles and re-adds the contents
                 */
                post: function(scope, element){
                    // Compile the contents
                    if(!compiledContents){
                        compiledContents = $compile(contents);
                    }
                    // Re-add the compiled contents to the element
                    compiledContents(scope, function(clone){
                        element.append(clone);
                    });

                    // Call the post-linking function, if any
                    if(link && link.post){
                        link.post.apply(null, arguments);
                    }
                }
            };
        }
    };
}]);

Dengan layanan ini Anda dapat dengan mudah membuat arahan pohon (atau arahan rekursif lainnya). Berikut adalah contoh arahan pohon:

module.directive("tree", function(RecursionHelper) {
    return {
        restrict: "E",
        scope: {family: '='},
        template: 
            '<p>{{ family.name }}</p>'+
            '<ul>' + 
                '<li ng-repeat="child in family.children">' + 
                    '<tree family="child"></tree>' +
                '</li>' +
            '</ul>',
        compile: function(element) {
            return RecursionHelper.compile(element);
        }
    };
});

Lihat Plunker ini untuk demo. Saya suka solusi ini karena:

  1. Anda tidak memerlukan arahan khusus yang membuat html Anda kurang bersih.
  2. Logika rekursi disarikan ke layanan RecursionHelper, sehingga Anda menjaga arahan Anda tetap bersih.

Pembaruan: Dukungan tambahan untuk fungsi penautan kustom.

Mark Lagendijk
sumber
1
ini tampaknya sangat rapi dan kuat, tahu mengapa ini bukan perilaku default di angularjs?
Paul
Saat menggunakan "kompilasi" seperti ini, bagaimana cara menambahkan atribut tambahan ke cakupan? Fungsi "tautan" tampaknya tidak lagi tersedia begitu "kompilasi" ada ...
Brian Kent
1
@ bkent314 Saya menambahkan dukungan untuk ini. Sekarang menerima fungsi menghubungkan dengan cara yang sama seperti kompilasi dapat mengembalikannya. Saya juga membuat proyek Github untuk layanan ini.
Mark Lagendijk
@MarkLagendijk Sangat, sangat apik! Anda layak mendapatkan banyak upvotes untuk mengabstraksi rekursi dari arahan. Semua arahan yang saya lihat terlihat sangat rumit dengan logika yang bercampur aduk. Apakah ada cara untuk membuat RecursionHelper Anda bekerja dengan transklusi?
acjay
Saya benar-benar menyarankan agar Anda membuang beberapa data pada jenis solusi ini - ya, hampir semua orang menerapkan pohon dengan arahan rekursif, mudah. Tapi ini sangat lambat sebagai ng-repeat $ digest - setelah Anda mencapai ratusan node, ini tidak berfungsi.
Artemiy
17

angular-ui-tree tampaknya melakukan pekerjaan yang baik untuk saya

Kalyanaraman Santhanam
sumber
15

Berikut ini contoh menggunakan arahan rekursif: http://jsfiddle.net/n8dPm/ Diambil dari https://groups.google.com/forum/#!topic/angular/vswXTes_FtM

module.directive("tree", function($compile) {
return {
    restrict: "E",
    scope: {family: '='},
    template: 
        '<p>{{ family.name }}</p>'+
        '<ul>' + 
            '<li ng-repeat="child in family.children">' + 
                '<tree family="child"></tree>' +
            '</li>' +
        '</ul>',
    compile: function(tElement, tAttr) {
        var contents = tElement.contents().remove();
        var compiledContents;
        return function(scope, iElement, iAttr) {
            if(!compiledContents) {
                compiledContents = $compile(contents);
            }
            compiledContents(scope, function(clone, scope) {
                     iElement.append(clone); 
            });
        };
    }
};
});
savagepanda
sumber
saya sedang bereksperimen dengan ini, dan saya ingin menggunakan transklusi juga, apakah Anda pikir itu mungkin?
L.Trabacchin
5

Contoh lain berdasarkan sumber aslinya , dengan struktur pohon sampel sudah ada (lebih mudah untuk melihat cara kerjanya IMO) dan filter untuk mencari pohon:

JSFiddle

GFoley83
sumber
4

Banyak sekali solusi hebat, tapi saya merasa semuanya sedikit rumit.

Saya ingin membuat sesuatu yang menciptakan kembali kesederhanaan dari awnser @Mark Lagendijk, tetapi tanpa itu mendefinisikan template dalam direktif, tetapi akan membiarkan "pengguna" membuat template dalam HTML ...

Dengan gagasan yang diambil dari https://github.com/stackfull/angular-tree-repeat dll ... Saya akhirnya membuat proyek: https://github.com/dotJEM/angular-tree

Yang memungkinkan Anda untuk membangun pohon Anda seperti:

<ul dx-start-with="rootNode">
  <li ng-repeat="node in $dxPrior.nodes">
    {{ node.name }}
    <ul dx-connect="node"/>
  </li>
</ul>

Yang bagi saya lebih bersih daripada harus membuat banyak arahan untuk pohon-pohon yang terstruktur berbeda .... Pada dasarnya menyebut di atas pohon agak salah, ia mengambil jauh lebih banyak dari awnser @ ganaraj tentang "templat rekursif", tetapi memungkinkan kita untuk tentukan template tempat kita membutuhkan pohon.

(Anda bisa melakukannya dengan template berbasis tag skrip, tetapi masih harus duduk tepat di luar simpul pohon yang sebenarnya, dan itu masih terasa agak yuk ...)

Ditinggalkan di sini hanya untuk pilihan lain ...

Jens
sumber
UPDATE: Pada 1,5 arahan rekursif sekarang agak asli didukung dalam Angular. Ini mempersempit kasus penggunaan untuk dotjem / angular-tree banyak.
Jens
3

Anda dapat mencoba dengan sampel Angular-Tree-DnD dengan Angular-Ui-Tree, tetapi saya diedit, kompatibilitasnya dengan tabel, kisi, daftar.

  • Mampu Seret & Jatuhkan
  • Arahan fungsi diperluas untuk daftar (selanjutnya, selanjutnya, getChildren, ...)
  • Saring data.
  • OrderBy (ver)
Nguyễn Thiện Hùng
sumber
Terima kasih. Saya membutuhkan Drag & Drop, dan ini tampaknya menjadi satu-satunya solusi dengan itu!
Doug
2

Berdasarkan @ganaraj 's jawaban , dan @ dnc253' s jawaban , saya hanya membuat sederhana 'direktif' untuk struktur pohon memiliki memilih, menambahkan, menghapus, dan mengedit fitur.

Jsfiddle: http://jsfiddle.net/yoshiokatsuneo/9dzsms7y/

HTML:

<script type="text/ng-template" id="tree_item_renderer.html">
    <div class="node"  ng-class="{selected: data.selected}" ng-click="select(data)">
        <span ng-click="data.hide=!data.hide" style="display:inline-block; width:10px;">
            <span ng-show="data.hide && data.nodes.length > 0" class="fa fa-caret-right">+</span>
            <span ng-show="!data.hide && data.nodes.length > 0" class="fa fa-caret-down">-</span>
        </span>
        <span ng-show="!data.editting" ng-dblclick="edit($event)" >{{data.name}}</span>
        <span ng-show="data.editting"><input ng-model="data.name" ng-blur="unedit()" ng-focus="f()"></input></span>
        <button ng-click="add(data)">Add node</button>
        <button ng-click="delete(data)" ng-show="data.parent">Delete node</button>
    </div>
    <ul ng-show="!data.hide" style="list-style-type: none; padding-left: 15px">
        <li ng-repeat="data in data.nodes">
            <recursive><sub-tree data="data"></sub-tree></recursive>
        </li>
    </ul>
</script>
<ul ng-app="Application" style="list-style-type: none; padding-left: 0">
    <tree data='{name: "Node", nodes: [],show:true}'></tree>
</ul>

JavaScript:

angular.module("myApp",[]);

/* https://stackoverflow.com/a/14657310/1309218 */
angular.module("myApp").
directive("recursive", function($compile) {
    return {
        restrict: "EACM",
        require: '^tree',
        priority: 100000,

        compile: function(tElement, tAttr) {
            var contents = tElement.contents().remove();
            var compiledContents;
            return function(scope, iElement, iAttr) {
                if(!compiledContents) {
                    compiledContents = $compile(contents);
                }
                compiledContents(scope, 
                                     function(clone) {
                         iElement.append(clone);
                                         });
            };
        }
    };
});

angular.module("myApp").
directive("subTree", function($timeout) {
    return {
        restrict: 'EA',
        require: '^tree',
        templateUrl: 'tree_item_renderer.html',
        scope: {
            data: '=',
        },
        link: function(scope, element, attrs, treeCtrl) {
            scope.select = function(){
                treeCtrl.select(scope.data);
            };
            scope.delete = function() {
                scope.data.parent.nodes.splice(scope.data.parent.nodes.indexOf(scope.data), 1);
            };
            scope.add = function() {
                var post = scope.data.nodes.length + 1;
                var newName = scope.data.name + '-' + post;
                scope.data.nodes.push({name: newName,nodes: [],show:true, parent: scope.data});
            };
            scope.edit = function(event){
                scope.data.editting = true;
                $timeout(function(){event.target.parentNode.querySelector('input').focus();});
            };
            scope.unedit = function(){
                scope.data.editting = false;
            };

        }
    };
});


angular.module("myApp").
directive("tree", function(){
    return {
        restrict: 'EA',
        template: '<sub-tree data="data" root="data"></sub-tree>',
        controller: function($scope){
            this.select = function(data){
                if($scope.selected){
                    $scope.selected.selected = false;
                }
                data.selected = true;
                $scope.selected = data;
            };
        },
        scope: {
            data: '=',
        }
    }
});
Tsuneo Yoshioka
sumber
0

Ya itu pasti mungkin. Pertanyaan di sini mungkin mengasumsikan Angular 1.x, tetapi untuk referensi di masa mendatang saya menyertakan contoh Angular 2:

Secara konseptual yang harus Anda lakukan adalah membuat templat rekursif:

<ul>
    <li *for="#dir of directories">

        <span><input type="checkbox" [checked]="dir.checked" (click)="dir.check()"    /></span> 
        <span (click)="dir.toggle()">{{ dir.name }}</span>

        <div *if="dir.expanded">
            <ul *for="#file of dir.files">
                {{file}}
            </ul>
            <tree-view [directories]="dir.directories"></tree-view>
        </div>
    </li>
</ul>

Anda kemudian mengikat objek pohon ke templat dan membiarkan Angular mengerjakan keajaibannya. Konsep ini jelas berlaku untuk Angular 1.x juga.

Berikut adalah contoh lengkapnya: http://www.syntaxsuccess.com/viewarticle/recursive-treeview-in-angular-2.0

TGH
sumber
0

Anda dapat menggunakan angular-recursion-injector untuk itu: https://github.com/knyga/angular-recursion-injector

Memungkinkan Anda melakukan sarang yang tidak terbatas dengan pengkondisian. Apakah kompilasi hanya jika diperlukan dan hanya mengkompilasi elemen yang tepat. Tidak ada keajaiban dalam kode.

<div class="node">
  <span>{{name}}</span>

  <node--recursion recursion-if="subNode" ng-model="subNode"></node--recursion>
</div>

Salah satu hal yang memungkinkannya bekerja lebih cepat dan lebih sederhana maka solusi lain adalah sufiks "- recursion".

Oleksandr Knyga
sumber
0

Ketika struktur pohon besar, Angular (hingga 1.4.x) menjadi sangat lambat dalam merender template rekursif. Setelah mencoba sejumlah saran ini, saya akhirnya membuat string HTML sederhana dan menggunakan ng-bind-htmluntuk menampilkannya. Tentu saja, ini bukan cara untuk menggunakan fitur Angular

Fungsi rekursif tanpa tulang ditampilkan di sini (dengan HTML minimal):

function menu_tree(menu, prefix) {
    var html = '<div>' + prefix + menu.menu_name + ' - ' + menu.menu_desc + '</div>\n';
    if (!menu.items) return html;
    prefix += menu.menu_name + '/';
    for (var i=0; i<menu.items.length; ++i) {
        var item = menu.items[i];
        html += menu_tree(item, prefix);
    }
    return html;
}
// Generate the tree view and tell Angular to trust this HTML
$scope.html_menu = $sce.trustAsHtml(menu_tree(menu, ''));

Dalam templat, hanya perlu satu baris ini:

<div ng-bind-html="html_menu"></div>

Ini memotong semua data Angular yang mengikat dan hanya menampilkan HTML dalam sepersekian waktu dari metode templat rekursif.

Dengan struktur menu seperti ini (sebagian file pohon dari sistem file Linux):

menu = {menu_name: '', menu_desc: 'root', items: [
            {menu_name: 'bin', menu_desc: 'Essential command binaries', items: [
                {menu_name: 'arch', menu_desc: 'print machine architecture'},
                {menu_name: 'bash', menu_desc: 'GNU Bourne-Again SHell'},
                {menu_name: 'cat', menu_desc: 'concatenate and print files'},
                {menu_name: 'date', menu_desc: 'display or set date and time'},
                {menu_name: '...', menu_desc: 'other files'}
            ]},
            {menu_name: 'boot', menu_desc: 'Static files of the boot loader'},
            {menu_name: 'dev', menu_desc: 'Device files'},
            {menu_name: 'etc', menu_desc: 'Host-specific system configuration'},
            {menu_name: 'lib', menu_desc: 'Essential shared libraries and kernel modules'},
            {menu_name: 'media', menu_desc: 'Mount point for removable media'},
            {menu_name: 'mnt', menu_desc: 'Mount point for mounting a filesystem temporarily'},
            {menu_name: 'opt', menu_desc: 'Add-on application software packages'},
            {menu_name: 'sbin', menu_desc: 'Essential system binaries'},
            {menu_name: 'srv', menu_desc: 'Data for services provided by this system'},
            {menu_name: 'tmp', menu_desc: 'Temporary files'},
            {menu_name: 'usr', menu_desc: 'Secondary hierarchy', items: [
                {menu_name: 'bin', menu_desc: 'user utilities and applications'},
                {menu_name: 'include', menu_desc: ''},
                {menu_name: 'local', menu_desc: '', items: [
                    {menu_name: 'bin', menu_desc: 'local user binaries'},
                    {menu_name: 'games', menu_desc: 'local user games'}
                ]},
                {menu_name: 'sbin', menu_desc: ''},
                {menu_name: 'share', menu_desc: ''},
                {menu_name: '...', menu_desc: 'other files'}
            ]},
            {menu_name: 'var', menu_desc: 'Variable data'}
        ]
       }

Outputnya menjadi:

- root
/bin - Essential command binaries
/bin/arch - print machine architecture
/bin/bash - GNU Bourne-Again SHell
/bin/cat - concatenate and print files
/bin/date - display or set date and time
/bin/... - other files
/boot - Static files of the boot loader
/dev - Device files
/etc - Host-specific system configuration
/lib - Essential shared libraries and kernel modules
/media - Mount point for removable media
/mnt - Mount point for mounting a filesystem temporarily
/opt - Add-on application software packages
/sbin - Essential system binaries
/srv - Data for services provided by this system
/tmp - Temporary files
/usr - Secondary hierarchy
/usr/bin - user utilities and applications
/usr/include -
/usr/local -
/usr/local/bin - local user binaries
/usr/local/games - local user games
/usr/sbin -
/usr/share -
/usr/... - other files
/var - Variable data
Brent Washburne
sumber
-3

Tidak rumit.

<div ng-app="Application" ng-controller="TreeController">
    <table>
        <thead>
            <tr>
                <th>col 1</th>
                <th>col 2</th>
                <th>col 3</th>
            </tr>
        </thead>
        <tbody ng-repeat="item in tree">
            <tr>
                <td>{{item.id}}</td>
                <td>{{item.fname}}</td>
                <td>{{item.lname}}</td>
            </tr>
            <tr ng-repeat="children in item.child">
                <td style="padding-left:15px;">{{children.id}}</td>
                <td>{{children.fname}}</td>
            </tr>
        </tbody>
     </table>
</div>

kode pengontrol:

angular.module("myApp", []).
controller("TreeController", ['$scope', function ($scope) {
    $scope.tree = [{
        id: 1,
        fname: "tree",
        child: [{
            id: 1,
            fname: "example"
        }],
        lname: "grid"
    }];


}]);
MBK
sumber