1. Get started

  2.  Welcome
  3.  Get started
  4.  Run as a Windows service
  5.  Build your first app
  6.  Requirements
  7.  Accessibility support
  8.  Development overview
  9.  About release versions
  10.  What's new
  11.  Frequently asked questions
  12. Working with apps

  13.  Home page
  14.  Create or import an app
  15.  Edit and launch an app
  16.  Choose a theme
  17.  Select a map or scene
  18.  Add widgets
  19.  Widgets overview
  20.  Configure app attributes
  21.  Preview apps on smaller screens
  22.  Export as a template
  23.  Edit or preview template
  24.  Use URL parameters
  25.  Upgrade apps
  26. Configure 2D widgets

  27.  About widget
  28.  Add Data widget
  29.  Analysis widget
  30.  Attribute Table widget
  31.  Batch Attribute Editor widget
  32.  Basemap Gallery widget
  33.  Bookmark widget
  34.  Business Analyst widget
  35.  Chart widget
  36.  Controller widget
  37.  Coordinate widget
  38.  Coordinate Conversion widget
  39.  Cost Analysis widget
  40.  Data Aggregation widget
  41.  Directions widget
  42.  Distance and Direction widget
  43.  District Lookup widget
  44.  Draw widget
  45.  Edit widget
  46.  Emergency Response Guide widget
  47.  Extent Navigate widget
  48.  Filter widget
  49.  Full Screen widget
  50.  Geocoder widget
  51.  GeoLookup widget
  52.  Geoprocessing widget
  53.  Grid Overlay widget
  54.  Gridded Reference Graphic widget
  55.  Group Filter widget
  56.  Image Measurement widget
  57.  Home Button widget
  58.  Incident Analysis widget
  59.  Infographic widget
  60.  Info Summary widget
  61.  Layer List widget
  62.  Legend widget
  63.  Measurement widget
  64.  My Location widget
  65.  Near Me widget
  66.  Network Trace widget
  67.  Oblique Viewer widget
  68.  Overview Map widget
  69.  Parcel Drafter widget
  70.  Print widget
  71.  Public Notification widget
  72.  Query widget
  73.  Related Table Charts widget
  74.  Report Feature widget
  75.  Reviewer Dashboard widget
  76.  Scalebar widget
  77.  Screening widget
  78.  Search widget
  79.  Select widget
  80.  Share widget
  81.  Situation Awareness widget
  82.  Smart Editor widget
  83.  Splash widget
  84.  Stream widget
  85.  Suitability Modeler widget
  86.  Summary widget
  87.  Swipe widget
  88.  Threat Analysis widget
  89.  Time Slider widget
  90.  Visibility widget
  91.  Zoom Slider widget
  92. Configure 3D widgets

  93.  3DFx widget
  94.  About widget
  95.  Basemap Gallery widget
  96.  Compass widget
  97.  Coordinate widget
  98.  Daylight widget
  99.  Full Screen widget
  100.  Home Button widget
  101.  Layer List widget
  102.  Legend widget
  103.  Measurement widget
  104.  My Location widget
  105.  Navigate widget
  106.  Search widget
  107.  Share widget
  108.  Slides widget
  109.  Splash widget
  110.  Zoom Slider widget
  111. Widget development

  112.  Get started
  113.  Naming conventions
  114.  In-panel and off-panel widgets
  115.  Deploy your widget
  116.  Required files
  117.  Widget manifest
  118.  Extend BaseWidget
  119.  Required properties
  120.  Define the template
  121.  Configure the Demo widget
  122.  Add i18n support
  123.  Make widgets user-friendly
  124.  Build your first app
  125.  Communication to app container
  126.  Widget properties
  127.  Make widgets responsive
  128.  Communication between widgets
  129.  Dojo dijit
  130.  Make widgets configurable in builder
  131.  Make widgets backward compatible
  132.  Create a controller widget
  133.  Widget life cycle
  134.  Add help for your widget
  135.  Create a feature action in your widget
  136.  Provide and consume data sources in widgets
  137.  Best practices for unit testing
  138. Theme development

  139.  Theme elements
  140.  Create a theme
  141. 3D development

  142.  3D development guide
  143. Sample code

  144.  Create a custom in-panel widget
  145.  Create a ListView widget
  146.  Create a custom widget using the Report dijit
  147.  Create a new theme
  148.  Create a new style for a theme
  149.  Create a new default layout
  150.  Create a nondefault layout
  151.  Create a new panel
  152.  Create a new layout widget
  153.  Create a controller widget
  154.  Create a feature action in your widget
  155.  Send a layer to the Attribute Table widget
  156.  Open multiple widgets simultaneously
  157.  Use other libraries
  158.  Change the URL of ArcGIS API for JavaScript
  159. Deployment

  160.  Deploy your app
  161.  Use proxy
  162.  Web-tier authentication
  163.  Custom widget and theme deployment
  164.  Upgrade custom widgets and themes
  165. Framework reference

  166.  CSS framework
  167.  FeatureActionManager class
  168.  FilterManager class
  169.  LayerInfo class
  170.  LayerInfos class
  171.  LayerNode class
  172.  LayerStructure class
  173.  PanelManager class
  174.  SelectionManager class
  175.  Utils class
  176.  WidgetManager class
  177. dijit

  178.  FeaturelayerChooserFromMap class
  179.  FeaturelayerServiceBrowser class
  180.  FeatureSetChooserForSingleLayer class
  181.  Filter class
  182.  ImageChooser class
  183.  ItemSelector class
  184.  LayerChooserFromMap class
  185.  RendererChooser class
  186.  Report class
  187.  SnapShot class
  188.  SymbolChooser class
  189. JSON reference

  190.  App configuration
  191.  Map configuration
  192.  Widget configuration
  193.  Widget pool configuration
  194.  Widget on-screen configuration
  195.  Panel configuration
  196.  Group configuration
  197.  DataSource configuration
  198.  About
  199.  Analysis
  200.  Attribute Table
  201.  Basemap Gallery
  202.  Bookmark
  203.  Chart
  204.  Coordinate
  205.  Directions
  206.  Draw
  207.  Edit
  208.  Full Screen
  209.  Geocoder
  210.  Geoprocessing
  211.  Controller
  212.  Home Button
  213.  Image Measurement
  214.  Infographic
  215.  Layer List
  216.  Legend
  217.  Loading Page
  218.  Measurement
  219.  My Location
  220.  Near Me
  221.  Oblique Viewer
  222.  Overview Map
  223.  Print
  224.  Query
  225.  Report Feature
  226.  Reviewer Dashboard
  227.  Scalebar
  228.  Search
  229.  Share
  230.  Splash
  231.  Stream
  232.  Swipe
  233.  Time Slider
  234.  Zoom Slider
  235. What's new archive

  236.  What's new in version 2.23
  237.  What's new in version 2.22
  238.  What's new in version 2.21
  239.  What's new in version 2.20
  240.  What's new in version 2.19
  241.  What's new in version 2.18
  242.  What's new in version 2.17
  243.  What's new in version 2.16
  244.  What's new in version 2.15
  245.  What's new in version 2.14
  246.  What's new in version 2.13
  247.  What's new in version 2.12
  248.  What's new in version 2.11
  249.  What's new in version 2.10
  250.  What's new in version 2.9
  251.  What's new in version 2.8
  252.  What's new in version 2.7
  253.  What's new in version 2.6
  254.  What's new in version 2.5
  255.  What's new in version 2.4
  256.  What's new in version 2.3
  257.  What's new in version 2.2
  258.  What's new in version 2.1
  259.  What's new in version 2.0
  260.  What's new in version 1.3
  261.  What's new in version 1.2
  262.  What's new in version 1.1

Create a controller widget

This topic explains how to create a sidebar controller widget in Web AppBuilder. The sidebar controller vertically displays and controls other widgets defined in widgetPool. As a result, it does not have a logo, title, or link.

Create a controller widget folder structure

Steps:
  1. Open the DemoTheme folder at ~/client/stemapp/themes.
  2. Create a new folder and name it widgets if a widgets folder does not already exist.
  3. Copy the Demo widget folder from ~/client/stemapp/widgets/samplewidgets to the widgets folder and change its name to SidebarController. The widgets folder should look similar to the following:
    widgets folder
    NoteNote:

    The Demo folder provides all the required files and folders to create a custom widget.

  4. Update the general information for the SidebarController widget, such as its name and label in the manifest.json and nls/string.js files as needed.
  5. In the manifest.json file, add the following properties to inform Web AppBuilder that this is a controller widget:
    "properties": {
        "inPanel": false,
        "isController": true,
        "isThemeWidget": true
      }
    

Add SidebarController to the default layout

Steps:
  1. Open the config.json file from the ~/client/stemapp/themes/DemoTheme/layouts/default folder.
  2. Add the following configuration to the widgets array in the widgetOnScreen section:
    {
          "uri": "themes/DemoTheme/widgets/SidebarController/Widget",
          "position": {
            "left": 0,
            "top": 0,
            "bottom": 0,
            "width": 50,
            "relativeTo": "browser"
          }
        }
    

    The controller is placed at the left of the screen with a width of 50 pixels and a height of 100 percent of the browser window.

  3. Reposition other on-screen widgets and widget placeholders to make space for SidebarController by adding 60 pixels to their left values. For example, the left value of the Scalebar widget is increased from 25 to 85.
    {
          "uri": "widgets/Scalebar/Widget",
          "position": {
            "left": 85,
            "bottom": 25
          }
        }
    
  4. Reload the Web AppBuilder app, and choose DemoTheme.

    The SidebarController widget is added as shown below.

    SidebarController widget

    The SidebarController widget still displays the sample content from the Demo widget, and there are no styles added yet.

    In addition, the controller configuration option is available as shown below.

    Configuration option

Customize the controller template

The widget.html file contains the HTML structure of the controller. In this section, you'll remove any sample code from the Demo widget and add a new dom element to contain widget icons that will be added when the app loads.

Steps:
  1. Open widget.html in the SidebarController folder and replace the content with the following:
    <div>
      <div data-dojo-attach-point="containerNode"></div>
    </div>
    
    CautionCaution:

    Don’t reload Web AppBuilder yet, because widget.js is still looking for the previous value of data-dojo-attach-point that was removed from the template.

Customize the controller widget

The widget.js file contains the basic widget life cycle methods and event handlers such as postCreate, startup, onSignIn, and onSignOut.

Steps:
  1. Set the controller’s base class name or names.

    Find the baseClass property and change its name to jimu-widget-sidebar-controller. This class name will help target the controller widget when you define its CSS styles. Add another class called jimu-main-background from the Jimu class framework to help create multiple styles.

    baseClass: 'jimu-widget-sidebar-controller jimu-main-background',
    
  2. Remove the following unused event handlers since they don't apply to the SidebarController widget:
    • onOpen
    • onClose
    • onMinimize
    • onMaximize
    • onSignIn
    • onSignOut
  3. Remove the following line of unused code from the startup method:
    this.mapIdNode.innerHTML = 'map id:' + this.map.id;
    
  4. Open Web AppBuilder, create a new app, and choose the Demo theme.

    The SidebarController widget appears as a red vertical bar on the left side of the screen as shown below.

    SidebarController widget in red

    NoteNote:

    The red background color is from the default style of DemoTheme that contains a CSS rule targeting the class name jimu-main-background.

  5. Read the app’s config file and populate the widget and group icons from widgetPool.
    1. Add jimu/PoolControllerMixin to the array of class declarations in widget.js.
      define(['dojo/_base/declare',
        'jimu/PoolControllerMixin',
        'jimu/BaseWidget'],
      function(declare, PoolControllerMixin, BaseWidget) {
        return declare([BaseWidget, PoolControllerMixin], {
          ...
        });
      });
      
    2. Declare a new widget property called allConfigs along with an empty array as its value.
      allConfigs: [],
      

      In the postCreate method, call getAllConfigs provided by PoolControlerMixin to get configurations for all the widgets and groups defined in the widget pool, and assign its returned value to this.allConfigs.

      this.allConfigs = this.getAllConfigs();
      
      NoteNote:

      See Create a controller widget for all the methods provided by PoolControlerMixin.

    3. Declare a new method called _createIconNode, and add logic to create HTML elements for widget and group icons in SidebarController.
      _createIconNode: function(iconConfig, targetNode) {
            var iconNode, iconImage;
            if(!targetNode) targetNode = this.containerNode;
      
            iconNode = document.createElement('DIV');
            iconNode.className = 'icon-node';
            if(iconConfig.icon) {
              iconImage = document.createElement('img');
              iconImage.src = iconConfig.icon;
            }
            if(iconConfig.label) {
              iconNode.title = iconConfig.label;
              iconImage.alt = iconConfig.label;
            }
      
            iconNode.appendChild(iconImage);
            targetNode.appendChild(iconNode);
      
            return iconNode;
          }
      
    4. Loop through this.allConfigs and call _createIconNode to add widget and group icons to the UI in the postCreate method.
      postCreate: function() {
        this.inherited(arguments);
        console.log('postCreate');
      
        this.allConfigs = this.getAllConfigs();
        for(var i = 0; i < this.allConfigs.length; i++) {
          this._createIconNode(this.allConfigs[i]);
        }
      },
      
    5. Restart Web AppBuilder, choose Demo theme, and add a few widgets and groups to SidebarController. The icons of widgets and groups display in the sidebar as shown below.
      Icons of widgets and groups display in SidebarController
  6. Display a widget in a panel when the widget icon is clicked.

    jimu/PoolControllerMixin provides an instance of jimu/PanelManger that can be accessed in widget.js by this.panelManager. When you pass a widget’s configuration in the showPanel method, the widget displays in the panel configured in the widget pool, or in the Jimu’s default on-screen panel.

    1. Declare a new method in widget.js and add logic to open a widget in a panel.
      _showWidgetContent: function(iconConfig) {
        this.panelManager.showPanel(iconConfig);
      }
      
      NoteNote:

      The above code does not support off-panel widgets when the inPanel property is set to false. You can use the following code to access the content of an off-panel widget and manually add its dom node to the interface. Note, however, it does not support groups.

      if(!iconConfig.inPanel) {
        var self = this;
        this.widgetManager.loadWidget(iconConfig).then(function(widget) {
          // add code to display off-panel widgets here
          self.widgetManager.openWidget(widget);
        });
      }
      
    2. Attach the onclick event handler to icon nodes, and call _showWidgetContent to open the widget. In the _createIconNode method, add the following code before return iconNode:
      var self = this;
      this.own(on(iconNode, 'click', function() {
        self._showWidgetContent(iconConfig);
      }));
      
      NoteNote:

      Make sure that the dojo/on class has been declared at the beginning of the file.

    3. Use SimpleBorderPanel as the default panel in widgetPool. Open the config.json file in the default layout folder, and add the following code after the map section:
      "widgetPool": {
          "panel": {
            "uri": "themes/DemoTheme/panels/SimpleBorderPanel/Panel",
            "position": {
              "top": 0,
              "left": 50,
              "bottom": 0,
              "width": 300
            }
          }
        }
      

      The panel will be placed next to the SidebarController widget when opened.

    4. Add functionality to close an open panel when a different icon is clicked.
      1. Add a new widget property called openedWidgetId to store the current open widget ID.
        openedWidgetId: '',
        
      2. In the _showWidgetContent method, add code to close an open panel, and update openedWidgetId.
        _showWidgetContent: function(iconConfig) {
          if(this.openedWidgetId) {
            this.panelManager.closePanel(this.openedWidgetId + '_panel');
          }
          this.panelManager.showPanel(iconConfig);
          this.openedWidgetId = iconConfig.id;
        }
        
  7. Create a new app in Web AppBuilder and choose Demo theme. Click the legend widget.

    The panel displays next to the sidebar.

    The panel displays

  8. Add or remove the active state class name to or from icon nodes.
    1. Include dojo/dom-class and dojo/query in the array of class declarations.
      define(['dojo/_base/declare',
          'dojo/on',
          'dojo/query',
          'dojo/dom-class',
          'jimu/PoolControllerMixin',
          'jimu/BaseWidget'
        ],
        function(declare, on, query, domClass, PoolControllerMixin, BaseWidget) {
      ...
      
    2. Add the jimu-state-active class name to the selected icon node in the icon node’s onclick event handler.
      this.own(on(iconNode, 'click', function() {
        domClass.add(this, 'jimu-state-active');
        self._showWidgetContent(iconConfig);
      }));
      
    3. Remove the jimu-state-active class name from any existing icon node.
      this.own(on(iconNode, 'click', function() {
        query('.jimu-state-active', self.domNode).removeClass('jimu-state-active');
        domClass.add(this, 'jimu-state-active');
        self._showWidgetContent(iconConfig);
      }));
      
  9. Close an open panel when its active icon node is clicked.
    1. Add a new widget property called activeIconNode.
      activeIconNode: null,
      
    2. In the icon node’s onclick event handler, assign the currently selected icon node to activeIconNode.
      this.own(on(iconNode, 'click', function() {
        query('.jimu-state-active', self.domNode).removeClass('jimu-state-active');
        domClass.add(this, 'jimu-state-active');
        self._showWidgetContent(iconConfig);
        self.activeIconNode = this;
      }));
      
    3. Add logic to check whether the currently clicked node is the active icon node, and close its panel if true.
      this.own(on(iconNode, 'click', function() {
        query('.jimu-state-active', self.domNode).removeClass('jimu-state-active');
        if(self.activeIconNode === this) {
          self.panelManager.closePanel(iconConfig.id + '_panel');
          self.activeIconNode = null;
          return;
        }
        domClass.add(this, 'jimu-state-active');
        self._showWidgetContent(iconConfig);
        self.activeIconNode = this;
      }));
      
  10. Remove the active state from an icon node when its panel is closed
    1. Add callback functions to this.panelManager.showPanel.
      _showWidgetContent: function (iconConfig) {
        if (this.openedWidgetId) {
          this.panelManager.closePanel(this.openedWidgetId + '_panel');
        }
        var self = this;
        this.panelManager.showPanel(iconConfig).then(function (widget) {
          // the panel displays successfully
        }, function (err) {
          // the panel failed to display
        });
        this.openedWidgetId = iconConfig.id;
      },
      
    2. In the success callback function, add an event handler to the widget’s onClose event to remove the active state from the selected icon node.
      _showWidgetContent: function (iconConfig) {
        if (this.openedWidgetId) {
          this.panelManager.closePanel(this.openedWidgetId + '_panel');
        }
        var self = this;
        this.panelManager.showPanel(iconConfig).then(function (widget) {
          // the panel displays successfully
          self.own(on.once(widget, 'close', function () {
            domClass.remove(self.activeIconNode, 'jimu-state-active');
            self.activeIconNode = null;
          }));
        }, function (err) {
          // the panel failed to display
        });
        this.openedWidgetId = iconConfig.id;
      },
      
      NoteNote:

      The above code removes the jimu-state-active class from the currently selected icon node, which is the same icon node that opens the panel currently displaying. This may not be the case if multiple panels can be opened at the same time, such as with the Launchpad theme.

  11. Configure the widget to open automatically when the app starts.

    A widget can open automatically when the app loads when openAtStart is true. You want this functionality with SidebarController.

    1. Insert the following code after the icon node creation in the _createIconNode method:
      _createIconNode: function(iconConfig, targetNode) {
        ...
       targetNode.appendChild(iconNode);
        // check if the widget is set to open at start
        if (iconConfig.openAtStart) {
          this.activeIconNode = iconNode;
          domClass.add(iconNode, 'jimu-state-active');
          this._showWidgetContent(iconConfig);
        }
        ...
      },
      
    2. Restart Web AppBuilder. Set a widget in the controller to open at start by turning on the dot to dark green. Refresh the page, and the widget should automatically open when the app loads.
  12. Handle group icons.

    Different controllers handle widget groups and opening their widgets differently. In this example, widgets in a group will have the following in common:

    • Their icons will be wrapped in a ToolTip container.
    • When a widget in a group is clicked, the icon of the group will be replaced by the currently selected widget icon, and the SimpleBorderPanel is displayed with its content.
    1. Declare a new widget property called groupTooltips along with an empty object as its value. This property will store all ToolTips created for the widget group.
      groupTooltips: {},
      
    2. Create a new method called _isGroupIcon to check whether an icon is a group icon.
      _isGroupIcon: function(iconConfig) {
        return iconConfig.widgets && iconConfig.widgets.length > 1;
      }
      
    3. In the _createIconNode method, after the if (iconConfig.openAtStart) {} statement, add a new if statement to check whether the current icon is a group icon. If it is, create a new ToolTip, loop through the widget icons and add them to the ToolTip. Then store the ToolTip in the this. groupTooltips array
      _createIconNode: function(iconConfig, targetNode) {
        ...
        if (iconConfig.openAtStart) {...}
        // check if the icon is a group icon
        if(this._isGroupIcon(iconConfig)) {
          // if group's tooltip has not been created yet
          if(!this.groupTooltips[iconConfig.id]) {
            // create group tooltip and its content
           var groupTooltip = document.createElement('div');
            groupTooltip.className = 'group-tooltip';
            document.body.appendChild(groupTooltip);
            for(var i = 0; i < iconConfig.widgets.length; i++) {
              this._createIconNode(iconConfig.widgets[i], groupTooltip);
            }
            this.groupTooltips[iconConfig.id] = groupTooltip;
          }
        }
        ...
      },
      
    4. In the icon node’s onclick event handler, add additional logic to handle clicking a group icon.
      this.own(on(iconNode, 'click', function() {
        // remove active state from any icon node
        query('.jimu-state-active', self.domNode).removeClass('jimu-state-active');
        // close panel
        self.panelManager.closePanel(self.openedWidgetId + '_panel');
        // close group tooltips
        query('.group-tooltip').removeClass('show');
        // if clicked on an active icon node
        if(self.activeIconNode === this) {
          self.activeIconNode = null;
          return;
        }
        // clicking on a group icon
        if (self._isGroupIcon(iconConfig)) {
          self.openedWidgetId = null;
          domClass.add(self.groupTooltips[iconConfig.id], 'show');
        } else { // clicking on a widget icon
          // show panel
          self._showWidgetContent(iconConfig);
        }
          domClass.add(this, 'jimu-state-active');
          self.activeIconNode = this;
      }));
      
    5. Reposition the ToolTip when opened.
      1. Add the following styles to the SidebarController widget’s style.css files:
        .group-tooltip {
          display: none;
          position: absolute;
          background: #000;
        }
        .group-tooltip.show {
          display: block;
        }
        .group-tooltip .icon-node {
          display: inline-block;
        }
        
      2. Add a new method called _positionTooltip.
        _positionTooltip: function(tooltip, iconNode) {
          var iconBoundingRect = iconNode.getBoundingClientRect();
          tooltip.style.top = iconBoundingRect.top + 'px';
          tooltip.style.left = (iconBoundingRect.width || iconNode.clientWidth) + 'px';
        }
        
      3. Call the _positionTooltip method when a group icon is clicked.
        this.own(on(iconNode, 'click', function() {
          // clicking on a group icon
          if (self._isGroupIcon(iconConfig)) {
            self.openedWidgetId = null;
            self._positionTooltip(self.groupTooltips[iconConfig.id], this);
            domClass.add(self.groupTooltips[iconConfig.id], 'show');
          } else { // clicking on a widget icon
            ...
          }
          ...
        }));
        

    Your result should look similar to the following:

    Group icon

Add CSS styles

You can add the following CSS styles to the SidebarController widget:

  • style.css from the~/client/stemapp/themes/DemoTheme/widgets/SidebarController/css folder—The widget-specific style regardless of the theme or style.
  • common.css from the ~/client/stemapp/themes/DemoTheme folder—The common CSS styles apply to all styles in the Demo theme.
  • style.css from the path/to/WAB/client/stemapp/themes/DemoTheme/styles/{styleName} folder—The CSS styles are only applied when a certain style is selected.

For the SidebarController widget, the CSS styles will be added to the following:

  • The style.css file in the SidebarController folder—Contains only the minimal styles for the layout purpose.
  • The style.css in the Default style folder—Defines the details of the widget UI.
Steps:
  1. Open style.css from ~/client/stemapp/themes/DemoTheme/widgets/SidebarController/css, and replace the content with the following CSS rules:
    .icon-node {
      min-width: 50px;
      padding: 12px 5px;
      text-align: center;
    }
    .icon-node img {
      width: 80%;
      max-width: 24px;
    }
    .icon-node.jimu-state-active {
      background: #000;
    }
    .group-tooltip {
      display: none;
      position: absolute;
      background: #000;
    }
    .group-tooltip.show {
      display: block;
    }
    .group-tooltip .icon-node {
      display: inline-block;
    }
    

    Your result should look similar to the following:

    CSS rules
  2. Replace the styles with the following code in style.css from ~\client/stemapp/themes/DemoTheme/styles/default:
    .jimu-main-background {
      background-color: #323232;
    }
    
    .icon-node img {
      opacity: 0.65;
    }
    
    .icon-node.jimu-state-active {
      background-color: #111;
      position: relative;
    }
    
    .icon-node.jimu-state-active:before {
      content: "";
      display: block;
      height: 100%;
      width: 3px;
      background-color: #2196F3;
      position: absolute;
      left: 0;
      top: 0;
    }
    
    .icon-node.jimu-state-active:after {
      content: "";
      display: block;
      height: 0;
      width: 0;
      border-width: 4px 4px 4px 0;
      border-style: solid;
      border-color: transparent #fff transparent transparent;
      margin-top: -4px;
      position: absolute;
      right: 0;
      top: 50%;
    }
    
    .icon-node:hover img,
    .icon-node.jimu-state-active img,
    .group-tooltip .icon-node.jimu-state-active:hover img {
      opacity: 1;
    }
    
    .group-tooltip {
      background-color: #111;
      border-left: 2px solid #fff;
    }
    
    .group-tooltip .icon-node.jimu-state-active:before,
    .group-tooltip .icon-node.jimu-state-active:after {
      content: none;
    }
    
    .group-tooltip .icon-node.jimu-state-active img {
      opacity: 0.65;
    }
    
    .jimu-border-panel {
      border: 0;
      -webkit-box-shadow: 1px 0 0.5px rgba(0,0,0,0.2), 1px 0 2px rgba(0,0,0,0.07);
      box-shadow: 1px 0 0.5px rgba(0,0,0,0.2), 1px 0 2px rgba(0,0,0,0.07);
    }
    

    Your result should look similar to the following:

    Final result