Search code examples
angularjstwitter-bootstrapdrop-down-menufaceted-searchangular-bootstrap

Is it possible to change angular bootstrap uib-dropdown templateUrl dynamically?


I want to change the uib-dropdown template dynamically when the user clicks one of its <li>, just like he could "navigate" within that dropdown.

I tried to make it via templateUrl, but nor the ng-templates nor standalone partials can successfully change the dropdown template dynamically, just like this plunkr demonstrates.

My goal is to create a faceted navigation via this dropdown to build query visually, as seen on Fieldbook's Sprint tracker (account required), which is something really like Pure Angular Advanced Searchbox, but I'm having overwhelming issues using this library.

Is this possible to achieve using just AngularJS and angular-bootstrap?


Solution

  • Demo(try the last one):

    http://plnkr.co/edit/1yLmarsQFDzcLd0e8Afu?p=preview

    How to get it to work?

    Based on your plunkr, you should change

    <div class="input-group" uib-dropdown auto-close="disabled">
        <input type="text" class="form-control" placeholder="Click to start a visual query search..." autocomplete="off" uib-dropdown-toggle/>
        <ul class="dropdown-menu" role="menu" ng-if="ctrl.dropdownReady" uib-dropdown-menu template-url="{{ctrl.dropdownTemplateFour}}">
        </ul>
        <span class="input-group-btn">
        <button type="submit" name="search" id="search-btn" class="btn btn-flat"><i class="fa fa-search"></i>
        </button>
        </span>
    </div>
    

    to

    <div class="input-group" uib-dropdown auto-close="disabled"  ng-if="ctrl.dropdownReady">
        <input type="text" class="form-control" placeholder="Click to start a visual query search..." autocomplete="off" uib-dropdown-toggle/>
        <ul class="dropdown-menu" role="menu" uib-dropdown-menu template-url="{{ctrl.dropdownTemplateFour}}">
        </ul>
        <span class="input-group-btn">
        <button type="submit" name="search" id="search-btn" class="btn btn-flat"><i class="fa fa-search"></i>
        </button>
        </span>
    </div>
    

    In which the ng-if="ctrl.dropdownReady" is moved to the div.input-group.

    And change

    vm.dropdownReady = false;
    console.log('vm.dropdownReady =', vm.dropdownReady, ' partial = ', partial);
    switch (template) {
      case 'word':
        partial ? vm.dropdownTemplateFour = 'word-dropdown-dom.template.html' : vm.dropdownTemplateThree = 'word-dropdown-dom.html';
        break;
      case 'main':
        partial ? vm.dropdownTemplateFour = 'main-dropdown-dom.template.html' : vm.dropdownTemplateThree = 'main-dropdown-dom.html';
        break;
    }
    vm.dropdownReady = true;
    

    to

    vm.dropdownReady = false;
    console.log('vm.dropdownReady =', vm.dropdownReady, ' partial = ', partial);
    switch (template) {
      case 'word':
        partial ? vm.dropdownTemplateFour = 'word-dropdown-dom.template.html' : vm.dropdownTemplateThree = 'word-dropdown-dom.html';
        break;
      case 'main':
        partial ? vm.dropdownTemplateFour = 'main-dropdown-dom.template.html' : vm.dropdownTemplateThree = 'main-dropdown-dom.html';
        break;
    }
    $timeout(function(){
      vm.dropdownReady = true;
    });
    

    Which has a $timeout wrap the vm.dropdownReady = true;. And you should inject the $timeout by hand;

    Keep the menu open

    According to the documentation, we can choose the initial state of drop menu with is-open attr. And we can listen the toggle event with on-toggle attr. So if we want to keep the menu open after user clicking the input, we should set the attributes of uib-dropdown like this:

    <div class="input-group" uib-dropdown auto-close="disabled"  ng-if="ctrl.dropdownReady" is-open="ctrl.open" on-toggle="ctrl.toggled(open)">
    

    And in controller:

    vm.toggled = function (open) {
        // the parameter `open` is maintained by *angular-ui/bootstrap*
        vm.open=open;//we don't need to init the `open` attr, since it's undefined at beginning
    }
    

    With these things done, once the menu is open, it doesn't close without user clicking the input again.

    Why?

    Let's check this snippet:

    $templateRequest(self.dropdownMenuTemplateUrl)
        .then(function(tplContent) {
        templateScope = scope.$new();
        $compile(tplContent.trim())(templateScope, function(dropdownElement) {
            var newEl = dropdownElement;
            self.dropdownMenu.replaceWith(newEl);//important
            self.dropdownMenu = newEl;
            $document.on('keydown', uibDropdownService.keybindFilter);
        });
    });
    

    The snippet above shows how does angular-ui/bootstrap use the template-url attr to retrive template and take it into effect. It replaces the original ul element with a newly created element. That's why the uib-dropdown-menu and template-url is missing after clicking the ul. Since they don't exist, you can't change the template-url with angular binding anymore.

    The reason executing vm.dropdownReady = true; immediately after the vm.dropdownReady = false; doesn't work is that angular have no chance to dectect this change and execute the "ng-if" to actually remove the dom. You must make the toggling of vm.dropdownReady asynchronous to give angular a chance to achieve this.