ChartJS not rendering when div hidden - angularjs

I have an angular application with tabs. Those tabs show and hide the div content using the ng-show directive. Each div contains inside some canvas showing ChartJS charts.
When i swipe from tab to another, i find that my charts are not rendered until y relaod the page or make another request to my function in order to reload the charts querying my server, eventhough all the data is already loaded by a $promise.
Here, my charts are correctly rendered...
But when i change my tab without forcing a reload (the data is already loaded), this is what happens...
This is my HTML
<section style="overflow-y:hidden" class="bg-white scroller-v" ng-init="AskForToggle()">
<div class="tab-nav clearfix">
<ul class="side-padding-3x">
<li data-ng-class="activeSubMenu('Actividades')">
<a class="size-12x" data-ng-click="Show('Actividades')">Actividades</a>
</li>
<li data-ng-class="activeSubMenu('Paginas')">
<a class="size-12x" data-ng-click="Show('Paginas')">Compromiso</a>
</li>
<li data-ng-class="activeSubMenu('Videos')">
<a class="size-12x" data-ng-click="Show('Videos')">Videos</a>
</li>
</ul>
</div>
<div class="col col-unpadded col-lg-2 col-md-3 col-sm-12 col-xs-12 side-menu">
<header>Fechas</header>
<ul class="list">
<li><input type="date" ng-model="analyticsDateDesde" ng-change="ValidateDateDesde(analyticsDateDesde)" /></li>
<li><input type="date" ng-model="analyticsDateHasta" ng-change="ValidateDateHasta(analyticsDateHasta)" /></li>
<li><input type="button" ng-click="UpdateAnalytics()" value="Actualizar Fechas" style="color:white; background-color:#3c8dbc;border:#3c8dbc" /></li>
</ul>
<header>Alumnos</header>
<ul class="list">
<li data-ng-repeat="user in users">
<a data-ng-class="activeNav(user)" data-ng-click="$parent.selectedUser = user">{{user.name}} {{user.lastName}}</a>
</li>
</ul>
</div>
<div id="divActividades" class="col col-unpadded col-lg-10 col-md-9 col-sm-12 col-xs-12" style="overflow-y: scroll; overflow-x:hidden;height: 100%;">
Some charts
</div>
<div id="divPaginas" class="col col-unpadded col-lg-10 col-md-9 col-sm-12 col-xs-12" style="overflow-y: scroll; overflow-x:hidden;height: 100%;">
Some charts
</div>
<div id="divVideos" class="col col-unpadded col-lg-10 col-md-9 col-sm-12 col-xs-12" style="overflow-y: scroll; overflow-x:hidden;height: 100%;">
Some charts
</div>
And this my angular controller
appFlipped.controller("ClassTraceability", ["$rootScope", "$scope", "Courseware", "$timeout", "$window", function (n, t, i, to, window) {
t.GetTypeClass = function (traza) {
if (traza.tipoAccion == 'Video')
return 'cd-timeline-img cd-movie';
else if (traza.tipoAccion == 'Problem')
return 'cd-timeline-img cd-picture';
else
return 'cd-timeline-img cd-location';
}
n.menuData = utils.courseMenu();
t.activeNav = function (n) {
return {
active: t.selectedUser && t.selectedUser.id == n.id
}
}
t.users = [];
t.selectedUser = null;
t.timeOnPlatform = null;
t.timeOnPlatformPerDayComparison = null;
t.timeOnPlatformPerDay = null;
i.init(n.routeData.classId).then(function () {
i.users.queryClass().$promise.then(function (n) {
t.users = n;
n.length && (t.selectedUser = n[0]);
})
});
t.$watch("selectedUser", function (n) {
n != null && t.loadTraceability();
});
t.UpdateAnalytics = function () {
t.loadTraceability();
}
t.loadTraceability = function () {
if (t.selectedUser != null) {
var p = {
userId: t.selectedUser.id,
courseURL: n.rutaParaAnalytics,
fechaDesde: t.analyticsDateDesde,
fechaHasta: t.analyticsDateHasta
};
i.traceability.get(p).$promise.then(function (n) {
t.Traceability = n.traceability;
t.showTimeline = t.Traceability.length > 0 ? true : false;
t.labelsTimeOnPlatform = n.timeOnPlatform[0];
t.dataTimeOnPlatform = n.timeOnPlatform[1];
t.loadPageActivity();
t.labelsTimeOnPlatformPerDay = n.timeOnPlatformPerDay[0];
t.dataTimeOnPlatformPerDay = n.timeOnPlatformPerDay[1];
t.loadPageActivityPerDay();
t.labelsTimeOnPlatformPerDayComparison = (n.timeOnPlatformPerDayComparison[0])[0];
t.dataTimeOnPlatformPerDayComparisonClass = (n.timeOnPlatformPerDayComparison[0])[1];
t.dataTimeOnPlatformPerDayComparisonStudent = (n.timeOnPlatformPerDayComparison[1])[1];
t.loadPageActivityComparison();
t.totalVideoCount = n.genericVideoAnalytics[0];
t.userTotalVideoCount = n.genericVideoAnalytics[1];
t.totalMinutesVideosCount = n.genericVideoAnalytics[2];
t.usertotalMinutesVideosCount = n.genericVideoAnalytics[3];
t.loadGenericVideoAnalytics();
t.videoTimeLabels = n.videoTime[0];
t.videoTimeData = n.videoTime[1];
t.loadVideoTime();
t.videoTimePerDayLabels = n.videoTimePerDay[0];
t.videoTimePerDayData = n.videoTimePerDay[1];
t.loadVideoTimePerDay();
t.videoTimePerDayComparisonLabels = (n.videoTimePerDayComparison[0])[0];
t.videoTimePerDayComparisonClass = (n.videoTimePerDayComparison[0])[1];
t.videoTimePerDayComparisonUser = (n.videoTimePerDayComparison[1])[1];
t.loadVideoTimePerDayComparison();
});
}
};
t.PaginaVisible = 'Actividades';
t.activeSubMenu = function (pagina) {
if (t.PaginaVisible == pagina)
return 'active';
else
return '';
}
t.Show = function (pagina) {
if (pagina == 'Actividades') {
angular.element("#divActividades")[0].style.display = 'block';
angular.element("#divPaginas")[0].style.display = 'none';
angular.element("#divVideos")[0].style.display = 'none';
t.PaginaVisible = 'Actividades';
}
if (pagina == 'Paginas') {
angular.element("#divActividades")[0].style.display = 'none';
angular.element("#divPaginas")[0].style.display = 'block';
angular.element("#divVideos")[0].style.display = 'none';
t.PaginaVisible = 'Paginas';
}
if (pagina == 'Videos') {
angular.element("#divActividades")[0].style.display = 'none';
angular.element("#divPaginas")[0].style.display = 'none';
angular.element("#divVideos")[0].style.display = 'block';
t.PaginaVisible = 'Videos';
}
}
}]);
The most wired thing is that one of all the charts is rendered!! Have anyone face the same problem?
Regards

A similar issue had been reported here :
https://github.com/jtblin/angular-chart.js/issues/29
If the chart is contained in a div that is hidden and then appear on screen
it won't render correctly (encountering the same issue right now!). Which is the case for your charts in the tabs hidden tabs with ng-show directives. A workaround would be to redraw the chart on changing tab.
This should be achievable via the myChart.update() method.
And it could be triggered in your Show method for instance.
I will try different solutions for my part and will update this answer accordingly. Hope it could help a little!

Related

two way binding not working even with dot notation

I'm starting with AngularJS and I am using a controller variable to navigate an array of questions, and it is working when using nextQuestion function, index gets updated and the next question is shown in the view, but if I try to obtain the same value (index) in a different function, it always returns 0.
I have seen on other questions that you should use an object to contain the variable to not manipulate primitive types directly in the controller, but it still does not work.
My controller:
myApp.controller('SurveyController',['$scope','$http', '$location','$routeParams','surveyMetrics','DataService',function($scope,$http, $location,$routeParams,surveyMetrics,DataService){
console.log('LOADED QUIZ CONTROLLER');
var vm = this;
vm.scope = {
index: 0
};
vm.surveyMetrics = surveyMetrics;
vm.surveyQuestions = DataService.surveyQuestions;
vm.DataService = DataService;
/*
vm.getQuestions = function(){
$http.get('/api/questions').then(function(response){
$scope.questions = response.data;
});
}
*/
/*
vm.activateSurvey = function(){
surveyMetrics.changeState(true);
}
*/
vm.getCurrentIndex = function(){
return vm.scope.index;
}
vm.nextQuestion = function () {
console.log('NEXT QUESTION!');
console.log('NUMBER OF QUESTIONS: '+ vm.surveyQuestions.length);
var currentIndex = vm.getCurrentIndex();
var newIndex = currentIndex+1;
scope = {};
if (currentIndex == vm.surveyQuestions.length) {
newIndex = vm.surveyQuestions.length -1;
}
vm.scope.index = newIndex;
console.log('Inside Object: '+vm.scope)
console.log('vm.index'+vm.scope.index);
console.log('vm.indexFunction'+vm.getCurrentIndex());
}
/*
vm.previousQuestion = function () {
console.log('PREVIOUS QUESTION!');
console.log('NUMBER OF QUESTIONS: '+ vm.surveyQuestions.length);
if (vm.scope.index == 0) {
vm.scope.index = 0;
}else{
vm.scope.index--;
}
}
*/
vm.activeSurveyQuestion = function(questionId,index){
console.log('question id and index',questionId,index);
if (questionId == index) {
var navBtn = document.getElementById('navBtn_'+index);
navBtn.classList.add('active');
}
}
vm.navigateSurvey = function () {
var answerPane = document.getElementById('answer-pane');
document.onkeydown = function (e) {
console.log('INSIDE KEYDOWN: ')
e.preventDefault();
var pressedKey = e.keyCode;
console.log('PRESSED KEY IN SURVEY: ' + pressedKey);
if (pressedKey === rightArrow) {
console.log('survey - right arrow pressed');
document.getElementById('nextQuestionBtn').click();
console.log('FUCKING INDEX FML!: '+vm.getCurrentIndex()+' | '+vm.scope.index);
var questionType = DataService.getQuestionType(vm.scope.index);
console.log('Survey Controller: question type: '+questionType);
}
if (pressedKey === leftArrow) {
console.log('survey - left arrow pressed');
document.getElementById('previousQuestionBtn').click();
}
(...)
My View:
<!--Satisfaction Survey-->
<div ng-controller="SurveyController as survey" ng-init="survey.getSurvey();">
<!--
<p ng-repeat="question in survey.surveyQuestions" ng-show ="survey.surveyMetrics.surveyActive">
{{question.question}}
</p>
-->
<!--Survey Modal -->
<div class="modal fade" id="surveyModal" role="dialog">
<div class="modal-dialog">
<!-- Modal content-->
<div class="modal-content">
<div class="text-center"> Customer Satisfaction Survey</div>
<div class="modal-header">
<h4 class="modal-title">{{survey.surveyQuestions[survey.getCurrentIndex()].question}}</h4>
</div>
<div class="modal-body survey" id="answer-pane">
<div class="row">
<div class="col-sm-2 survey-left-arrow" ng-click="survey.previousQuestion();" id="previousQuestionBtn">
<p>‹</p>
</div>
<div class="col-sm-8">
<!-- <p ng-repeat="answer in survey.surveyQuestions[survey.index].answers">{{answer}}</p> -->
<p ng-repeat="answer in survey.surveyQuestions[survey.getCurrentIndex()].answers">
<button type="button" class="btn" id="answerId_{{survey.getCurrentIndex()}}"
ng-class="{'survey-check-box': (survey.surveyQuestions[survey.getCurrentIndex()].type !== 'SingleChoice'),
'survey-btn_{{($index+1)}}': (survey.surveyQuestions[survey.getCurrentIndex()].type === 'SingleChoice')}">
<input type="checkbox" ng-if="survey.surveyQuestions[survey.getCurrentIndex()].type !== 'SingleChoice'"> {{answer}}
</button>
</p>
</div>
<div class="col-sm-2 survey-right-arrow " ng-click="survey.nextQuestion();" id="nextQuestionBtn">
<p>›</p>
</div>
</div>
</div>
<div class="text-center">
<strong> <p>Question: {{survey.surveyQuestions[survey.scope.index].questionNum}} of {{survey.surveyQuestions.length}}</p> </strong>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
<!-- <nav aria-label="Survey navigation">
<ul class="pagination pagination-sm justify-content-center">
<div ng-repeat="question in survey.surveyQuestions" >
<li class="page-item">
<a class="page-link" id = "navBtn_$index" ng-click="survey.index = $index">{{question.id}}</a>
</li>
</div>
</ul>
</nav> -->
</div>
</div>
I would like for the controller to change the variable and the view to update accordingly
Thank you for your time and thank you in advance.
It's 'survey.scope.index' not 'survey.index' in your HTML. I think you may be unclear the difference between using 'this' and '$scope'. You're mixing the two together which is not necessary. I would suggest removing 'scope' and just reference it in your HTML as 'survey.index'.

How to Expand a Default Angular UI Tree Node

I have the following markup:
<div class="modal fade" id="locationSearchModal" tabindex="-1" role="dialog">
<div class="modal-dialog narrow-modal" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
<h4 class="modal-title" id="myModalLabel">Search for Locations</h4>
</div>
<div class="modal-body">
<div class="tree-container">
<section>
<h2>Browse for Locations</h2>
<div ui-tree="" data-drag-enabled="false" id="tree-root">
<ul ui-tree-nodes="" ng-model="data">
<li ng-repeat="item in data" ui-tree-node="" collapsed="!item.ItemsRetrieved" ng-include="item.Place || item.$hashkey == undefined ? 'parent_items_renderer' : 'terminal_item_renderer' " ></li>
</ul>
</div>
<script type="text/ng-template" id="parent_items_renderer">
<div ui-tree-handle class="tree-node tree-node-content" ng-class="{'tree-node-open': !collapsed}" ng-click="toggle(item); convertObjs(item)">
<i class="fa fa-caret-right" ng-class="{'fa-caret-right': collapsed, 'fa-caret-down': !collapsed}"></i>
<i class="fa fa-map-marker" ng-class="{'text-blue': !collapsed}"></i>
<span class="" ng-bind-html="item.PlaceName"></span>
</div>
<ul ng-if="item.Place != null" ui-tree-nodes ng-model="item.Place" ng-class="{hidden: collapsed}">
<li ng-repeat="item in item.Place" ui-tree-node collapsed="!item.ItemsRetrieved" ng-include="item.Place ? 'parent_items_renderer' : 'terminal_item_renderer' " on-finish-render="ngRepeatFinished"> </li>
</ul>
</script>
<script type="text/ng-template" id="terminal_item_renderer">
<div ui-tree-handle class="tree-node tree-node-content" ng-class="{'tree-node-open': !collapsed}" ng-click="addLocation(item)">
<a href title="Add Location"><span class="" ng-bind-html="item.PlaceName"></span></a>
</div>
</script>
</section>
</div>
</div>
</div>
</div>
</div>
The JSON data object that contains the location data is an hierarchical collection of Places:
{
"PlaceHierarchy":{
"Places":{
"Place":{
"PlaceID":"1000",
"PlaceTypeID":"5",
"PlaceName":"Company",
"AbbrName":"Company",
"Place":[
{
"PlaceID":"2000",
"PlaceTypeID":"4",
"PlaceName":"Region",
"AbbrName":"ThePlace",
"Place":[
{
"PlaceID":"3000",
"PlaceTypeID":"3",
"PlaceName":"SubRegion",
"AbbrName":"TheSubPlace",
"Place":[
{
"PlaceID":"4000",
"PlaceTypeID":"2",
"PlaceName":"SubSubRegion",
"AbbrName":"TheSubSubPlace",
"Place":[
{
"PlaceID":"5000",
"PlaceTypeID":"1",
"PlaceName":"Building",
"AbbrName":"Building",
"Place":[
{
"PlaceID":"5001",
"PlaceTypeID":"6",
"PlaceName":"Lobby",
"AbbrName":"Lobby"
},
{
"PlaceID":"5002",
"PlaceTypeID":"6",
"PlaceName":"Lobby 2",
"AbbrName":"Lobby2"
}
]
}
]
}
]
}
]
}
]
}
}
}
}
When I get that JSON back from the API, I need to process the data to make sure that all nodes are arrays. I do it like this:
$scope.processLocationNodes = function (nodes) {
for (var node in nodes) {
if (angular.isArray(node)) {
$scope.processLocationNodes(node);
} else {
$scope.convertObjs(node);
};
}
};
$scope.convertObjs = function (item) {
angular.forEach(item.Place, function (items) {
if (items != undefined && !angular.isString(items)) {
if (items.Place && !angular.isArray(items.Place)) {
var PlaceObj = items.Place;
items.Place = [];
items.Place.push(PlaceObj);
}
}
});
};
Now, when the modal is shown, the data properly displays and the tree works as expected. The only problem is, I want to default the tree to expand to the node of the Default Place of the user. I do that by the following logic:
The onFinishRender directive (debugging the code shows that this is hit):
app.directive('onFinishRender', function ($timeout) {
return {
link: function (scope, element, attr) {
if (scope.$last === true) {
$timeout(function () {
scope.$emit(attr.onFinishRender);
});
}
}
}
});
The ngRepeatFinished function is as follows:
$scope.$on('ngRepeatFinished', function (ngRepeatFinishedEvent) {
var rootScope = $scope.getRootNodesScope();
if (rootScope != undefined) {
rootScope.collapseAll();
$scope.expandNode($scope.defaultPlace);
}
});
$scope.getRootNodesScope = function() {
return angular.element(document.getElementById("tree-root")).scope().$nodesScope.childNodes()[0];
}
$scope.expandNode = function(nodeId) {
// We need to get the whole path to the node to open all the nodes on the path
var parentScopes = $scope.getScopePath(nodeId);
for (var i = 0; i < parentScopes.length; i++) {
parentScopes[i].expand();
}
};
$scope.getScopePath = function (nodeId) {
return $scope.getScopePathIter(nodeId, $scope.getRootNodesScope(), []);
};
$scope.getScopePathIter = function(nodeId, scope, parentScopeList) {
if (!scope) return null;
var newParentScopeList = parentScopeList.slice();
newParentScopeList.push(scope);
if (scope.$modelValue && scope.$modelValue.id === nodeId) return newParentScopeList;
var foundScopesPath = null;
var childNodes = scope.childNodes();
for (var i = 0; foundScopesPath === null && i < childNodes.length; i++) {
foundScopesPath = $scope.getScopePathIter(nodeId, childNodes[i], newParentScopeList);
}
return foundScopesPath;
};
Now, here's what my problem is:
First of all at the angular.element(document.getElementById("tree-root")).scope().$nodesScope.childNodes()[0] code, "childNodes()" is empty. No childNodes exist at all. Thus, the code has nothing to collapse or expand. I don't know why the childNodes collection is empty.
Second, once I figure that out, I can see what the actual NodeId is for a specific node and then be able to expand the tree to that node using the $scope.defaultPlace object.
Essentially, I just need to know why the childNodes collection is empty.

How to select first element in every page in pagination like $event click in angular?

I have created a pagination example dynamically in angular. Now ng-click event works fine with ng-repeat. when I click on any row element I styled it dynamically using ng-style(with red border). After that if I click on collapse button the selected row collapses. Now I want this selection to inherit to every first row element with border red without clicking it by default on pageload. just by directly clicking on collapse button, the first row should collapse just like how ng-click is working for every row. when I click on prev, next buttons in both gold and silver links how can I make this work only for every first element selected with red border & be also able to work with collapse button to it. do I have to use data-ng-init="";? Any help would be grateful. I have my plunkr link demo below including json file
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<style>
body{background:#fff;height:100%}h2{text-transform:capitalize;font-size:14px;color:#636363;font-weight:700}.small{width:100%;text-align:left;float:left;background:#efefef;line-height:50px;border-bottom:1px solid #bfbfbf}#btns{float:right;z-index:0;position:relative}#gold,#hide,#leftBtn,#rightBtn,#silver{width:auto;min-width:48px;height:48px;border:none;outline:0;float:left;border-radius:7px;margin:10px 10px 10px 0}#hide{margin:10px 0}#rightBtn{float:right}#gold,#silver{background:pink}.link{color:#2196F3;cursor:pointer}button[disabled],html input[disabled]{cursor:default;opacity:.4}div.caption{font-family:Oswald,sans-serif;color:#000;text-transform:uppercase;font-size:15px;margin:0;float:left}
</style>
<div ng-app="myApp" ng-controller="tabCtrl">
<div class="col-md-12" id="main">
<div class="row">
<button id="gold" class="active" ng-click="gold()">gold</button>
<button id="silver" class="" ng-click="silver()">silver</button>
<button id="hide" ng-click="hide()" class="pull-right"> collapse </button>
<div class="small" ng-repeat="x in myData | startFrom:(currentPage)*pageSize | limitTo:pageSize" ng-click="setActive(x, $event)" ng-style="activeMenu === x && divStyle">
<h2 class="col-md-4 pull-left"> {{ x.caption }} </h2>
<div class="col-md-4 pull-left"> {{ x.description }} </div>
<div class="col-md-4 link text-right">view site</div>
</div>
</div>
<!--previous, next buttons-->
<div id="btns">
<button id="rightBtn" ng-disabled="currentPage >= myData.length/pageSize - 1" ng-click="currentPage=currentPage+1;">next</button>
<button id="leftBtn" ng-disabled="currentPage == 0" ng-click="currentPage=currentPage-1">prev</button>
</div>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
<script>
var app = angular.module('myApp', []);
app.controller('tabCtrl', function ($scope, $http, $timeout) {
$scope.currentPage = 0;
$scope.pageSize = 5;
$scope.myData = [];
for (var i = 0; i < $scope.myData; i++) {
$scope.myData.push(i);
}
$scope.numberOfPages = function () {
return Math.ceil($scope.myData.length / $scope.pageSize);
}
var x, y;
$http.get("category.json").then(function (response) {
$scope.myData = response.data.gold;
$scope.gold = function () {
$scope.currentPage = 0;
$scope.myData = response.data.gold;
}
$scope.silver = function () {
$scope.currentPage = 0;
$scope.myData = response.data.silver;
}
$scope.setActive = function (list, $event) {
$scope.activeMenu = list;
y = '';
y = angular.element($event.target).prop('clientHeight');
$scope.divStyle = {
"border": '2px solid red',
"pointer-events": "none"
}
}
$scope.hide = function () {
$scope.divStyle = {
"height": y * 2 + 'px',
"border": '2px solid green',
"pointer-events": "none"
}
}
});
});
app.filter('startFrom', function () {
return function (input, start) {
/*if (!angular.isArray(input)) { return []; }*/
if (!input || !input.length) {
return;
}
start = +start; //parse to int
return input.slice(start);
};
});
</script>
PLUNKR
I think that this should be your solution, I think I have understood your requirements, please go through and let me know if you have any issues with the code.
Plunkr Demo
I am using the condition $index === 0, but I am also adding another condition where if there is any selection, the first row will get disabled. This gets reset when you press the buttons gold and silver
There was another issue in your code, you had declared the style object inside the $scope.setActive function, I have moved it out, only then the style will get applied by default.
Apart from this, this is how I implemented the functionality!
<div class="small" ng-repeat="x in myData | startFrom:(currentPage)*pageSize | limitTo:pageSize" ng-click="setActive(x, $event)"
ng-style="$index === 0 && testBool ? divStyle : activeMenu === x ? divStyle: ''">
<h2 class="col-md-4 pull-left"> {{ x.caption }} </h2>
<div class="col-md-4 pull-left"> {{ x.description }} </div>
<div class="col-md-4 link text-right">view site</div>
</div>
This testBool scope variable is intially set to true. Then when any of the elements is clicked its set to false using the function.
$scope.setActive = function (list, $event) {
$scope.testBool = false;
$scope.activeMenu = list;
y = '';
y = angular.element($event.target).prop('clientHeight');
}
Then finally when the user presses the gold or the silver button, the variable is reset to true so that the first element will be selected again.
<button id="gold" class="active" ng-click="gold();testBool = true;">gold</button>
<button id="silver" class="" ng-click="silver();testBool = true;">silver</button>
I hope my explanation was understandable, please let me know if you have any queries.
To select by default 1st element you can write something like:
ng-style="$index === 0 ? divStyle : ''"
You can also make your code simple by put out and write something like:
<div id="btns">
<button id="rightBtn" ng-disabled="currentPage >= myData.length/pageSize - 1"
ng-click="currentPage=currentPage+1;activeMenu=myData[currentPage*5];">next</button>
<button id="leftBtn" ng-disabled="currentPage == 0"
ng-click="currentPage=currentPage-1;activeMenu=myData[currentPage*5];">prev</button>
</div>
Demo PLunker
You can use $first property with ng-class in ng-repeat like
<div class="small" ng-class="$first?'active':''" ng-repeat="x in myData | startFrom:(currentPage)*pageSize | limitTo:pageSize" ng-click="setActive(x, $event)" ng-style="activeMenu === x && divStyle">

Service Portal Widget form Returns "Record not Found"

I created new widget by cloning existing form widget in Service Portal (ServiceNow). Modified sys_id and table name in the URL (https://dev32223.service-now.com/aaportal/?id=departments&table=x_34334_aaaa_incident&sys_id=ee384830db2e32001cf8dec0cf9619de). Created new page with the widget and the resulting web page shows 'Record Not Found'
HTML:
<div ng-if="!data.isValid && !data.emptyStateTemplate" class="panel panel-default">
<div class="panel-body wrapper-lg text-center">
${Record not found}
</div>
</div>
<div ng-if="!data.isValid && data.emptyStateTemplate" class="panel-shift">
<div class="empty-state-wrapper panel panel-default" ng-include="data.emptyStateTemplate"></div>
</div>
<div ng-if="data.isValid" class="panel-shift">
<div class="" ng-if="!data.f._view.length && data.hideRelatedLists && data.emptyStateTemplate">
<div class="empty-state-wrapper panel panel-default" ng-include="data.emptyStateTemplate"></div>
</div>
<div class="" ng-if="!data.f._view.length && data.hideRelatedLists && !data.emptyStateTemplate">
<div class="panel panel-default">
<div class="panel-heading"><span class="panel-title">{{data.f.title}}</span> <span ng-if="options.showFormView == 'true' && data.f.view != ''">[{{data.f.view_title}} view]</span></div>
<div class="panel-body wrapper-lg text-center">
${No elements to display}
</div>
</div>
</div>
<div class="panel panel-default" ng-if="data.f._view.length || !data.hideRelatedLists" >
<div class="panel-heading" ng-if="data.f.title.length" sp-context-menu="getUIActionContextMenu(event)">
<span class="dropdown m-r-xs" ng-if="(data.isAdmin || getUIActions('context').length > 0) && options.omitHeaderOptions != 'true'">
<span class="dropdown-toggle glyphicon glyphicon-menu-hamburger" style="line-height: 1.4em" id="adminMenu" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true"></span>
<ul class="dropdown-menu" aria-labelledby="adminMenu">
<li ng-if="::data.isAdmin">${Open in platform}</li>
<li ng-if="::data.isAdmin" class="dropdown-header">${Configure}</li>
<li ng-if="::data.isAdmin">${Form Layout}</li>
<li ng-if="::data.isAdmin">${Related Lists}</li>
<li ng-if="::data.isAdmin">${UI Policies} <span class="badge pull-right" ng-if="f.policy.length">{{f.policy.length}}</span></li>
<li ng-if="::data.isAdmin">${Client Scripts} <span class="badge pull-right" ng-if="adminMenu.getClientScriptCount()">{{adminMenu.getClientScriptCount()}}</span></li>
<li ng-if="getUIActions('context').length > 0 && data.isAdmin" role="separator" class="divider"></li>
<li ng-repeat="action in getUIActions('context')">{{action.name}}</li>
<li ng-if="::data.isAdmin || getUIActions('context').length > 0" role="separator" class="divider"></li>
<li><a target="_new" href="/{{data.f.table}}.do?PDF&sys_id={{data.sys_id}}&sysparm_view={{data.f.view}}">${Export to PDF}</a></li>
<li><a target="_new" href="/{{data.f.table}}.do?PDF&landscape=true&sys_id={{data.sys_id}}&sysparm_view={{data.f.view}}">${Export to PDF (landscape)}</a></li>
</ul>
</span>
<span class="panel-title">{{data.f.title}}</span> <span ng-if="options.showFormView == 'true' && data.f.view != ''">[{{data.f.view_title}} view]</span>
<div ng-if="attachmentHandler && data.canAttach" title="{{::data.addAttachmentMsg}}" class="pull-right attachment-button">
<sp-attachment-button></sp-attachment-button>
</div>
</div>
<div class="panel-body">
<!-- performance debug -->
<div ng-if="data.show_sql">
<div class="comment">
<span ng-if="data.f._perf.sql_count">${SQL Statements {{data.f._perf.sql_count}}}, </span>
<span>${Time {{data.f._perf.time}}}</span>
</div>
<div ng-repeat="s in data.f._perf.sql" class="{{s.type}}">
{{s.statement}}
</div>
</div>
<!-- attachments -->
<sp-attachment-manager table="data.table" sys-id="data.f._attachmentGUID" omit-edit="!data.canAttach"></sp-attachment-manager>
<!-- form -->
<div>
<sp-model form_model="data.f" mandatory="mandatory"></sp-model>
</div>
<!-- UI Action Links -->
<div ng-if="getUIActions('link').length > 0">
<label style="margin: 0;">${Related Links}</label>
<div ng-repeat="action in getUIActions('link')">
<a href ng-click="triggerUIAction(action)">{{action.name}}</a>
</div>
</div>
<!-- related lists -->
<div ng-if="!data.hideRelatedLists">
<label style="margin: 0">${Related Lists}</label>
<div style="margin-bottom: 7px; padding-bottom: 7px; border-bottom: 1px solid #f5f5f5;">
<span ng-repeat="rl in data.f._related_lists" ng-if="rl.visible">
<a ng-if="rl.type != 'REL'" href="?id=lf&table={{rl.table}}&filter={{rl.field}}%3D{{data.f.sys_id}}&view={{data.f.view}}" ng-click="openRelatedList($event, {id: 'lf', table: '{{rl.table}}', filter: '{{rl.field}}%3D{{data.f.sys_id}}'})">{{rl.plural}}
<span class="label label-as-badge label-primary" ng-if="rl.count">{{rl.count}}</span>
</a>
<a ng-if="rl.type == 'REL'" href="?id=lf&table={{rl.table}}&relationship_id={{rl.relationship_id}}&apply_to={{rl.apply_to}}&apply_to_sys_id={{rl.apply_to_sys_id}}&view={{data.f.view}}" ng-click="openRelatedList($event, {id: 'lf', table: '{{rl.table}}', apply_to: '{{rl.apply_to}}', apply_to_sys_id: '{{rl.apply_to_sys_id}}', relationship_id: '{{rl.relationship_id}}'})">{{rl.label}}
<span class="label label-as-badge label-primary" ng-if="rl.count">{{rl.count}}</span>
</a>
<span ng-if="!$last" style="padding-left: .5em; padding-right: .5em;"> | </span>
</span>
</div>
</div>
</div>
<div class="panel-footer">
<button ng-mousedown="triggerUIAction(action)" ng-repeat="action in getUIActions('button')" class="btn btn-default action-btn">{{action.name}}</button>
<span>{{status}}</span>
<button ng-if="getPrimaryAction()" type="submit" ng-mousedown="triggerUIAction(getPrimaryAction())" class="btn btn-primary action-btn pull-right">${Save} <span ng-if="saveButtonSuffix">(${{{saveButtonSuffix}}})</span></button>
<div style="clear: both;"></div>
<div ng-if="mandatory.length" class="alert alert-info" style="margin-top: .5em">
<span ng-if="mandatory.length > 0">${Required information} </span>
<span ng-repeat="f in mandatory" class="label label-danger" style="margin-right: .5em; display: inline-block;">{{f.label}}</span>
</div>
</div>
</div>
</div>
Client Script:
function ($scope, $rootScope, $timeout, spUtil, $location, $window, nowAttachmentHandler) {
$scope.mandatory = [];
$scope.data.show_sql = false;
$scope.saveButtonSuffix = spUtil.getAccelerator('s');
$scope.adminMenu = {
encodedPageUrl: encodeURIComponent($location.url()),
getClientScriptCount: function() {
var count = 0;
if ($scope.data.f.client_script) {
count += $scope.data.f.client_script.onChange.length;
count += $scope.data.f.client_script.onLoad.length;
count += $scope.data.f.client_script.onSubmit.length;
}
return count;
}
};
$scope.getUIActions = function(type) {
if ($scope.data.disableUIActions)
return [];
if (type) {
return $scope.data.f._ui_actions.filter(function(action) {
//We handle the primary action button separately.
return !action.primary && action['is_' + type];
});
} else {
return $scope.data.f._ui_actions;
}
}
$scope.getPrimaryAction = function() {
var primaryActions = $scope.data.f._ui_actions.filter(function(action) {
return action.primary;
});
return (primaryActions.length) ? primaryActions[0] : null;
}
$scope.getUIActionContextMenu = function(event) {
var menu = [];
if (event.ctrlKey)
return menu;
var contextActions = $scope.getUIActions('context');
contextActions.forEach(function(action) {
menu.push([action.name, function() {
$scope.triggerUIAction(action);
}]);
});
if (contextActions.length > 0)
menu.push(null);
menu.push([$scope.data.exportPDFMsg, function() {exportPDF("");}]);
menu.push([$scope.data.exportPDFLandMsg, function() {exportPDF('true');}]);
return menu;
}
function exportPDF(landscape) {
$window.open("/" + $scope.data.f.table + ".do?PDF&landscape=" + landscape + "&sys_id=" + $scope.data.sys_id + "&sysparm_view=" + $scope.data.f.view);
}
//trigger the primary UI Action on save (if there is one)
var deregister = $scope.$on('$sp.save', function() {
var primaryAction = $scope.getPrimaryAction();
if (primaryAction)
$scope.triggerUIAction(primaryAction);
});
$scope.$on('$destroy', function() {deregister()});
$scope.triggerUIAction = function(action) {
if ($scope.data.disableUIActions)
return;
if (g_form) {
$timeout(function() {
g_form.submit(action.action_name || action.sys_id);
});
}
}
$scope.$on("spModel.uiActionComplete", function(evt, response) {
var sysID = (response.isInsert) ? response.sys_id : $scope.data.sys_id;
loadForm($scope.data.table, sysID).then(constructResponseHandler(response));
});
function constructResponseHandler(response) {
return function() {
var message;
var eventName = "sp.form.record.updated";
if (response.isInsert) {
message = $scope.data.recordAddedMsg;
var search = $location.search();
search.sys_id = response.sys_id;
search.spa = 1;
$location.search(search).replace();
} else
message = $scope.data.updatedMsg;
$scope.data.hideRelatedLists = hideRelatedLists();
$scope.$emit(eventName, $scope.data.f._fields);
$rootScope.$broadcast(eventName, $scope.data.f._fields);
$scope.status = message;
spUtil.addTrivialMessage(message);
$timeout(clearStatus, 2000);
}
}
var ctrl = this;
// switch forms
var unregister = $scope.$on('$sp.list.click', onListClick);
$scope.$on("$destroy", function() {
unregister();
})
function onListClick(evt,arg) {
loadForm(arg.table, arg.sys_id);
}
function loadForm(table, sys_id){
var f = {};
//$scope.data.table = f.table = 'x_19668_halo_incident';
//$scope.data.sys_id = f.sys_id ='ee384830db2e32001cf8dec0cf9619de';
$scope.data.table = f.table = table;
$scope.data.sys_id = f.sys_id = sys_id;
f.view = $scope.data.view;
return $scope.server.update().then(setupAttachmentHandler);
}
function openRelatedList(e, queryString){
// todo: Open this in a modal
$location.search(queryString);
e.preventDefault();
}
$scope.$on('spModel.fields.rendered', function() {
if (ctrl.panels)
ctrl.panels.removeClass('shift-out').addClass('shift-in');
});
var g_form;
$scope.$on('spModel.gForm.initialized', function(e, gFormInstance) {
if (gFormInstance.getTableName() == $scope.data.f.table)
g_form = gFormInstance;
});
// Show or hide related lists
$scope.$watch('data.f._related_lists', function(){
$scope.data.hideRelatedLists = hideRelatedLists();
}, true);
function hideRelatedLists() {
if (!$scope.data.f._related_lists)
return true;
if ($scope.options.hideRelatedLists == true)
return true;
if ($scope.data.sys_id == '-1')
return true;
// If all related lists are visible=false then hide
if ($scope.data.f._related_lists.length > 0) {
for (var i in $scope.data.f._related_lists) {
var list = $scope.data.f._related_lists[i];
if (list.visible) {
return false;
}
}
}
return true;
}
function clearStatus() {
$scope.status = "";
}
function setupAttachmentHandler(){
$scope.attachmentHandler = new nowAttachmentHandler(appendSuccess, appendError);
$timeout(function() {
var sizeLimit = 1024 * 1024 * 24; // 24MB
$scope.attachmentHandler.setParams($scope.data.table, $scope.data.f._attachmentGUID, sizeLimit);
});
$scope.$on('dialog.upload_too_large.show', function(e){
console.log($scope.data.largeAttachmentMsg);
spUtil.addErrorMessage($scope.data.largeAttachmentMsg);
});
}
setupAttachmentHandler();
function appendSuccess() {
spUtil.addTrivialMessage($scope.data.attachmentUploadSuccessMsg);
$scope.$broadcast("sp.attachments.update", $scope.data.f._attachmentGUID);
}
function appendError(error) {
$scope.errorMessages.push(error);
}
}
Server Script:
// form functionality - URL parameter driven
(function($sp, input, data, options, gs)
{
/* "use strict"; -linter issues */
// populate the 'data' variable
data.attachmentUploadSuccessMsg = gs.getMessage("Attachment upload was successful");
data.recordAddedMsg = gs.getMessage("Record Added");
data.updatedMsg = gs.getMessage("updated_uppercase");
data.exportPDFMsg = gs.getMessage("Export to PDF");
data.exportPDFLandMsg = gs.getMessage("Export to PDF (landscape)");
data.addAttachmentMsg = gs.getMessage("Add an attachment");
data.largeAttachmentMsg = gs.getMessage("Attached files must be smaller than {0} - please try again", "24MB");
data.isAdmin = true;
data.emptyStateTemplate = options.empty_state_template;
data.disableUIActions = options.disableUIActions || false;
data.hideRelatedLists = options.hideRelatedLists || false;
if (input) {
data.table = input.table;
data.sys_id = input.sys_id;
data.view = input.view;
var result = {};
if (input._fields)
result = $sp.saveRecord(input.table, input.sys_id, input._fields);
if (input.sys_id == '-1'){
data.sys_id = result.sys_id;
data.isNewRecord = true;
}
} else {
data.table = $sp.getParameter("t") || $sp.getParameter("table") || $sp.getParameter("sl_table") || options.table;
data.sys_id = $sp.getParameter("sys_id") || $sp.getParameter("sl_sys_id") || options.sys_id;
data.view = $sp.getParameter("v") || $sp.getParameter("view") || options.view; // no default
}
data.query = $sp.getParameter("query") || options.query;
data.f = {};
if (!data.table)
return;
//if (!GlideTableDescriptor.isValid(data.table))
//return;
if (!data.sys_id)
return;
gs.info("data.table, data.sys_id 3:");
var rec = $sp.getRecord(data.table, data.sys_id); gs.info("data.table, data.sys_id 31:");
data.isValid = rec.isValid() || data.sys_id == "-1"; gs.info("data.table, data.sys_id 32:");
gs.info("data.table, data.sys_id 4:");
if (!data.isValid)
return;
gs.info("data.table, data.sys_id :"+data.table+ data.sys_id);
data.table = rec.getRecordClassName();
data.tableHierarchy = GlideDBObjectManager.getTables(data.table).toArray().join();
data.canWrite = rec.canWrite();
data.canAttach = data.canWrite && gs.hasRole(gs.getProperty('glide.attachment.role')) && !GlideTableDescriptor.get(data.table).getED().getBooleanAttribute("no_attachment");
data.f = $sp.getForm(data.table, data.sys_id, data.query, data.view);
// Activity formatter is hardcoded to set specific options
for (var f in data.f._formatters) {
var fm = data.f._formatters[f];
if (fm.formatter == "activity.xml") {
fm.hardcoded = true;
fm.widgetInstance = $sp.getWidget('widget-ticket-conversation',
{table: data.table,
sys_id: data.sys_id,
includeExtended: true,
title: "${Activity}",
placeholder: "${Add a comment}",
btnLabel: "${Post}"});
} else
fm.widgetInstance = $sp.getWidget(fm.widget, data);
}
})($sp, input, data, options, gs);
It looks like the control is not passing the line var rec = $sp.getRecord(data.table, data.sys_id); I can not figure out the issue, but I have an alternate solution. You can use iframes (shown below) to show form or list of records.
Mark it as answer if this answers your question.

I keep on getting TypeError: v2.CurrentTaskForce is not a function

Left hand side of the page is displaying list of task force.
On click of the list item related data is to be displayed on right hand side.
When I click on the list item, first time it works fine. It displays the task force name as a header in right hand side panel. When I click on another list item it gives TypeError: v2.CurrentTaskForce is not a function
var VirtualDir = GetVirtualDirectory();
angular.module('MyApp',[])
.controller('TaskForceController', function ($scope, TaskForceService) { // inject taskforce service
$scope.TaskForceList = null;
//$scope.CurrentTaskForce = {}
TaskForceService.GetTaskForceList().then(function (d) {
$scope.TaskForceList = d.data;
}, function () {
alert('failed');
});
$scope.CurrentTaskForce = function (item) {
angular.forEach($scope.TaskForceList, function (value, index) {
value.IsActive = false ;
})
item.IsActive = true;
alert("s");
$scope.CurrentTaskForce = item;
}
})
.factory('TaskForceService', function ($http) { //here factory is created which is a populer way to create and configure services
var fac = {};
fac.GetTaskForceList = function () {
return $http.get(VirtualDir + '/TaskForce/GetMyTaskForce/');
}
return fac;
});
<div class="container-fluid" ng-controller="TaskForceController as tf">
<div class="row">
<div class="col-sm-3 col-md-2 sidebar">
<ul class="nav nav-sidebar" >
<%-- <li class="active">Overview <span class="sr-only">(current)</span></li>--%>
<li ng-repeat="item in TaskForceList" ng-class="{active: item.IsActive == true}" ng-click="CurrentTaskForce(item)" ><a href="#" >{{item.TaskForce}}</a></li>
</ul>
</div>
<div class="col-sm-9 co-md-9" style="left:20%">
<div class="panel panel-default" ">
<div class="panel-heading">
<p class="panel-title">{{CurrentTaskForce.TaskForce}}</p>
<!--ng-repeat="curritem in CurrentTaskForce"-->
</div>
</div>
</div>
</div>
</div>
While copying item to CurrentTask force instead of
$scope.CurrentTaskForce = item;
I used
angular.copy(item, $scope.CurrentTaskForce);
It is happening because CurrentTaskForce is defined as a function and a variable in same controller. Please use a different name for either.

Resources