-1

The AngularStrap tabs functionality works great when the site/app is run with the grunt serve command, but when the grunt serve:dist command is used the tabs disable feature does not work.

In particular, the 'disabled' flag works with grunt serve, with the relevant tabs being properly disabled, but with grunt serve:dist the tabs are not disabled.

The following code added to a standard angular-fullstack generated project will reproduce this issue (adding 'mgcrea.ngStrap' to the client dependencies also and installing it via bower) - the final tab is not disabled as it should be when run via grunt serve:dist:

main.controller.js:

'use strict';

angular.module('tabTestApp')
  .component('main', {
    templateUrl: 'app/main/main.html',
    controller: function($scope, $templateCache) {
                    $scope.tabs = [
                      {title:'Select Video', content: 'test 0'},
                      {title:'Edit Video', content: 'test 1'},
                      {title:'Edit Audio', content: 'test 2'},
                      {title:'Play back', content: 'test 3', disabled: 'true'},
                    ];

                    $scope.tabs[1].disabled = 'false';
                    $scope.tabs[2].disabled = "false";
                    $scope.tabs[3].disabled = true;
                    $scope.tabs.activeTab = 0;
                }

  });

main.html:

<!-- Partial for view1
    This partial contains tabs using AngularStrap tab mechanism -->
<div class="bs-docs-section" >
    <!-- bsActivePane is optional - see angular-starp documentation -->
    <div bs-active-pane="tabs.activeTab" bs-tabs>
      <div ng-repeat="tab in tabs" title="{{ tab.title }}" name="{{ tab.title }}" disabled="{{ tab.disabled }}" ng-bind="tab.content" bs-pane>
      </div>
    </div>
</div>

The easiest way to understand the problem may be with some images of the results.

This is what the site looks like when the command 'grunt serve' is used:

enter image description here And this is what it looks like when the command grunt serve:dist is used (see the last tab below which is set to disabled in the code above is not disabled in this case, although it is above):

enter image description here

This is a tricky issue as it might be a Angular-fullstack issue or it might be an AngularStrap issue - my feeling is that it is more likely the former.

If anyone has either seen this issue, or know a good tool/method to debug this type of grunt build issue, any help would be gratefully appreciated!

Mick
  • 24,231
  • 1
  • 54
  • 120

1 Answers1

0

Ok, by process of elmination I found what appears to be causing the problem.

As this was quite tricky I have captuted the approach I took:

  • looked at the final HTML in the browser using the browser developer tools - this showed that the correct version had a 'disabled' class inserted but this didn't really help show how it was being insterted
  • looked at the templates in the client/app/main/main.html in the Angular-Fullstack project directory
  • looked in the dist/client/app/app.4d38fc87.js file (file name is auto generated but it should be the only app*****.js file in this folder). Using a webtools debugger you can 'pretty print' the js file and seacrh for the templates which you would expect to be the same (or at least similar) to the main.html file above.

The main.html file contained these lines:

<!-- Partial for view1
    This partial contains tabs using AngularStrap tab mechanism -->
<div class="bs-docs-section" >
    <!-- bsActivePane is optional - see angular-starp documentation -->
    <div bs-active-pane="tabs.activeTab" bs-tabs>
      <div ng-repeat="tab in tabs" title="{{ tab.title }}" name="{{ tab.title }}" disabled="{{ tab.disabled }}" ng-bind="tab.content" bs-pane>
      </div>
    </div>
</div>

but the app.4d38fc87.js was instering a slightly different template:

a.put("app/main/main.html", '<!-- Partial for view1\n   This partial contains tabs using AngularStrap tab mechanism --><div class=bs-docs-section><!-- bsActivePane is optional - see angular-starp documentation --><div bs-active-pane=tabs.activeTab bs-tabs><div ng-repeat="tab in tabs" title="{{ tab.title }}" name="{{ tab.title }}" disabled ng-bind=tab.content bs-pane></div></div></div>')
  • Looking closely, it is clear that the 'disabled="{{ tab.disabled }}"' part was being modified.

  • From this it seemed likely that something was mistakenly updating the template so the gruntfile seemed a logical place to look.

Looking at the Gruntfile.js in the Angular-Fullstack project to try to see how it was controlling template maniuplation, it contained the following lines:

    // Package all the html partials into a single javascript payload
    ngtemplates: {
      options: {
        // This should be the name of your apps angular module
        module: 'tabTestApp',
        htmlmin: {
          collapseBooleanAttributes: true,
          collapseWhitespace: true,
          removeAttributeQuotes: true,
          removeEmptyAttributes: true,
          removeRedundantAttributes: true,
          removeScriptTypeAttributes: true,
          removeStyleLinkTypeAttributes: true
        },
        usemin: 'app/app.js'
      },
  • By changing the attributes all to false, the problem was resolved.

  • Changing them back to all true and working through them one by one, the first line seemed to be causing the problem - simply changing this to false and leaving everything else true resolved the issue.

  • It seemed clear that this boolean attribute collapse functionality must be tripping over the word 'disabled' for some reason (I changed the spelling to 'dizabled' and it did not get changed in the template, just to check it really was the word itself causing the issue).

  • Digging deeper, Gruntfile is using HTML minifier - looking at its gitHub home there is the following note linked to collapseBooleanAttributes:

Collapse boolean attributes HTML 4.01 has so-called boolean attributes—“selected”, “disabled”, “checked”, etc. These may appear in a minimized (collapsed) form, where attribute value is fully ommited. For example, instead of writing <input disabled="disabled">, we can simply write— <input disabled>.

Minifier has an option to perform this optimization, called collapseBooleanAttributes:

Boolean attributes are strange in that they can only have one value which is their own name and this implies they are true. You can skip the value part (so instead of disabled="disabled", you can just have disabled by itself) and it has the same effect, hence this minification technique (see: http://www.w3.org/TR/html4/intro/sgmltut.html#h-3.3.4.2).

The way to set the disabled attribute in HTML to false is simply not to include it at all (whihc is what AngularStrap does in the final HTML in the browser as noted in the first step above).

So, the original problem can be solved by changing the gruntfile in Angular-fullstack as noted above, but it could also be resolved by AngularStrap simply using a differnt approach to include the attribute. I'll riase it with both projects and let them decide.

I'll also leave this answer unaccepted for now in case anyone has a better or more detailed explanation to share.

Mick
  • 24,231
  • 1
  • 54
  • 120
  • What about if you add `ignoreCustomFragments: [/disabled=".*?"/]` to your htmlmin options? – Andrew Koroluk May 19 '16 at 17:27
  • The ingnoreCustomFragments looks like it would work, but to be honest I think we really want to avoid people having to think about this type of detail when building apps if possible. – Mick May 19 '16 at 21:23