Açısal ile Ağaç Görünümü yapmak mümkün müdür?


177

Bir web uygulamasında bir ağaç yapısında veri görüntülemek istiyorum. Bu görev için Angular'ı kullanmayı umuyordum.

Görünüşe göre ng-repeat bir düğüm listesi üzerinden yineleme yapmama izin veriyor, ancak belirli bir düğümün derinliği arttığında nasıl iç içe yerleştirebilirim?

Aşağıdaki kodu denedim , ancak HTML otomatik kaçan bu çalışmasını engelliyor. Ayrıca, son ul etiketi yanlış yerde.

Bu problemi tamamen yanlış şekilde yapacağımdan eminim.

Herhangi bir fikir?


Bunu farklı bir soruya oldukça genel bir şekilde cevapladım: stackoverflow.com/questions/14430655/…
tilgovi

Yanıtlar:


231

Bu kemana bir bak

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

Güncelleme: http://jsfiddle.net/animaxf/uXbn6/4779/

Bu, tree like structureaçısal kullanımın nasıl gösterileceği hakkında iyi bir fikir vermelidir . Html içinde özyineleme kullanarak bir tür!


94
neden kaynağını belirtmiyorsun ? bu konuya bir yazı yazdınız ve şimdi burada kendi adınızla bir url yayınlıyor musunuz?
Janus Troelsen

5
İşte aynı versiyonu (sanırım), CSS bölümünde Twitter Bootstrap'e sahip olmadığı için (en azından benim için) çok daha hızlı yükleniyor. jsfiddle.net/brendanowen/uXbn6/8
KajMagnus

10
ahbap kaynağını belirtmelisin.
Ajax3.14

46
Bu konuda sürekli yorum yapan insanlardan gerçekten çok yoruldum, URL'nin ismim var (ve bu nedenle intihal!). Ne yazık ki jsfiddle böyle çalışır. Eğer giriş yaparken bir şey çatal, kullanıcı adınızı korur. Şimdi orijinal URL ile bağlantı kurduğumu söyledikten sonra. Yanlışsa bir cevabı küçümseyin - Cevap, bu senaryoda doğru URL'nin, adımı içerdiği bir şeyle doğru olur.
ganaraj

5
Sürümünüze daralt ve genişlet düğmesini ekledim
jbaylina

77

Bootstrap CSS kullanıyorsanız ...

Bir Bootstrap "nav" listesine dayalı AngularJS için basit bir yeniden kullanılabilir ağaç kontrolü (yönerge) oluşturduk. Ekstra girinti, simgeler ve animasyon ekledim. HTML özellikleri yapılandırma için kullanılır.

Özyineleme kullanmaz.

Ben buna açısal-bootstrap-nav-tree diyorum (akılda kalıcı isim, değil mi?)

Burada bir örnek var ve kaynak burada .


1
Çok güzel, ama Angular 1.0.x dalında çalışmadığı konusunda uyarılmalıdır.
Danita

3
Evet, yeni animasyon malzemelerini kullanıyor ... Açısal 1.1.5 gerektirir (sanırım?)
Nick Perkins

3
GÜNCELLEME: artık Açısal 1.1.5 veya Açısal 1.2.0 ile çalışıyor ve ayrıca Bootsrap 2 veya Bootstrap 3 ile çalışıyor
Nick Perkins

1
Sadece FYI, Bower kullanıyorsa, Nick şimdi bunu kolay kurulum için kullanılabilir hale getirdi - "bower arama açısal-bootstrap-nav-tree" ve "bower install açısal-bootstrap-nav-tree --save" ve işiniz bitti.
arcseldon

2
@Nick Perkins - lütfen açısal önyükleme-nav-ağacınızın neden bir Şube / Düğümü kaldırmak için API'si olmadığını açıklayabilir misiniz? En azından, kaynağın hızlı bir şekilde incelenmesinden ve testinizin / örneklerinizin kontrol edilmesinden bu seçenek görünmüyor. Bu kesinlikle kritik bir ihmal mi?
arcseldon

35

Böyle bir şey yaparken en iyi çözüm özyinelemeli bir direktiftir. Ancak, böyle bir yönerge yaptığınızda AngularJS'nin sonsuz bir döngüye girdiğini öğrenirsiniz.

Bunun çözümü, derlemenin compile olayı sırasında öğeyi kaldırmasına ve bunları el ile derlemesine ve link olaylarına eklemesine izin vermektir.

Ben de bu konuda öğrendim bu iş parçacığı ve bu işlevselliği soyutlanmış bir hizmete .

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);
                    }
                }
            };
        }
    };
}]);

Bu hizmetle kolayca bir ağaç yönergesi (veya diğer özyinelemeli yönergeler) yapabilirsiniz. Bir ağaç yönergesi örneği:

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);
        }
    };
});

Bir demo için bu Plunker'a bakın . Bu çözümü en çok seviyorum çünkü:

  1. Html'nizi daha az temiz yapan özel bir yönerge gerekmez.
  2. Özyineleme mantığı, RecursionHelper hizmetine kaldırılır, böylece yönergelerinizi temiz tutarsınız.

Güncelleme: Özel bağlantı işlevleri için destek eklendi.


1
Bu çok düzgün ve güçlü görünüyor, bunun neden angularjs'da varsayılan bir davranış olmadığı hakkında bir fikriniz var mı?
Paul

Bunun gibi "derleme" kullanılırken, kapsama nasıl ek nitelikler eklenir? "Derleme" bir kez orada "link" fonksiyonu artık mevcut gibi görünmüyor ...
Brian Kent

1
@ bkent314 Buna destek ekledim. Artık derleme işlevlerini derleyebildiği gibi bağlama işlevlerini kabul eder. Hizmet için bir Github projesi de oluşturdum.
Mark Lagendijk

@MarkLagendijk Çok, çok kaygan! Direktifin özyinelemesini soyutlamak için birçok oyu hak ediyorsunuz. Gördüğüm tüm direktifler bu mantıkla umutsuzca karmaşık görünüyor.
acjay

Gerçekten bu tür bir çözüme bazı veriler atmanızı öneririm - evet, neredeyse herkes ağacı özyinelemeli yönergelerle uygular, kolaydır. Ancak $ digest'in tekrarladığı gibi son derece yavaştır - yüzlerce düğüme ulaştığınızda, bu gerçekleşmez.
Artemiy


15

İşte özyinelemeli yönergesini kullanarak bir örnek: http://jsfiddle.net/n8dPm/ Alındığı 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); 
            });
        };
    }
};
});

Ben bunu deniyordum ve ben de transkripsiyon kullanmak istiyorum, bunun mümkün olduğunu düşünüyor musunuz?
L.Trabacchin



4

Pek çok harika çözüm, ama hepsinin bir şekilde aşırı karmaşık olduğunu hissediyorum.

@Mark Lagendijk'in enkazının basitliğini yeniden yaratan bir şey yaratmak istedim, ancak direktifte bir şablon tanımlamaksızın, "kullanıcı" nın HTML'de şablonu oluşturmasına izin verdim ...

Https://github.com/stackfull/angular-tree-repeat vb. ' Den alınan fikirlerle ... Projeyi oluşturmaya başladım: https://github.com/dotJEM/angular-tree

Bu da ağacınızı şöyle inşa etmenizi sağlar:

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

Hangi benim için farklı yapılandırılmış ağaçlar için birden fazla direktif oluşturmak zorunda daha temiz .... Özünde yukarıda bir ağaç çağırmak biraz yanlış, @ ganaraj "özyinelemeli şablonlar" uyandırıcı çok daha fazla alır, ama bize izin verir ağaca ihtiyacımız olan şablonu tanımlar.

(bunu komut dosyası etiketi tabanlı bir şablonla yapabilirsiniz, ancak yine de gerçek ağaç düğümünün hemen dışında oturması gerekir ve yine de biraz yuk hisseder ...)

Başka bir seçenek için burada kaldı ...


GÜNCELLEME: 1.5'ten itibaren tekrarlanan direktifler şu anda Angular'da yerel olarak desteklenmektedir. Bu, dotjem / açısal ağaç için kullanım durumlarını büyük ölçüde daraltır.
Jens

3

Angular-Ui-Tree ile Angular-Tree-DnD örneği ile deneyebilirsiniz , ancak ben düzenledim, tablo, ızgara, liste ile uyumluluk.

  • Able Sürükle ve Bırak
  • Liste için genişletilmiş işlev yönergesi (next, prev, getChildren, ...)
  • Verilere filtre uygulayın.
  • OrderBy (ver)

Teşekkür ederim. Sürükle & Bırak'a ihtiyacım vardı ve bu tek çözüm gibi görünüyor!
Doug

2

@Ganaraj dayanarak 'ın cevabı ve @ dnc253' ın cevabı , sadece ağaç yapısı, seçme sahip ekleme, silme ve özellik düzenlemek için basit 'yönergesi' yaptı.

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: '=',
        }
    }
});

0

Evet kesinlikle mümkün. Buradaki soru muhtemelen Açısal 1.x olduğunu varsayar, ancak ileride başvurmak için bir Açısal 2 örneği ekliyorum:

Kavramsal olarak yapmanız gereken tek şey yinelemeli bir şablon oluşturmaktır:

<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>

Daha sonra bir ağaç nesnesini şablona bağlar ve Açısal'ın sihrini çalıştırmasına izin verirsiniz. Bu konsept Angular 1.x için de geçerlidir.

İşte tam bir örnek: http://www.syntaxsuccess.com/viewarticle/recursive-treeview-in-angular-2.0


0

Bunun için açısal-özyineleme-enjektör kullanabilirsiniz: https://github.com/knyga/angular-recursion-injector

Koşullandırma ile sınırsız derinlik yuvalama yapmanızı sağlar. Yalnızca gerektiğinde yeniden derleme yapar ve yalnızca doğru öğeleri derler. Kodda sihir yok.

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

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

Diğer çözümlerden daha hızlı ve daha basit çalışmasını sağlayan şeylerden biri "--recursion" son ekidir.


0

Ağaç yapısı büyük olduğunda, açısal (1.4.x'e kadar) özyinelemeli bir şablon oluşturmada çok yavaşlar. Bu önerilerin bir kısmını denedikten sonra, basit bir HTML dizesi oluşturup ng-bind-htmlgörüntülemek için kullandım. Tabii ki, bu açısal özellikleri kullanmanın yolu değil

Çıplak kemik özyinelemeli fonksiyon burada gösterilir (minimum HTML ile):

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, ''));

Şablonda, yalnızca bu tek satıra ihtiyacı vardır:

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

Bu, Angular'ın tüm veri bağlamasını atlar ve HTML'yi özyinelemeli şablon yöntemlerinin zamanının çok kısa bir bölümünde görüntüler.

Böyle bir menü yapısıyla (bir Linux dosya sisteminin kısmi bir dosya ağacı):

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'}
        ]
       }

Çıktı şöyle olur:

- 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

-3

Karmaşık değil.

<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>

denetleyici kodu:

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


}]);
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.