Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

angular directive's content not rendered, unless you force page reflow manually

I experience the following strange behavior in ui-bootstrap and angular 1.4. When I put a footable table directive inside a customized bootstrap panel, called hpanel, the footable initially takes more place than the panel itself:

enter image description here

But if I resize the screen (e.g. by collapsing the Developer Tools panel here), the footable directive draws itself and fits within panel:

enter image description here

Importantly, I've experienced similar problems with angular-c3 charts directives (they load incorrectly, exceeding the size of hpanel, but upon page resize behave fine), so it's probably not just a broken directive.

Have you seen anything similar?

DETAILS:

Below is an HTML template that represents the non-functional part of page. There we have an hpanel and within it a table with angular-footable directive ^1.0.3, applied to it.

Here's the template (toolList.html):

<div class="content">
    <div class="row">
        <div class="col-lg-12">
            <div class="hpanel">
                <div class="panel-heading">
                    <div class="panel-tools">
                        <a class="showhide"><i class="fa fa-chevron-up"></i></a>
                        <a class="closebox"><i class="fa fa-times"></i></a>
                    </div>
                    Available tools.
                </div>
                <div class="panel-body">
                    <input type="text" class="form-control input-sm m-b-md" id="filter" placeholder="Search in table">
                    <table id="example1" class="footable table table-stripped toggle-arrow-tiny" data-page-size="8" data-filter=#filter>
                        <thead>
                        <tr>
                            <th data-toggle="true">Id</th>
                            <th>Class</th>
                            <th>Label</th>
                            <th>Description</th>
                            <th data-hide="all">Owner</th>
                            <th data-hide="all">Contributor</th>
                            <th data-hide="all">Inputs</th>
                            <th data-hide="all">Outputs</th>
                            <th data-hide="all">Base command</th>
                            <th data-hide="all">Arguments</th>
                            <th data-hide="all">Requirements</th>
                            <th data-hide="all">Hints</th>
                        </tr>
                        </thead>
                        <tbody>
                        <tr ng-repeat="tool in vm.tools">
                            <td><a ui-sref="tool-detail({id: tool.id})">{{tool.id}}</a></td>
                            <td>{{tool.tool_class}}</td>
                            <td>{{tool.label}}</td>
                            <td>{{tool.description}}</td>
                            <td>{{tool.owner}}</td>
                            <td>{{tool.contributor}}</td>
                            <td>{{tool.baseCommand}}</td>
                            <td>{{tool.arguments}}</td>
                            <td>{{tool.requirements}}</td>
                            <td>{{tool.hints}}</td>
                        </tr>
                        </tbody>
                        <tfoot>
                        <tr>
                            <td colspan="5">
                                <ul class="pagination pull-right"></ul>
                            </td>
                        </tr>
                        </tfoot>
                    </table>

                </div>
            </div>
        </div>
    </div>
</div>

The footable directive is meant to hide some columns of the table and show them upon click on a table row. It also provides pagination. It doesn't seem to work upon page load, but when I resize the page and the size of screen crosses the media-type margin (so that from medium-size screen it becomes large screen in bootstrap css terms), pagination buttons appear and columns that are meant to be hidden are hidden.

Here's how I import the footable directive in my main module app.js:

require("footable/js/footable");
require("footable/js/footable.filter");
require("footable/js/footable.striping");
require("footable/js/footable.sort");
require("footable/js/footable.paginate");
require("footable/css/footable.core.css")
require("angular-footable");

angular.module("app", [
    ...,
    "ui.footable",
])

I use webpack to load all those modules and bower to install the dependencies.

hpanel is just a scss class, here is its definition:

/* Panels */
.hpanel > .panel-heading {
  color: inherit;
  font-weight: 600;
  padding: 10px 4px;
  transition: all .3s;
  border: 1px solid transparent;
}

.hpanel .hbuilt.panel-heading {
  border-bottom: none;
}

.hpanel > .panel-footer, .hpanel > .panel-section {
  color: inherit;
  border: 1px solid $border-color;
  border-top: none;
  font-size: 90%;
  background: $color-bright;
  padding: 10px 15px;
}

.hpanel.panel-collapse > .panel-heading, .hpanel .hbuilt {
  background: #fff;
  border-color: $border-color;
  border: 1px solid $border-color;
  padding: 10px 10px;
  border-radius: 2px;
}

.hpanel .panel-body {
  background: #fff;
  border: 1px solid $border-color;
  border-radius: 2px;
  padding: 20px;
  position: relative;
}


.hpanel.panel-group .panel-body:first-child {
  border-top: 1px solid $border-color;
}

.hpanel.panel-group .panel-body {
  border-top: none;
}

.panel-collapse .panel-body {
  border: none;
}

.hpanel {
  background-color: none;
  border: none;
  box-shadow: none;
  margin-bottom: 25px;
}

.panel-tools {
  display: inline-block;
  float: right;
  margin-top: 0;
  padding: 0;
  position: relative;
}

.hpanel .alert {
  margin-bottom: 0;
  border-radius: 0;
  border: 1px solid $border-color;
  border-bottom: none;
}

.panel-tools a {
  margin-left: 5px;
  color: lighten($color-text, 20%);
  cursor: pointer;
}

.hpanel.hgreen .panel-body {
  border-top: 2px solid $color-green;
}

.hpanel.hblue .panel-body {
  border-top: 2px solid $color-blue;
}

.hpanel.hyellow .panel-body {
  border-top: 2px solid $color-yellow;
}

.hpanel.hviolet .panel-body {
  border-top: 2px solid $color-violet;
}

.hpanel.horange .panel-body {
  border-top: 2px solid $color-orange;
}

.hpanel.hred .panel-body {
  border-top: 2px solid $color-red;
}

.hpanel.hreddeep .panel-body {
  border-top: 2px solid $color-red-deep;
}

.hpanel.hnavyblue .panel-body {
  border-top: 2px solid $color-navy-blue;
}

.hpanel.hbggreen .panel-body {
  background: $color-green;
  color: #fff;
  border:none;
}

.hpanel.hbgblue .panel-body {
  background: $color-blue;
  color: #fff;
  border:none;
}

.hpanel.hbgyellow .panel-body {
  background: $color-yellow;
  color: #fff;
  border:none;
}

.hpanel.hbgviolet .panel-body {
  background: $color-violet;
  color: #fff;
  border:none;
}

.hpanel.hbgorange .panel-body {
  background: $color-orange;
  color: #fff;
  border:none;
}

.hpanel.hbgred .panel-body {
  background: $color-red;
  color: #fff;
  border:none;
}

.hpanel.hbgreddeep .panel-body {
  background: $color-red-deep;
  color: #fff;
  border:none;
}

.hpanel.hbgnavyblue .panel-body {
  background: $color-navy-blue;
  color: #fff;
  border:none;
}

.panel-group .panel-heading {
  background-color: $color-bright;
}

.small-header .hpanel {
  margin-bottom: 0;
}

.small-header {
  padding: 0 !important;
}

.small-header .panel-body {
  padding: 15px 25px;
  border-right: none;
  border-left: none;
  border-top: none;
  border-radius: 0;
  //  background: $color-bright;
}

.panel-body h5, .panel-body h4 {
  font-weight: 600;
}

.small-header .panel-body h2 {
  font-size: 14px;
  font-weight: 600;
  text-transform: uppercase;
  margin: 0 0 0 0;
}

.small-header .panel-body small {
  color: lighten($color-text, 10%);
}

.hbreadcrumb {
  padding: 2px 0px;
  margin-top: 6px;
  margin-bottom: 0px;
  list-style: none;
  background-color: #fff;
  font-size: 11px;

  > li {
    display: inline-block;

    + li:before {
      padding: 0 5px;
      color: $color-navy-blue;
    }
  }

  > .active {
    color: lighten($color-text,20%);
  }
}

.wrapper {
  padding: 10px 20px;
}

.hpanel.collapsed .panel-body, .hpanel.collapsed .panel-footer {
  display: none;
}

.hpanel.collapsed .fa.fa-chevron-up:before {
  content: "\f078";
}
.hpanel.collapsed .fa.fa-chevron-down:before {
  content: "\f077";
}

.hpanel.collapsed.panel-collapse .panel-body {
  border-width: 0 1px 1px 1px;
  border-color: $border-color;
  border-style: solid;
}

.hpanel.collapsed .hbuilt.panel-heading {
  border-bottom: 1px solid $border-color;
}

body.fullscreen-panel-mode {
  overflow-y: hidden;
}

.hpanel.fullscreen {
  z-index: 2030;
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  overflow: auto;
  margin-bottom: 0;
}

.hpanel.fullscreen .showhide {
  display: none;
}

.hpanel.fullscreen .panel-body {
  min-height: calc(100% - 77px);
}

Here's tool.module.js file, which animates the template:

import angular from "angular";

var ToolResource = require("workflow/tool/tool.service");

class ToolListController {
    // @ngInject
    constructor($location, $stateParams, $state, tools) {
        this.$location = $location;
        this.$state = $state;
        this.$stateParams = $stateParams;

        this.tools = tools;
    }
}

// @ngInject
function routesList($stateProvider) {
    $stateProvider.state("tool-list", {
        url: "/tool",
        parent: "layout",
        templateUrl: "/app/workflow/tool/toolList.html",
        controller: "ToolListController",
        controllerAs: "vm",
        data: {
            pageTitle: "Tool",
            pareDesc: "List of tools, available for workflow construction.",
        },
        resolve: {
            ToolResource: "ToolResource",
            tools: function(ToolResource) {
                return ToolResource.query().$promise;
            }
        }
    });
}

module.exports = angular.module("tool", [])
  .service('ToolResource', ToolResource)
  .controller('ToolListController', ToolListController)
  .config(routesList);

tool.service.js:

module.exports = function ToolResource($resource) {
    return $resource('/api/tool/:id', {id: '@id'});
}

ANSWER: Community is awesome!

  • 1.5 years ago this directive was created
  • 12 days ago this bug was fixed by Alexryan in his fork
  • 10 days ago I posted this question on StackOverflow
  • 8 days ago I placed a bounty on this question
  • 7 days ago ziscloud approved pull request
  • in the morning today the bounty expired and in the nick of time Walfrat found out that the bug was fixed

So, yes, it was a bug in the directive that made it draw itself before getting the data from server. With the bugfix I just added load-when="vm.tools" attribute to the directive and it works fine now.

Thank you, Alexryan, ziscloud, Walfrat and other commenters/answerers. StackOverflow and Github just made my day!

like image 594
Boris Burkov Avatar asked May 07 '16 17:05

Boris Burkov


2 Answers

Are you using this directive ? https://github.com/ziscloud/angular-footable/blob/master/src/angular-footable.js. It's an homemade (meaning not done by the editor of the footable) directive so it can be not rightly implemented to works with Angularjs.

Looking at the code it seems that you have to use an attribute load-when if you want to delay the initialization of the grid even though you use the resolve attribute in your state, it can be worth to test it.load-when shall be an empty array at start an will trigger the load after the array won't be empty anymore, but the data binded won't be used for the initialization from what i saw.

Note : i wasn't able to set a proper plnkr myself, i don't know the version you're using (and with which jQuery version) and online links doesn't seems available.

like image 148
Walfrat Avatar answered Nov 12 '22 13:11

Walfrat


Since you are asynchronously loading data (as was mentioned in the comments) your html is rendered prior to it having any data in it. This means the directive may be fired too early (if it is attempting to adapt based on data). Typically, in this scenario, you'll want to throw an ng-if on the portion of your html that is dependent on the data loading (and show a loading gif or something in its place). You can either run the ng-if off of the data itself being defined, or maintain a separate boolean that you set once the promise is resolved.

like image 1
sourdoughdetzel Avatar answered Nov 12 '22 14:11

sourdoughdetzel