2 ================================================================================
4 ================================================================================
5 Copyright (C) 2017 AT&T Intellectual Property
6 ================================================================================
7 Licensed under the Apache License, Version 2.0 (the "License");
8 you may not use this file except in compliance with the License.
9 You may obtain a copy of the License at
11 http://www.apache.org/licenses/LICENSE-2.0
13 Unless required by applicable law or agreed to in writing, software
14 distributed under the License is distributed on an "AS IS" BASIS,
15 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 See the License for the specific language governing permissions and
17 limitations under the License.
18 ================================================================================
20 <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
21 <%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>
22 <%@ page isELIgnored="false"%>
23 <%@ page import="org.onap.portalsdk.core.util.SystemProperties"%>
24 <%@ page import="org.onap.portalsdk.core.onboarding.util.PortalApiProperties"%>
25 <%@ page import="org.onap.portalsdk.core.onboarding.util.PortalApiConstants"%>
26 <%@ page import="org.onap.portalsdk.core.domain.MenuData"%>
27 <link rel="stylesheet" type="text/css" href="app/fusion/external/ebz/ebz_header/header.css">
28 <link rel="stylesheet" type="text/css" href="app/fusion/external/ebz/ebz_header/portal_ebz_header.css">
29 <link rel="stylesheet" type="text/css" href="app/fusion/external/ebz/sandbox/styles/style.css" >
32 <jsp:include page="/WEB-INF/fusion/jsp/ebz/loginSnippet.html" ></jsp:include>
35 <c:set var="UserName" value="<%= session.getAttribute(\"fullName\")%>" />
36 <c:set var="UserFirstName" value="<%= session.getAttribute(\"first_name\")%>" />
39 String contactUsLink = SystemProperties.getProperty(SystemProperties.CONTACT_US_LINK);
40 String redirectUrl = PortalApiProperties.getProperty(PortalApiConstants.ECOMP_REDIRECT_URL);
41 String portalUrl = redirectUrl.substring(0, redirectUrl.lastIndexOf('/')) + "/process_csp";
42 String getAccessLink = redirectUrl.substring(0, redirectUrl.lastIndexOf('/')) + "/get_access";
44 <c:set var="returnPortalUrl" value="<%=portalUrl%>" />
45 <c:set var="contactUsLink" value="<%=contactUsLink%>" />
46 <c:set var="getAccessLink" value="<%=getAccessLink%>" />
51 <%@include file="/WEB-INF/fusion/jsp/ebz/loginSnippet.html" %>
53 <div style="position: relative; z-index: 999;">
54 <div ng-controller="headerController">
56 <div class="headerContainer" id="headerContainer" ng-cloak ng-show="{{showHeader}}">
57 <div id="megaMenuContainer" class="megaMenuContainer" style="margin-top: 0; overflow: visible;">
60 <!-- Mega Menu parent-tab directive with three models menu-items, active-sub-menu, active-menu -->
61 <div id="topMenu" class="top-megamenu" ng-mouseleave="activeClickSubMenu.x.active=false; activeClickMenu.x.active=false">
62 <div style="float:left;width:100%;"parent-tab menu-items="megaMenuDataObject" active-sub-menu='activeClickSubMenu.x' active-menu='activeClickMenu.x'>
63 <div parentmenu-tabs mega-menu="true" menu-items="megaMenuDataObject" style="height:55px;">
64 <div style="float:left">
65 <li class="megamenu__item" style="line-height:55px;" onclick="returnToPortal()">
66 <div ng-include src="'app/fusion/scripts/DS2-view-models/header-logo.html'"></div>
68 <div menu-tabs mega-menu="true" tab-name="item.text" menu-item="item" active-menu="activeClickMenu.x"
69 ng-repeat="item in megaMenuDataObject" style="font-size: 18px;" ng-mousedown="loadFavorites()" >
70 <div parentmenu-tabs sub-menu="true" ng-show="activeClickMenu.x.active && item.active" menu-items="activeClickMenu.x.children">
71 <!-- Second level menu -->
73 <div menu-tabs sub-menu="true" tab-name="subItem.text"
74 tab-url="subItem.url" menu-item="subItem"
75 ng-repeat="subItem in activeClickMenu.x.children | orderBy : 'column'" active-menu="activeClickSubMenu.x"
76 sub-item-active="{{subItem.active}}" style="float:left;" aria-label="{{subItem.text}}"
77 ng-mouseenter="submenuLevelAction(subItem.text,subItem.column)"
78 ng-mouseleave="submenuLevelAction(subItem.text,subItem.column)"
79 ng-click="submenuLevelAction(subItem.text,subItem.column)" >
80 <i ng-if="subItem.text=='Favorites'" id="favorite-star"
81 class="icon-star favorites-icon-active">
85 <div class="sub__menu" ng-mouseleave="activeClickSubMenu.x.active=false" >
86 <ul ng-show="activeClickSubMenu.x.active" role="menubar" class="columns">
87 <!-- Third level menu -->
88 <div menu-tabs menu-item="subItem"
90 ng-repeat="subItem in activeClickSubMenu.x.children | orderBy : 'column'"
91 ng-show="activeClickSubMenu.x.active">
93 <i id="favorite-selector-third-level"
94 ng-show="isUrlFavorite(subItem.menuId)==false"
95 class="icon-star favorites-icon-inactive"
96 ng-if="subItem.url.length > 1">
98 <i id="favorite-selector-third-level"
99 ng-show="isUrlFavorite(subItem.menuId)"
100 class="icon-star favorites-icon-active"
101 ng-if="subItem.url.length > 1">
103 <span class="title" aria-label="{{subItem.text}}"
104 ng-click="goToUrl(subItem)">{{subItem.text}}</span>
105 <!-- Fourth level menus -->
106 <div att-links-list="">
107 <i id="favorite-selector-fourth-level"
108 class="icon-star favorites-icon-inactive"
109 ng-show="isUrlFavorite(tabValue.menuId)==false"
110 ng-if="tabValue.url.length > 1">
113 <i id="favorite-selector-fourth-level"
114 class="icon-star favorites-icon-active"
115 ng-show="isUrlFavorite(tabValue.menuId)"
116 ng-if="tabValue.url.length > 1">
119 <span role="menuitem" att-links-list-item=""
120 ng-repeat="tabValue in subItem.children"
121 ng-click="goToUrl(tabValue)"
122 att-accessibility-click="13,32"
123 ng-class="{'disabled': tabValue.disabled}">{{tabValue.text}}</span>
125 <hr ng-show="!$last"/>
129 <!-- Favorites level menu -->
130 <div class="favorites-window" ng-show='favoritesWindow' ng-mouseleave="hideFavoritesWindow()">
131 <div id="favorites-menu-items" ng-show="showFavorites">
132 <div ng-repeat="subItem in favoritesMenuItems" att-links-list="" style='display: inline'>
133 <i id="favorite-selector-favorites-list" class="icon-star favorites-icon-active">
135 <a id="favorites-list" aria-label="{{subItem.text}}"
136 ng-click="goToUrl(subItem)"
137 style="margin-left: 3px; margin-right: 20px; text-decoration: none; color: #666666;">
143 <p style='font-weight: 400; font-family: "Omnes-ONAP-W02", Arial !important;
144 font-size: 18px; text-align: center; background-color: lightgray;
145 width: 400px; margin-left: 25%; margin-right: 25%;'>
146 Manage favorites on ONAP Portal.
150 <!-- Favorites when empty -->
151 <div id="favorites-empty" ng-show='favoritesWindow' ng-show="emptyFavorites">
152 <div id="favorites-empty" ng-show="emptyFavorites" class="favorites-window-empty">
154 <img src="app/fusion/external/ebz/images/no_favorites_star.png">
155 <p class='favoritesLargeText'>No Favorites</p>
156 <p class='favoritesNormalText'>Manage favorites on ONAP Portal.</p>
166 <li class="megamenu__item" style="line-height:55px;" ng-if="loadMenufail">
167 <strong style="font-weight: 400 !important; font-family: "Omnes-ONAP-W02", Arial !important; font-size: 18px;" >Unable to load menus</strong>
169 <!-- <li class="megamenu__item" style="width: 20%;"> </li>
171 <!-- Login Snippet-->
172 <div style="float:right">
173 <li id="bcLoginSnippet" class="megamenu__item" style="width: 140px;" >
174 <div popover="loginSnippet.html" aria-label="Login Snippet" referby="loginSnippet" att-accessibility-click="13,32" popover-style="\" popover-placement="below" style="width: 200px;">
175 <div class="icon-user-small login-snippet-icon"></div>
176 <div class="login-snippet-text" style="display: inline-block; font-size:12px; margin-left:5px;overflow: hidden; max-height: 31px; max-width:120px; padding-top: 0px; margin-top: 0px; white-space: nowrap;" ng-bind="userProfile.firstName"></div>
179 <li class="megamenu__item" style="width:120px;"> </li>
184 <div style="clear: both"></div>
189 <div class="license-notification" id="license-notification">
190 <a href="javascript:void(0)" style="background-color:#bbb;" class="button button--small" tooltip="Please contact ONAP Portal team to get the license" tooltip-placement="below" tooltip-style="light" tooltip-popup-delay="500" >
191 <span style="">{{app_name_full}}</span>
194 <div style="position: relative; color: black; top: 70px;">
196 <span ng-style="adjustHLeftMenu('burgerIcon')" style="z-index:998; position:fixed; left:0%; font-size:35px; margin-left:10px;text-decoration:none;">
197 <a ng-click="toggleDrawer();isOpen = !isOpen" href="javascript:void(0);" class="arrow-icon-left" >
198 <span class="icon-hamburger"></span></a>
199 <span ng-init="isOpen = true" ng-show="isOpen" style="font-size:16px; position:relative; top:-8px; left:-15px;">    {{app_name}}</span>
201 <div att-drawer drawer-slide="left" drawer-custom-top="{{drawer_custom_top}}px" drawer-size="200px" drawer-open="drawerOpen" drawer-custom-height="100%" >
202 <div ng-style="adjustHLeftMenu('leftMenu')">
203 <div class="attDrawer" style="margin-top:{{drawer_margin_top}}px;">
204 <div style="margin-left:10px; margin-right:10px;">
205 <accordion close-others="true" css="att-accordion--no-box">
206 <accordion-group ng-repeat="parent in menuItems" heading="{{parent.parentLabel}}" child="{{parent.parentAction}}" parent-link="{{parent.parentAction}}" image-source="{{parent.parentImageSrc}}" child-length="{{parent.childItemList.length}}" is-open="parent.open">
207 <div ng-repeat="subMenu in parent.childItemList" style="font-size:12px; margin-left:10px;">
208 <a href="{{subMenu.action}}" style="font-size:12px; color:#666666;" >{{subMenu.label}}</a>
222 function returnToPortal(){
223 window.location.href = "<c:out value='${returnPortalUrl}'/>";
225 detectScrollEvent = function() {
226 var footerOff = $('#footerContainer').offset().top;
227 var headOff = $('#headerContainer').offset().top;
228 var winHeight = $(window).height();
229 if ((footerOff - headOff) <= winHeight) {
230 $('.att-drawer').css({
231 "height" : footerOff - headOff - 55
234 $('.att-drawer').css({
239 $(window).scroll(function() {
240 if ($('.att-drawer').is(':visible')) {
244 app.controller("headerController", function($scope, $timeout, $log, $http, UserInfoService, $window, $cookies,LeftMenuService) {
245 // $log.debug('HeaderController started');
246 $scope.jsonMenuData = [];
247 $scope.loadMenufail=false;
248 $scope.app_name = "";
249 $scope.app_name_full = "";
250 $scope.megaMenuDataObject =[];
251 $scope.activeClickSubMenu = {
254 $scope.activeClickMenu = {
257 $scope.favoritesMenuItems = [];
258 $scope.favoriteItemsCount = 0;
259 $scope.showFavorites = false;
260 $scope.emptyFavorites = false;
261 $scope.favoritesWindow = false;
268 /*Put user info into fields*/
269 $scope.inputUserInfo = function(userInfo){
270 if (typeof(userInfo) != "undefined" && userInfo!=null && userInfo!=''){
271 if (typeof(userInfo.USER_FIRST_NAME) != "undefined" && userInfo.USER_FIRST_NAME!=null && userInfo.USER_FIRST_NAME!='')
272 $scope.userProfile.firstName = userInfo.USER_FIRST_NAME;
273 if (typeof(userInfo.USER_LAST_NAME) != "undefined" && userInfo.USER_LAST_NAME!=null && userInfo.USER_LAST_NAME!='')
274 $scope.userProfile.lastName = userInfo.USER_LAST_NAME;
275 if (typeof(userInfo.USER_EMAIL) != "undefined" && userInfo.USER_EMAIL!=null && userInfo.USER_EMAIL!='')
276 $scope.userProfile.email = userInfo.USER_EMAIL;
279 /*getting user info from session*/
280 $scope.getUserNameFromSession = function(){
281 UserInfoService.getFunctionalMenuStaticDetailSession()
282 .then(function (res) {
283 $scope.userProfile.firstName = res.firstName;
284 $scope.redirectUrl = res.portalUrl;
287 $scope.getTopMenuStaticInfo=function() {
288 var promise = UserInfoService.getFunctionalMenuStaticDetailShareContext();
291 if(res==null || res==''){
292 $log.info('failed getting static User information');
293 $scope.getUserNameFromSession();
295 $log.info('Received static User information');
297 $scope.inputUserInfo(resData);
298 $scope.userProfile.fullName = $scope.userProfile.firstName+ ' '+ $scope.userProfile.lastName;
302 $log.info('failed getting static User information');
307 var unflatten = function( array, parent, tree ){
308 tree = typeof tree !== 'undefined' ? tree : [];
309 parent = typeof parent !== 'undefined' ? parent : { menuId: null };
310 var children = _.filter( array, function(child){ return child.parentMenuId == parent.menuId; });
312 if( !_.isEmpty( children ) ){
313 if( parent.menuId === null ){
316 parent['children'] = children
318 _.each( children, function( child ){ unflatten( array, child ) } );
324 var menuStructureConvert = function(menuItems) {
325 var megaMenuDataObjectTemp = [
334 url:"<c:out value='${contactUsLink}'/>"
338 url:"<c:out value='${getAccessLink}'/>"
342 return megaMenuDataObjectTemp;
347 LeftMenuService.getAppName().then(function(response){
350 if(j && j !== "null" && j!== "undefined"){
351 // console.log("app name is " + $scope.app_name);
352 $scope.app_name_full = j.data;
353 var processed_app_name = j.data;
354 if(processed_app_name.indexOf("[")<=-1) {
355 if (document.getElementById('license-notification')!=null)
356 document.getElementById('license-notification').style.display = "none";
358 var n = processed_app_name.length;
362 $scope.app_name = processed_app_name.substr(0, n);
364 throw "Get app_name response is not an object/is empty";
367 console.log("error happened while trying to get app name "+e);
371 console.log('getAppName failed', error);
374 $scope.getTopMenuStaticInfo();
375 $scope.getMenu=function() {
379 url: 'get_functional_menu',
380 // TIMEOUT USED FOR LOCAL TESTING ONLY
382 }).success(function (response) {
383 if(response == '101: Timeout') {
384 $log.error('Timeout attempting to get_functional_menu');
385 // TIMEOUT USED FOR LOCAL TESTING ONLY
386 // $scope.createErrorMenu();
387 $scope.megaMenuDataObject = menuStructureConvert('');
389 $log.debug('get_functional_menu success: ' + response);
390 if(typeof response != 'undefined' && response.length!=0 && typeof response[0] != 'undefined' && typeof response[0].error!="undefined"){
391 // createErrorMenu() USED FOR LOCAL TESTING ONLY
392 // $scope.createErrorMenu();
393 $scope.megaMenuDataObject = menuStructureConvert('');
394 // $scope.loadMenufail=true;
396 $scope.jsonMenuData = unflatten( response );
397 $scope.megaMenuDataObject = menuStructureConvert($scope.jsonMenuData);
400 }).error(function (response){
401 // createErrorMenu() USED FOR LOCAL TESTING ONLY
402 // $scope.createErrorMenu();
404 //$scope.loadMenufail=true;
405 $scope.megaMenuDataObject = menuStructureConvert('');
406 $log.debug('REST API failed get_functional_menu...'+ response);
409 $scope.adjustHLeftMenu = function (type){
410 $scope.showHeader = ($cookies.show_app_header == undefined ? true : $cookies.show_app_header);
412 if($scope.showHeader == true) {
413 $scope.drawer_margin_top = 60;
414 $scope.drawer_custom_top = 54;
415 $scope.toggle_drawer_top = 55;
419 $scope.drawer_margin_top = 50;
420 $scope.drawer_custom_top = 0;
421 $scope.toggle_drawer_top = 10;
423 if(type=='burgerIcon'){
424 return { "top": $scope.toggle_drawer_top+"px"};
425 }else if(type=='leftMenu'){
426 return { "margin-top": $scope.drawer_margin_top+"px"};
430 $scope.adjustHeader=function() {
431 $scope.showHeader = ($cookies.show_app_header == undefined ? true : $cookies.show_app_header);
433 if($scope.showHeader == true) {
434 $scope.drawer_margin_top = 50;
435 $scope.drawer_custom_top = 54;
436 $scope.toggle_drawer_top = 55;
440 $scope.drawer_margin_top = 40;
441 $scope.drawer_custom_top = 0;
442 $scope.toggle_drawer_top = 10;
450 $scope.adjustHeader();
453 /* **************************************************************************/
454 /* Logic for the favorite menus is here */
456 $scope.loadFavorites = function () {
457 $log.debug('loadFavorites has happened.');
458 if ($scope.favoritesMenuItems == '') {
459 $scope.generateFavoriteItems();
460 $log.debug('loadFavorites is calling generateFavoriteItems()');
462 $log.debug('loadFavorites is NOT calling generateFavoriteItems()');
466 $scope.goToUrl = function (item) {
467 $log.info("goToUrl called")
471 var restrictedApp = item.restrictedApp;
472 $log.debug('Restricted app status is: ' + restrictedApp);
474 $log.info('No url found for this application, doing nothing..');
478 $window.open(url, '_blank');
480 $window.open(url, '_self');
485 $scope.submenuLevelAction = function(index, column) {
486 if ($scope.favoritesMenuItems == '') {
487 $scope.generateFavoriteItems();
488 $log.debug('submenuLevelAction is calling generateFavoriteItems()');
490 $log.debug('item hovered/clicked: ' + index + '; column = ' + column);
491 if (column == 2) { // 2 is Design
492 $scope.favoritesWindow = false;
493 $scope.showFavorites = false;
494 $scope.emptyFavorites = false;
496 if (index=='Favorites' && $scope.favoriteItemsCount != 0) {
497 $log.debug('Showing Favorites window');
498 $scope.favoritesWindow = true;
499 $scope.showFavorites = true;
500 $scope.emptyFavorites = false;
502 if (index=='Favorites' && $scope.favoriteItemsCount == 0) {
503 $log.debug('Hiding Favorites window in favor of No Favorites Window');
504 $scope.favoritesWindow = true;
505 $scope.showFavorites = false;
506 $scope.emptyFavorites = true;
509 $scope.favoritesWindow = false;
510 $scope.showFavorites = false;
511 $scope.emptyFavorites = false;
515 $scope.hideFavoritesWindow = function() {
516 $log.debug('$scope.hideFavoritesWindow has been called');
517 $scope.showFavorites = false;
518 $scope.emptyFavorites = false;
521 $scope.isUrlFavorite = function (menuId) {
522 // $log.debug('array objects in menu favorites = ' + $scope.favoriteItemsCount + '; menuId=' + menuId);
523 var jsonMenu = JSON.stringify($scope.favoritesMenuItems);
524 var isMenuFavorite = jsonMenu.indexOf('menuId\":' + menuId);
525 if (isMenuFavorite==-1) {
533 $scope.generateFavoriteItems = function() {
536 url: 'get_favorites',
537 // TIMEOUT USED FOR LOCAL TESTING ONLY
539 }).success(function (response) {
540 if (response == '101: Timeout') {
541 $log.error('Timeout attempting to get_favorites_menu');
543 if(typeof response != 'undefined' && response.length!=0 && typeof response[0] != 'undefined' && typeof response[0].error!="undefined"){
544 $log.error('REST API failed get_favorites' + response);
546 $log.debug('get_favorites = ' + JSON.stringify(response));
547 $scope.favoritesMenuItems = response;
548 $scope.favoriteItemsCount = Object.keys($scope.favoritesMenuItems).length;
549 $log.info('number of favorite menus: ' + $scope.favoriteItemsCount);
552 }).error(function (response){
553 $log.error('REST API failed get_favorites' + response);
554 //createFavoriteErrorMenu() USED FOR LOCAL TESTING ONLY
555 // $scope.createFavoriteErrorMenu();
559 $scope.createFavoriteErrorMenu=function() {
560 $scope.favoritesMenuItems = [
563 $scope.favoriteItemsCount = Object.keys($scope.favoritesMenuItems).length;
564 $log.info('number of favorite menus: ' + $scope.favoriteItemsCount);
567 /* end of Favorite Menu code */
568 /* **************************************************************************/
571 /* **************************************************************************/
572 // THIS IS USED FOR LOCAL TESTING ONLY
573 /* **************************************************************************/
575 $scope.createErrorMenu=function() {
576 $scope.jsonMenuData = [
581 "parentMenuId": null,
587 "text": "Infrastructure Ordering",
588 "parentMenuId": null,
594 "text": "Service Creation",
595 "parentMenuId": null,
601 "text": "Service Mgmt",
602 "parentMenuId": null,
610 "url": "http://google.com"
615 "text": "Mike Little's Coffee Cup",
617 "url": "http://coffee.com"
622 "text": "Andy and his Astrophotgraphy",
624 "url": "http://nightskypix.com"
631 "url": "http://http://jsonlint.com"
638 "url": "http://ebiz.sbc.com/hronestop"
644 "text": "3rd Level App1c R200",
646 "url": "http://app1c.com"
651 "text": "3rd Level App4b R16",
653 "url": "http://app4b.com"
658 "text": "3rd Level App2b R16",
660 "url": "http://app2b.com"
666 "parentMenuId": null,
670 $scope.jsonMenuData = unflatten( $scope.jsonMenuData );
671 $scope.megaMenuDataObject = menuStructureConvert($scope.jsonMenuData);
672 // $log.debug(JSON.stringify($scope.jsonMenuData));
674 var childItemList="";
677 childItemList = ${menu.childItemList};
678 parentList = ${menu.parentList};
680 console.log("ebz_header: failed to get child/parent lists", err);
683 var pageUrl = window.location.href.split('/')[window.location.href.split('/').length-1];
685 $scope.menuItems = [];
686 for (var i = 0; i < parentList.length; i++) {
687 $scope.openCurrentMenu = false;
688 if(pageUrl==parentList[i].action)
689 $scope.openCurrentMenu = true;
690 $scope.childItemList = childItemList[i];
691 for(chIndex in $scope.childItemList){
692 if($scope.childItemList.length>0)
693 if($scope.childItemList[chIndex].action!=null){
694 if($scope.childItemList[chIndex].action==pageUrl)
695 $scope.openCurrentMenu = true;
699 parentLabel : parentList[i].label,
700 parentAction : parentList[i].action,
701 parentImageSrc : parentList[i].imageSrc,
702 open:$scope.openCurrentMenu,
703 childItemList : $scope.childItemList
705 $scope.menuItems.push($scope.item);
707 $scope.arrowShow = true;
708 $scope.drawerOpen = false;
709 $scope.subMenuContent = false;
710 $scope.toggleSubMenu = function() {
711 $scope.subMenuContent = !$scope.subMenuContent;
714 var drawerOpen = 'open';
715 if (drawerOpen == 'open') {
716 $scope.drawerOpen = true;
717 $scope.arrowShow = true;
719 $scope.arrowShow = false;
721 $scope.arrowShow = true;
722 $scope.drawerOpen = false;
723 $scope.toggleDrawer = function() {
724 $scope.drawerOpen = !($scope.drawerOpen);
725 if ($scope.drawerOpen) {
726 $scope.arrowShow = true;
727 if (document.getElementById('mContent')!=null)
728 document.getElementById('mContent').style.marginLeft = "0px";
730 $scope.arrowShow = false;
731 if (document.getElementById('mContent')!=null)
732 document.getElementById('mContent').style.marginLeft = "-150px";
735 //var drawerOpen = getCookie('drawerOpen');
736 if (drawerOpen == 'open') {
737 $scope.drawerOpen = true;
738 $scope.arrowShow = true;
740 $scope.arrowShow = false;
742 $timeout(function() {
748 app.filter("ellipsis", function(){
749 return function(text, length){
751 var ellipsis = text.length > length ? "..." : "";
752 return text.slice(0, length) + ellipsis;