I am working on a web application using angularJs and I need to display gantt charts. I use the jquery-gantt plugin and everything works just fine when only one chart is displayed but if I want to display two or more, they seem to have the same source even if it is not the case.
<chronogramme source="$ctrl.firstSource"></chronogramme>
<chronogramme source="$ctrl.secondSource"></chronogramme>
($ctrl is my alias for my controller, defined with "controllerAs")
For instance, when I update "secondSource", the first chart is updated too and shows the same things as the second one.
Is it even possible to have multiple charts ?
Thanks
UPDATE :
The problem must be in here, as the selector for the jquery gantt affects every single chart, I will look into it (put this here if it can helps others)
"chronogramme" directive :
app.directive("chronogramme", function(){
return {
restrict: 'E',
replace: true,
scope: {
source: '=source',
minScale: '=?minScale',
scale: '=?scale',
onItemClick: '=onItemClick',
onTitleClick: '=onTitleClick'
},
template: '<div class="gantt"></div>',
link: function(scope, element, attrs){
if (!scope.minScale) { scope.minScale = 'months'; }
if (!scope.scale) { scope.scale = 'months'; }
scope.$watch('source', function(){
$(".gantt").gantt({
source: scope.source,
navigate: "scroll",
minScale: scope.minScale,
scale: scope.scale,
itemsPerPage: 20,
waitText: "Chargement...",
months: ["Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet", "Août", "Septembre", "Octobre", "Novembre", "Décembre"],
dow: ["Di", "Lu", "Ma", "Me", "Je", "Ve", "Sa"],
scrollToToday: true,
onItemClick: function(data) {
scope.onItemClick(data);
},
onTitleClick: function(data) {
scope.onTitleClick(data);
}
});
});
}
};
});
The directive is using a class selector to invoke the plugin:
template: '<div class="gantt"></div>',
link: function(scope, element, attrs){
if (!scope.minScale) { scope.minScale = 'months'; }
if (!scope.scale) { scope.scale = 'months'; }
scope.$watch('source', function(){
̶$̶(̶"̶.̶g̶a̶n̶t̶t̶"̶)̶.̶g̶a̶n̶t̶t̶(̶{̶
element.gantt({
Instead of invoking the plugin on all of the elements with the class gantt, invoke it on the element specific to the directive.
Also the directive replace property has been deprecated. It could cause problems if the directive is used with ng-repeat.
Using Angular Gantt clearly solves the issue. You can check the code for gantt directive inside the library it's much complex and you don't need to invest time on creating one in your application using just jquery version of the library. So with using the gantt directive you can have multiple charts on same view. (As of course it's created with isolated scope). Here's example of two charts on same view with different data set: https://plnkr.co/edit/lyBH7ZnZ5PgThqe46YKk?p=preview
Related
I am using angularjs 1.5.0 with angular ui grid 3.1.1.
When I assign gridOptions (passed to the grid directive) object in controller body like this:
$scope.gridOptions = {
data: [{"mock2": 1, "mock1": 2}, {"mock2": 10, "mock1": 22} ]
};
HTML:
<div ui-grid="gridOptions"></div>
It displays the table as expected. But when I try to change data inside $scope.on:
$scope.$on('update', function (event, passedFromBroadcast) {
$scope.gridOptions.data= [{"mock2": "set", "mock1": "inside"}, {"mock2": "$scope", "mock1": "on"} ] ;
});
It renders only frame of the table, when including pagination it will also render pagination related controls - but not the content itself.
Content (the rows and column headers) appear only after I resize my browser window.
Why doesn't angular-ui grid update table content when the data changes inside $scope.on?
How can I manually update the angular ui grid table?
Things I already tried:
$scope.gridApi.core.handleWindowResize(); // Does not work
$scope.gridApi.core.refresh(); // Does not work
$timeout(function(){$scope.gridOptions.data= [{"mock2": "set", "mock1": "inside"}, {"mock2": "$scope", "mock1": "on"} ] ;}) // Does not work
It's a known bug when the gridOptions.data length doesn't change after update, proposed solution is to clear data and with use of $timeout refresh it
$scope.$on('update', function (event, passedFromBroadcast) {
$scope.gridOptions.data = null
$timeout(function(){
$scope.gridOptions.data = [{"mock2": "set", "mock1": "inside"}, {"mock2": "$scope", "mock1": "on"} ] ;
});
});
There are a two issues with this code, but the reason for the table contents showing only after browser window resize is lack of css class defining width and height, basic (working) example:
.grid {
width: 500px;
height: 250px;
}
and in HTML:
<div class="grid" ui-grid="gridOptions"></div>
The Other issue (mentioned by other people in this thread:
assigning gridOption fields (data but also columnDefs) must be done inside $timeout, additionally both data and columnDefs need to be cleared before that. Otherwise it change might not become visible and table contents and headers will remain unchanged (known bug in ui-grid as #maurycy mentioned)
Putting the window resize handler on $interval has worked for me in the past inside the gridOptions.onRegisterApi method:
var gridResizeHandlerInterval;
$scope.gridOptions = {
..., // etc.
onRegisterApi: function(gridApi) {
ctrl.gridApi = gridApi;
gridResizeHandlerInterval = $interval( function() {
$scope.gridApi.core.handleWindowResize();
}, 10, 500);
}
};
And then make sure you tidy up and cancel the interval when your controller gets destroyed:
$scope.$on('$destroy', function() {
$interval.cancel(gridResizeHandlerInterval);
});
Hope this helps you...
You can try the below:
$scope.$on('update', function (event, passedFromBroadcast) {
$scope.gridOptions.data.length = 0;
$timeout(function(){
$scope.gridOptions.data = [{"mock2": "set", "mock1": "inside"}, {"mock2": "$scope", "mock1": "on"} ] ;
});
});
Hope this helps!
I am trying to make a guided tour for the first time user of my site. It works perfectly on localhost, but when I upload it onto my dev server it doesn't work properly i.e. the tooltips are not at the designated place. See the following two screenshots -
I think the issue is because the component is not loaded so far i think so the rendering of the tooltip happens in the middle of the page.
Repository I am using is here
Code - Controller
$scope.IntroOptions = {
steps:[
{
element: '#step1',
intro: "<b>First Step First: </b>Click here to define structure of your test scripts!!<br/>You can modify this later on any time",
position: 'left'
},
{
element: '#step2',
intro: "Click here to select a folder/test script spreadsheet from google drive.",
position: 'bottom'
}]
,
showStepNumbers: false,
exitOnOverlayClick: false,
exitOnEsc: false,
nextLabel: '<strong>NEXT!</strong>',
prevLabel: '<span style="color:green">Previous</span>',
skipLabel: 'Exit',
doneLabel: 'Thanks!'
};
$scope.ShouldAutoStart = false;
$timeout(function(){
$scope.CallMe();
}, 0);
HTML code -
<div ng-controller="tourController" class="container-narrow">
<div ng-intro-options="IntroOptions" ng-intro-method="CallMe"
ng-intro-oncomplete="CompletedEvent" ng-intro-onexit="ExitEvent"
ng-intro-onchange="ChangeEvent" ng-intro-onbeforechange="BeforeChangeEvent"
ng-intro-onafterchange="AfterChangeEvent"
ng-intro-autostart="ShouldAutoStart">
</div>
</div>
**I tried using angular.element(document).ready() also but its not working.
Things are working now with timeout 1000 mili secs but i think there
must be a better way of doing this
I am using gridstack.js (https://github.com/troolee/gridstack.js) in Meteor.
I have the grid
<template name="grid">
<div class="grid-stack" data-gs-width="12" data-gs-animate="yes">
{{#each tiles}}
{{> gridItem}}
{{/each}}
</div>
</template>
and I apply gridstack to my grid element with
Template.grid.onRendered(function() {
$('.grid-stack').gridstack();
});
It works as it should, but if I first go to another route and then back to the route with the grid, the gridstack features are not "active" any longer (but the console doesn't say anything wrong). If I refresh, the gridstack will again work as it should. So the problem occurs only when I come back to this page without refreshing the entire page.
I have tried changing onRendered to onCreated, and suddently the grid gets the exact same behaviour when I refresh, i.e. the gridstack doesn't work neither when refreshing the page nor when I first go to another page and then back to the page with the grid.
So I guess I should still use onRendered, but it seems Meteor doesn't render the same way when I 'browse' between pages. Should I probably deinitialize the library when I leave the page, so it can initialize correctly again when the template is rerendered?
Edit
I have tried
Template.grid.onRendered(function() {
var $el = $('.grid-stack');
// destroy if already applied
if ($el.data('gridstack')) {
$el.data('gridstack').destroy();
}
// apply gridstak to grid element
var grid = $el.gridstack();
});
and also
Template.grid.onDestroyed(function() {
$('.grid-stack').data('gridstack').destroy();
});
but neither works.
But it says
TypeError: $(...).data(...).destroy is not a function
I've faced the same problem while I was implementing Gridstack on AngularJS. The Page change breaks the Gridstack. Here is how I solved the issue in my controller:
.controller('HomeCtrl', ['$scope', 'analyticsService', '$modal', '$timeout', function($scope, analyticsService, $modal, $timeout) {
$scope.homeWidgets = [
{sizeX: 6, sizeY: 5, row: 0, col:0, type: 'ndl-action-items'},
{sizeX: 6, sizeY: 5, row: 0, col:7, type: 'ndl-stage-shift-graph'},
{sizeX: 6, sizeY: 5, row: 1, col:0, type: 'ndl-nodules-in-system'},
{sizeX: 6, sizeY: 5, row: 1, col:7, type: 'ndl-case-volume'}
];
$scope.loadGrid = function() {
if ($scope.homeGrid && $scope.homeGrid.destroy) {
$scope.homeGrid.destroy();
}
var options = {
cell_height: 70,
vertical_margin: 10
};
// console.log("init grid");
$scope.homeGrid = $('.grid-stack').gridstack(options);
// console.log($scope.homeGrid);
};
$scope.$on('$viewContentLoaded', function(event) {
$timeout($scope.loadGrid, 300);
// event.preventDefault();
/* Act on the event */
// console.log('loaded');
// $scope.loadGrid();
});
}]);
So basically when my view reloads, I destroy the grid object if it exists and then recreate it. I had to use timeout so that the DOM gets loaded before the grid is initialized.
I am creating a custom angular directive that will use the Image Tilt Effect plugin.
<tilt></tilt>
the template looks like this.
<li class="grid__item">
<div class="grid__img grid__img--example-1" >
<img src="img/3.jpg" class="tilt-effect" alt="grid01" data-tilt-options='{ "opacity" : 0.3, "extraImgs" : 3, "movement": { "perspective" : 1200, "translateX" : -5, "translateY" : -5, "rotateX" : -5, "rotateY" : -5 } }' />
</div>
The problem I have is this script that I am loading at the bottom of the page doesnt seem to be on when the custom directive is injected into the page.
<script src="js/tiltfx.js"></script>
If I try and move the script into the html fragment for the custom directive I get an Error: $compile:tplrt
I've created a wrapper directive for this the Image Tilt Effect plugin (fiddle).
When you have a DOM plugin you need to use in angular, don't use auto initialization, such as the data-tilt-options of this plugin, because they are hard to predict, and may cause weird behavior, memory leaks, etc... This plugin has a manual init method - new TiltFx(element, options), so we can use that.
Another problem is that this plugin must wait for angular to finish rendering, before it should be initialized. The simple fix is just using setTimeout or $timeout (if we need to update the digest cycle), to put the init at the end of the JS queue. We can also use mutation observers to do that, but that's out of this answer's scope.
One troubling aspect of using a DOM plugin in angular, is memory leaks. Plugins should have some sort of a cleaning mechanism. This plugin doesn't. So, you'll have to check for memory leaks, and if there are stray event handlers remove them, when the wrapped element is removed.
Directive code:
appModule.directive('tilt', function tilt() {
var ddo = {
template: '<div class="grid__img"><img class="tilt-effect" ng-src="{{imgSrc}}" alt="The image" />',
restrict: 'E',
replace: true,
scope: {
imgSrc: '#', // the image src
tiltOptions: '=?' // the tilt options object - optional
},
link: function (scope, $el) {
var img = $el[0].querySelector('img.tilt-effect'); // find the img element
var tilt;
setTimeout(function () { // wait 'till directive is rendered
tilt = new TiltFx(img, scope.tiltOptions); // apply tilt on image with options (if any)
});
$el.on('$destroy', function() {
// use tilt variable to perform cleanup on wrapper and img if needed
tilt = null;
});
}
};
return ddo;
});
Usage:
<div ng-app="tilt">
<tilt img-src="http://cdn.cutestpaw.com/wp-content/uploads/2013/12/Most-Famous-Felines-034.jpg" tilt-options='{ "opacity" : 0.3, "extraImgs" : 3, "movement": { "perspective" : 1500, "translateX" : -5, "translateY" : -5, "rotateX" : -5, "rotateY" : -5 } }'></tilt>
<tilt img-src="http://www.cats.org.uk/uploads/images/pages/photo_latest14.jpg"></tilt>
</div>
Don't forget that this plugin requires the container to have fixed width and height, for example:
.grid__img {
width: 400px;
height: 400px;
}
Change
<script src="js/tiltfx.js"></script>
to
<script ng-src="js/tiltfx.js"></script>
I am using angularjs-gridster (https://github.com/ManifestWebDesign/angular-gridster) with higharts-ng directive (https://github.com/pablojim/highcharts-ng/blob/master/README.md)
I am trying to generate these highcharts inside the grid cells. My problem is that the highcharts are occupying their default width and height (600px * 400px) even when i place my graph drawer function in a $timeout service. Here's the code:
HTML:
<div class="graph-list" gridster="gridsterOpts">
<ul>
<li gridster-item="graph.grid" class="graph-set" ng-repeat="graph in graphs | orderBy: 'number'">
<highchart id="{{'graph' + graph.number}}" class="graph" config="graph.config"></highchart>
</li>
</ul>
</div>
JS:
// inside the graph-list div controller
$scope.gridsterOpts = {
colums: 4,
rowHeight: 240,
margins: [10,10],
outerMargin: false,
draggable: {
enabled: false // whether dragging items is supported
}
};
$scope.graphs = {}; //
$scope.somefunction(){ /* this function populates the graphs object */ };
function drawGraphs(){ /* this function populates the graph.config object by looping through all the graph objects */ }
$timeout(function(){
drawGraphs();
});
I have tried creating watch on the grid-cell width and height but it shows no change. I have not given the highchart width and height explicitly in the graph.config options because I read in the highcharts-ng documentation that it takes the parent width and height by default but its not happening. Can anyone guide me what could be the possible problem.
Seems to me that the angularjs-gridster plugin is not able to set the grid width and height before the highcharts directive is able to render itself. Please help.
I eventually did it. I needed to add the chart.reflow() method (which just resizes the chart instead of redrawing it so better performance wise also, I guess) in the func() options as provided in the highcharts-ng documentation.
graph.config = {
options: { },
series: [],
func: function (chart) {
$timeout(function(){
chart.reflow();
})
}
}
Hope it helps someone else.