0

I am building a web application for our customer support. One of the needs is to be able to keep multiple tickets opened at the same time.
I was able to do the first part easily using a tabulation system and UI-Router. However, with my current implementation, each time I change active tab, the previously-current tab is unloaded, and the now-current tab is loaded (because it was unloaded with a previous tab change).

This is not at all the expected behavior. I've already spent a couple of days trying to find a way to achieve this, without any luck. The closest thing I was able to do is to use the multiple views system from UI-Router, but I need multiple instance of the same view to keep in memory (if multiple tickets are opened, they all are on the same view, with the same controller, but a different scope)

Here's my current implementation: supportApp.js: var app = angular.module("supportApp", ["ui.router", "ui.bootstrap"]);

app.config(function($stateProvider, $urlRouterProvider, $httpProvider){
    $urlRouterProvider.otherwise("/");
    $stateProvider
        .decorator('d', function(state, parent){
            state.templateUrl = generateTemplateUrl(state.self.templateUrl);
            return state;
        })
        .state("main", {
            abtract: true,
            templateUrl: "main.html",
            controller: "mainController"
        })
        .state("main.inbox", {
            url: "/",
            templateUrl: "inbox.html",
            controller: "inboxController"
        })
        .state('main.viewTicket', {
            url: '/ticket/{id:int}',
            templateUrl: "viewTicket.html",
            controller: "ticketController"
        })
    ;
});

mainController.js: (handles other stuff, minimal code here)

app.controller("mainController", function($rootScope, $http, $scope, $state, $interval){
    // Tabs system
    $scope.tabs = [
        { heading: "Tickets", route:"main.inbox", active:false, params:{} }
    ];

    var addTabDefault = {
        heading: '',
        route: null,
        active: false,
        params: null,
        closeable: false
    };

    $rootScope.addTab = function(options){
        if(!options.hasOwnProperty('route') || !options.route)
        {
            throw "Route is required";
        }

        var tabAlreadyAdded = false;
        for(var i in $scope.tabs)
        {
            var tab = $scope.tabs[i];
            if(tab.route == options.route && angular.equals(tab.params, options.params))
            {
                tabAlreadyAdded = true;
                break;
            }
        }
        if(!tabAlreadyAdded)
        {
            $scope.tabs.push($.extend({}, addTabDefault, options));
        }
        if(options.hasOwnProperty('active') && options.active === true)
        {
            $state.go(options.route, options.hasOwnProperty('params')?options.params:null);
        }
    };

    $scope.removeTab = function($event, tab){
        $event.preventDefault();
        if($scope.active(tab.route, tab.params))
        {
            $scope.go($scope.tabs[0].route, $scope.tabs[0].params);
        }
        $scope.tabs.splice($scope.tabs.indexOf(tab), 1);
    };

    $scope.go = function(route, params){
        $state.go(route, params);
    };
    $scope.active = function(route, params){
        return $state.is(route, params);
    };

    $scope.$on("$stateChangeSuccess", function() {
        $scope.tabs.forEach(function(tab) {
            tab.active = $scope.active(tab.route, tab.params);
        });
    });
});

main.html:

<div class="container-fluid" id="sav-container">
    <div class="row-fluid">
        <div class="col-lg-2">
            <form role="form" id="searchForm" action="#">
                <div class="form-group has-feedback">
                    <input class="form-control" type="search" />
                    <span class="glyphicon glyphicon-search form-control-feedback"></span>
                </div>
            </form>
        </div>
        <div class="col-lg-10" id="support_main_menu">
            <ul class="nav nav-tabs">
                <li ng-repeat="t in tabs" ng-click="go(t.route, t.params)" ng-class="{active: t.active, closeable: t.closeable}" style="max-width: calc((100% - 128px) / {{tabs.length}});">
                    <a href class="nav-tab-text">
                        <button ng-show="t.closeable" ng-click="removeTab($event, t)" class="close" type="button">&times;</button>
                        <span>{{t.heading}}</span>
                    </a>
                </li>
            </ul>
        </div>
    </div>
    <div class="row-fluid">
        <div class="tab-content" ui-view></div>
    </div>
</div>

It seems to me that what I ask is pretty standard, but I sadly couldn't find any usefull thing on the Internet

3
  • So basically you want to keep one controller 'alive' while you change the route and instantiate another controller? is that it? Commented Mar 19, 2015 at 14:29
  • That is basically it, yes Commented Mar 19, 2015 at 14:30
  • I believe you can't do that, because the controller will be destroyed once you change the state, it's just how controllers work. I think htat whether you put the content of the scope in a cache and then retrieves it when the controller gets instantiated again, or you create on single controller to all the tabs. Commented Mar 19, 2015 at 14:34

1 Answer 1

1

The basic idea is to store state (i.e. list of tickets) in a service as opposed to a controller. Services hang around for the life of the application. There are some articles on this. I'm still developing my approach but here is an example:

var RefereeRepository = function(resource)
{
  this.resource = resource; // angular-resource

  this.items = []; // cache of items i.e. tickets

  this.findAll = function(reload)
  {
    if (!reload) return this.items;

    return this.items = this.resource.findAll(); // Kicks off actual json request
  };
  this.findx = function(id) 
  { 
    return this.resource.find({ id: id }); // actual json query
  };

  this.find = function(id) // Uses local cache
  {
    var itemx = {};

    // Needs refining
    this.items.every(function(item) {
      if (item.id !== id) return true;
      itemx = item; 
      return false; 
    });
    return itemx;
  };
  this.update = function(item)
  {
    return this.resource.update(item);
  };
};

refereeComponent.factory('refereeRepository', ['$resource',
function($resource)
{
  var resource = 
  $resource('/app_dev.php/referees/:id', { id: '@id' }, {
    update: {method: 'PUT'},
    findAll:  { 
      method: 'GET' , 
      isArray:true,
      transformResponse: function(data)
      {
        var items = angular.fromJson(data);
        var referees = [];
        items.forEach(function(item) {
          var referee = new Referee(item); // Convert json to my object
          referees.push(referee);
        });
        return referees;
      }
    },
    find:  { 
      method: 'GET',
      transformResponse: function(data)
      {
        var item = angular.fromJson(data);
        return new Referee(item);
      }
    }
});
var refereeRepository = new RefereeRepository(resource);

// Load items when service is created
refereeRepository.findAll(true);

return refereeRepository;
}]);

So basically we made a refereeRepository service that queries the web server for a list of referees and then caches the result. The controller would then use the cache.

refereeComponent.controller('RefereeListController', 
  ['$scope', 'refereeRepository',
  function($scope, refereeRepository) 
  {
    $scope.referees = refereeRepository.findAll();
  }
]);
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.