7
\$\begingroup\$

I'm experimenting with angular directives, and as a proof of concept I wanted to create a basic tab control. I'd like to know if anything can be changed to be more fluid, or if I'm doing things "right".

Live Example: http://jsfiddle.net/6wa2x/

HTML (with embedded angular templates):

<!doctype html>
<html ng-app="mainModule">
<body>
<tab-control id="MainTabControl" class="red">
        <tab name="FirstTab" id="Tab1">
            Tab 1 stuff
        </tab>
        <tab name="SecondTab" id="Tab2" selected>
            Tab 2 stuff
        </tab>
        <tab name="ThirdTab" id="Tab3">
            <strong>Tab 3</strong>
        </tab>
    </tab-control>

    <div ng-controller="DynamicTabController">
        <tab-control>
            <tab ng-repeat="tab in tabs" name="{{tab.name}}" id="{{tab.id}}" selected="{{ $index == 1 }}">
                {{tab.name}}
            </tab>
        </tab-control>
    </div>

    <script type="text/ng-template" id="tabControlTemplate">
        <ul id="{{id}}" class="{{klass}} tab-control">
            <li ng-repeat="tab in tabs" ng-class="{ selected: tab.selected }" class="tab" ng-click="selectTab(tab)">{{tab.name}}</li>
        </ul>

        <section ng-transclude>
        </section>
    </script>

    <script type="text/ng-template" id="tabTemplate">
        <article id="{{id}}" ng-class="{ selected: selected}" class="{{klass}} tab-content">
            <header>
                <h1>{{ name }}</h1>
            </header>
            <section ng-transclude>
            </section>
        </article>
    </script>
    </body>
</html>

Javascript:

var mainModule = angular.module("mainModule", []);

        mainModule.directive('tabControl', function() {
            return {
                restrict: 'E',
                templateUrl: 'tabControlTemplate',
                scope: {
                    id: '@id',
                    klass: '@class',
                },
                transclude: true,
                controller: ['$scope', function($scope) {
                    $scope.tabs = []

                    this.addTab = function(tab){
                        $scope.tabs.push(tab);
                    }

                    $scope.selectTab = function(tab){
                        for(var i=0; i<$scope.tabs.length; i++){
                            if(tab.name != $scope.tabs[i].name){
                                $scope.tabs[i].selected = false;
                            }
                            else {
                                $scope.tabs[i].selected = true;
                            }
                        }
                    }
                }]
            };
        });

        mainModule.directive('tab', function(){
            return {
                restrict: 'E',
                templateUrl: 'tabTemplate',
                transclude: true,
                replace: true,
                scope: {
                    id: '@id',
                    name: '@name',
                },
                require: '^tabControl',
                link: function(scope, element, attrs, ctrl) {
                    scope.selected = attrs.selected == "" || attrs.selected == true
                    ctrl.addTab(scope);
                }
            };
        });

        mainModule.controller("DynamicTabController", ['$scope', function($scope){
            $scope.tabs = [
                { name: 'Tab1', id: 'Tab1'},
                { name: 'Tab2', id: 'Tab2'},
                { name: 'Tab3', id: 'Tab3'},
                { name: 'Tab4', id: 'Tab4'},
                { name: 'Tab5', id: 'Tab5'},
                { name: 'Tab6', id: 'Tab6'},
                { name: 'Tab7', id: 'Tab7'}
            ]
        }]);

CSS:

.tab-control {
    list-style: none;
    padding: 0;
    margin: 5px;
}

.tab-control li {
    display: inline;
    padding: 5px 20px;
    border: 1px solid grey;
    border-bottom: 0;
    border-radius: 5;
    cursor: pointer;
}

.tab-control li.selected {
    background-color: lightblue;
}

.tab-content header {
    display : none;
}

.tab-content {
    border: 1px solid black;
    width: 100%;
    height: 100%;
    display: none;
}

.tab-content.selected {
    display: block;
}
\$\endgroup\$
4
\$\begingroup\$

From a once over:

  • It seems both <tab name="FirstTab" id="Tab1"> and <tab ng-repeat="tab in tabs" name="{{tab.name}}" id="{{tab.id}}" selected="{{ $index == 1 }}"> will share the id Tab1, that's not good

  • I am not sure what the point is of setting name in the elements, id should suffice

  • I am not sure what the point is of tab2ContentTemplate, deleting it does not change functionality

  • This :

    if (tab.name != $scope.tabs[i].name) {
        $scope.tabs[i].selected = false;
    } else {
        $scope.tabs[i].selected = true;
    }
    

    could be

    $scope.tabs[i].selected = ( tab.name == $scope.tabs[i].name );
    
  • There are some minor style issues you could find/solve by using jshint.com

All in all, I find the HTML part very hard to follow, only after playing with it for a while did I really grasp what you are doing. You should carefully review the id's you employ. For example: tabTemplate <- I would call this articleTemplate perhaps ?

\$\endgroup\$
  • \$\begingroup\$ Yes, in this example your right, both FirstTab and the ng-repeated will end up with the same id, it was more an example using static tabs vs dynamically created tabs. Id can suffice, the difference was to allow id to be the tabs identifier, whereas the name is what's displayed on the tab itself. tab2Content template was removed, your right it wasn't used in the example (copy paste error). I don't agree to renaming tabTemplate to articleTemplate, the template is more about the tab content (using article in this case as an example.) maybe "TabContentTemplate". Thank you very much 4 the feedback! \$\endgroup\$ – Mitchell Lee Apr 17 '14 at 16:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.