Passed
Push — master ( 1713a6...bcb549 )
by Peter
02:05
created

FuzzEd/static/script/faulttree/analytical_menus.js   F

Complexity

Total Complexity 90
Complexity/F 1.53

Size

Lines of Code 998
Function Count 59

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 0
dl 0
loc 998
rs 2.1818
c 1
b 0
f 0
wmc 90
nc 128
mnd 3
bc 87
fnc 59
bpm 1.4745
cpm 1.5254
noi 36

How to fix   Complexity   

Complexity

Complex classes like FuzzEd/static/script/faulttree/analytical_menus.js often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
define(['factory', 'canvas', 'menus', 'faulttree/config', 'alerts', 'datatables', 'datatables-api', 'faulttree/node_group', 'highcharts', 'jquery-ui'],
2
function(Factory, Canvas, Menus, FaulttreeConfig, Alerts, DataTables) {
3
    
4
    /**
5
     * Class: CutsetsMenu
6
     *      A menu for displaying a list of minimal cutsets calculated for the edited graph. The nodes that belong to a
7
     *      cutset become highlighted when hovering over the corresponding entry in the cutsets menu.
8
     *
9
     * Extends: <Base::Menus::Menu>
10
     */
11
    var CutsetsMenu = Menus.Menu.extend({
12
        /**
13
         * Group: Members
14
         *      {Editor} _editor - <Faulttree::Editor> the editor that owns this menu.
15
         */
16
        _editor: undefined,
17
18
        /**
19
         * Group: Initialization
20
         */
21
22
        /**
23
         * Constructor: init
24
         *      Sets up the menu.
25
         *
26
         * Parameters:
27
         *      {Editor} _editor - <Faulttree::Editor> the editor that owns this menu.
28
         */
29
        init: function(editor) {
30
            this._super();
31
            this._editor = editor;
32
        },
33
34
        /**
35
         * Method: _setupContainer
36
         *      Sets up the DOM container element for this menu and appends it to the DOM.
37
         *
38
         * Returns:
39
         *      A {jQuery} set holding the container.
40
         */
41
        _setupContainer: function() {
42
            return jQuery(
43
                '<div id="' + Factory.getModule('Config').IDs.CUTSETS_MENU + '" class="menu" header="Cutsets">\
44
                    <div class="menu-controls">\
45
                       <i class="menu-minimize"></i>\
46
                       <i class="menu-close">   </i>\
47
                    </div>\
48
                    <ul class="nav-list unstyled"></ul>\
49
                </div>'
50
            ).appendTo(jQuery('#' + Factory.getModule('Config').IDs.CONTENT));
51
        },
52
53
        /**
54
         * Group: Actions
55
         */
56
57
        /**
58
         * Method: show
59
         *      Display the given cutsets in the menu and make the menu visible.
60
         *
61
         * Parameters:
62
         *      {Array[Object]} cutsets - A list of cutsets calculated by the backend.
63
         *
64
         *  Returns:
65
         *      This{<Menu>} instance for chaining.
66
         */
67
        show: function(cutsets) {
68
            if (typeof cutsets === 'undefined') {
69
                this.container.show();
70
                return this;
71
            }
72
73
            var listElement = this.container.find('ul').empty();
74
75
            _.each(cutsets, function(cutset) {
76
                var nodeIDs = cutset['nodes'];
77
                var nodes = _.map(nodeIDs, function(id) {
78
                    return this._editor.graph.getNodeById(id);
79
                }.bind(this));
80
                var nodeNames = _.map(nodes, function(node) {
81
                    return node.name;
82
                });
83
84
                // create list entry for the menu
85
                var entry = jQuery('<li><a href="#">' + nodeNames.join(', ') + '</a></li>');
86
87
                // highlight the corresponding nodes on hover
88
                entry.hover(
89
                    // in
90
                    function() {
91
                        var disable = _.difference(this._editor.graph.getNodes(), nodes);
92
                        _.invoke(disable, 'disable');
93
                        _.invoke(nodes, 'highlight');
94
                    }.bind(this),
95
96
                    // out
97
                    function() {
98
                        var enable = _.difference(this._editor.graph.getNodes(), nodes);
99
                        _.invoke(enable, 'enable');
100
                        _.invoke(nodes, 'unhighlight');
101
                    }.bind(this)
102
                );
103
104
                listElement.append(entry);
105
            }.bind(this));
106
107
            this._super();
108
            return this;
109
        }
110
    });
111
    
112
   /*
113
    * Abstract Class: AnalysisResultMenu
114
    *      Base class for menus that display the results of a analysis performed by the backend. It contains a chart
115
    *      (implemented with Highcharts) and a table area (using DataTables).
116
    *
117
    */
118
   var AnalysisResultMenu = Menus.Menu.extend({
119
       /**
120
        * Group: Members 
121
        *      {<Editor>}        _editor               - The <Editor> instance.
122
        *      {<Job>}           _job                  - <Job> instance of the backend job that is responsible for
123
        *                                                calculating the probability.
124
        *      {jQuery Selector} _graphIssuesContainer - Display
125
        *      {jQuery Selector} _chartContainer       - jQuery reference to the chart's container.
126
        *      {jQuery Selector} _tableContainer       - jQuery reference to the table's container.
127
        *      {Highchart}       _chart                - The Highchart instance displaying the result.
128
        *      {DataTables}      _table                - The DataTables instance displaying the result.
129
        *      {jQuery Selector} _staticInfoContainer  - Static result information from the backend, directly shown.
130
        *      {Object}          _configNodeMap        - A mapping of the configuration ID to its node set.
131
        *      {Object}          _configNodeMap        - A mapping of the configuration ID to its edge set.
132
        *      {Object}          _redundancyNodeMap    - A mapping of the configuration ID to the nodes' N-values
133
        *      {Object}          _configMetaDataCached - A dictionary indicating if choice meta data for a specific configuration is cached.
134
135
        */
136
       _editor:               undefined,
137
       _job:                  undefined,
138
       _graphIssuesContainer: undefined,
139
       _chartContainer:       undefined,
140
       _tableContainer:       undefined,
141
       _chart:                undefined,
142
       _table:                undefined,
143
       _staticInfoContainer:  undefined,
144
       _configNodeMap:        {},
145
       _configEdgeMap:        {},
146
       _redundancyNodeMap:    {},
147
       _configMetaDataCached: {},
148
       
149
150
       /**
151
        * Group: Initialization
152
        */
153
154
       /**
155
        * Constructor: init
156
        *      Sets up the menu.
157
        */
158
       init: function(editor) {
159
           this._super();
160
           this._editor = editor;
161
           this._graphIssuesContainer = this.container.find('.graph_issues');
162
           this._chartContainer       = this.container.find('.chart');
163
           this._tableContainer       = this.container.find('.table_container');
164
           this._staticInfoContainer  = this.container.find('.static_info');
165
       },
166
167
       /**
168
        * Group: Actions
169
        */
170
171
       /**
172
        * Method: show
173
        *      Display the given job status its results.
174
        *
175
        * Parameters:
176
        *      {<Job>} job - The backend job that calculates the probability of the top event.
177
        *
178
        * Returns:
179
        *      This {<AnalysisResultMenu>} instance for chaining.
180
        */
181
       show: function(job) {
182
           // clear the content
183
           this._clear();
184
185
           job.successCallback  = this._evaluateResult.bind(this);
186
           job.updateCallback   = this._displayProgress.bind(this);
187
           job.errorCallback    = this._displayJobError.bind(this);
188
           job.notFoundCallback = this._displayJobError.bind(this);
189
           job.queryInterval    = 500;
190
191
           this._job = job;
192
           job.progressMessage = Factory.getModule('Config').ProgressIndicator.CALCULATING_MESSAGE;
193
           job.start();
194
195
           this._super();
196
           return this;
197
       },
198
199
       /**
200
        * Method: hide
201
        *      Hide the menu and clear all its content. Also stops querying for job results.
202
        *
203
        * Returns:
204
        *      This {<AnalysisResultMenu>} instance for chaining.
205
        */
206
       hide: function() {
207
           this._super();
208
           // cancel query job
209
           this._job.cancel();
210
           // clear content
211
           this._clear();
212
213
           return this;
214
       },
215
216
       /**
217
        * Method: _clear
218
        *      Clear the content of the menu and cancel any running jobs.
219
        *
220
        * Returns:
221
        *      This {<AnalysisResultMenu>} instance for chaining.
222
        */
223
       _clear: function() {
224
           if (typeof this._job !== 'undefined') this._job.cancel();
225
           
226
           // reset height of the chart container (which is set after resizing event)
227
           this._chartContainer.height('');
228
           
229
           // reset height in case it was set during grid creation
230
           this._tableContainer.css('min-height', '');
231
           
232
           // reset container width (which is set after initalisation of DataTables)
233
           this.container.css('width','');
234
           
235
           this._graphIssuesContainer.empty();
236
           this._chartContainer.empty();
237
           this._tableContainer.empty();
238
           this._chart = null;
239
           this._table = null;
240
           this._staticInfoContainer.empty();
241
           this._configNodeMap        = {};
242
           this._redundancyNodeMap    = {};
243
           this._configEdgeMap        = {};
244
           this._configMetaDataCached = {};
245
246
           return this;
247
       },
248
       
249
       /**
250
        * Group: Accessors
251
        */
252
      
253
       /**
254
        *  Abstract Method: _progressMessage
255
        *      Should compute the message that is displayed while the backend calculation is pending.
256
        *
257
        *  Returns:
258
        *      A {String} with the message. May contain HTML.
259
        */
260
       _progressMessage: function() {
261
           throw new SubclassResponsibility();
262
       },
263
       
264
       /**
265
        *  Abstract Method: _menuHeader
266
        *      Computes the header of the menu.
267
        *
268
        *  Returns:
269
        *      A {String} which is the header of the menu.
270
        */
271
       _menuHeader: function() {
272
           throw new SubclassResponsibility();
273
       },
274
       
275
       /**
276
        * Method: _containerID
277
        *      Computes the HTML ID of the container.
278
        *
279
        * Returns:
280
        *      A {String} which is the ID of the Container.
281
        */
282
       _containerID: function() {
283
           throw new SubclassResponsibility();
284
       },
285
286
       /**
287
        *  Group: Setup
288
        */
289
290
       /**
291
        * Abstract Method: _setupContainer
292
        *      Sets up the DOM container element for this menu and appends it to the DOM.
293
        *
294
        * Returns:
295
        *      A jQuery object of the container.
296
        */
297
       _setupContainer: function() {
298
           return jQuery(
299
               '<div id="' + this._containerID()  + '" class="menu probabillity_menu" header="'+ this._menuHeader() +'">\
300
                   <div class="menu-controls">\
301
                       <i class="menu-minimize"></i>\
302
                       <i class="menu-close"></i>\
303
                   </div>\
304
                   <div class="graph_issues"></div>\
305
                   <div class="chart"></div>\
306
                   <div class="table_container content"></div>\
307
                   <div class="static_info"></div>\
308
               </div>'
309
           ).appendTo(jQuery('#' + Factory.getModule('Config').IDs.CONTENT));
310
       },
311
312
       /**
313
        * Method: setupResizing
314
        *      Enables this menu to be resizable and therefore to enlarge or shrink the calculated analysis results.
315
        *      Any subclass has to ensure that its particular outcomes adhere to this behaviour.
316
        *
317
        * Returns:
318
        *      This {<AnalysisResultMenu>} for chaining.
319
        */
320
       _setupResizing: function() {
321
           this.container.resizable({
322
               minHeight: this.container.outerHeight(), // use current height as minimum
323
               minWidth : this.container.outerWidth(),
324
               resize: function(event, ui) {
325
326
                   if (this._chart != null) {
327
                       
328
                       // fit all available space with chart    
329
                       this._chartContainer.height(this.container.height() - 
330
                       this._graphIssuesContainer.height() - 
331
                       this._tableContainer.height() -
332
                       this._staticInfoContainer.height());
333
                       
334
                       this._chart.setSize(
335
                           this._chartContainer.width(),
336
                           this._chartContainer.height(),
337
                           false
338
                       );
339
                   }   
340
               }.bind(this),
341
               stop: function(event, ui) {
342
                   // set container height to auto after resizing (because of collapsing elements)
343
                   this.container.css('height', 'auto');
344
                   
345
               }.bind(this)
346
347
           });
348
       },
349
350
       /**
351
        * Group: Evaluation
352
        */
353
354
       /**
355
        * Method: _evaluateResult
356
        *      Evaluates the job result. Either displays the analysis results or the returned error message.
357
        *
358
        * Parameters:
359
        *      {String} data           - Data returned from the backend containing global graph issues,
360
        *                                configuration data for Highcharts, as well as column definitions for DataTables.
361
        *
362
        *      {String} job_result_url - URL for accessing the job result (configuration data).
363
        *
364
        */
365
       _evaluateResult: function(data, job_result_url) {
366
           
367
           data   = jQuery.parseJSON(data);
368
           var issues = data.issues;
369
           var static_info = data.static_info;
370
           
371
           if (issues){
372
               if (_.size(issues.errors) > 0 || _.size(issues.warnings) > 0){
373
                   this._displayGraphIssues(issues.errors, issues.warnings);
374
               }
375
           }
376
377
           if (static_info){
378
              this._displayStaticInfo(static_info);
379
           }
380
               
381
           // remove progress bar
382
           this._chartContainer.empty();
383
           var axisTitles = data.axis_titles;
384
           this._initializeHighcharts(axisTitles);
385
          
386
           
387
           // display results within a table
388
           var columns = data.columns;
389
           this._displayResultWithDataTables(columns, job_result_url);
390
           
391
           
392
       },
393
394
       /**
395
        * Method: _chartTooltipFormatter
396
        *      This function is used to format the tooltip that appears when hovering over a data point in the chart.
397
        *      The scope object ('this') contains the x and y value of the corresponding point.
398
        *
399
        *  Returns:
400
        *      A {String} that is displayed inside the tooltip. It may HTML.
401
        */
402
       _chartTooltipFormatter: function() {
403
           return '<b>' + this.series.name + '</b><br/>' +
404
                  '<i>Probability:</i> <b>' + Highcharts.numberFormat(this.x, 5) + '</b><br/>' +
405
                  '<i>Membership Value:</i> <b>' + Highcharts.numberFormat(this.y, 2) + '</b>';
406
       },
407
408
       /**
409
        * Group: Conversion
410
        */
411
412
       /**
413
        *  Method: _collectNodesAndEdgesForConfiguration
414
        *      Traverses the graph and collects all nodes and edges which are part of the configuration defined by the
415
        *      given set of choices. Remember those entities in the <_configNodeMap> and <_configEdgeMap> fields
416
        *      using the given configID.
417
        *
418
        * Parameters:
419
        *      {String}         configID - The id of the configuration that is used to store the nodes and edges.
420
        *      {Array[Object]}  choices  - A map from node IDs to choice objects (with 'type' and 'value') used to
421
        *                                  filter the graph entities.
422
        *      {<Node>}         topNode  - [optional] The top node of the graph. Used for recursion. Defaults to the
423
        *                                  top event.
424
        *
425
        * Returns:
426
        *      This {<AnalysisResultMenu>} for chaining.
427
        */
428
       _collectNodesAndEdgesForConfiguration: function(configID, choices, topNode) {
429
           // start from top event if not further
430
           if (typeof topNode === 'undefined') topNode = this._editor.graph.getNodeById(0);
431
           // get children filtered by choice
432
           var children = topNode.getChildren();
433
           var nodes = [topNode];
434
           var edges = topNode.incomingEdges;
435
436
           if (topNode.id in choices) {
437
               var choice = choices[topNode.id];
438
439
               switch (choice['type']) {
440
                   case 'InclusionChoice':
441
                       // if this node is not included (optional) ignore it and its children
442
                       if (!choice['included']) {
443
                           children = [];
444
                           nodes = [];
445
                           edges = [];
446
                       }
447
                       break;
448
449
                   case 'FeatureChoice':
450
                       // only pick the chosen child of a feature variation point
451
                       children = [_.find(children, function(node) {return node.id == choice['featureId']})];
452
                       break;
453
454
                   case 'RedundancyChoice':
455
                       // do not highlight this node and its children if no child was chosen
456
                       if (choice['n'] == 0) {
457
                           nodes = [];
458
                           children = [];
459
                           edges = [];
460
                       }
461
                       break;
462
               }
463
           }
464
465
           this._configNodeMap[configID] = nodes.concat(this._configNodeMap[configID] || []);
466
           this._configEdgeMap[configID] = edges.concat(this._configEdgeMap[configID] || []);
467
468
           // recursion
469
           _.each(children, function(child) {
470
               this._collectNodesAndEdgesForConfiguration(configID, choices, child);
471
           }.bind(this));
472
473
           return this;
474
       },
475
476
       /**
477
        * Group: Display
478
        */
479
480
       /**
481
        * Method: _displayProgress
482
        *      Display the job's progress in the menu's body.
483
        *
484
        * Parameters:
485
        *      {Object} data - Data returned from the backend with information about the job's progress.
486
        *
487
        * Returns:
488
        *      This {<AnalysisResultMenu>} for chaining.
489
        */
490
       _displayProgress: function(data) {
491
           if (this._chartContainer.find('.progress').length > 0) return this;
492
493
           var progressBar = jQuery(
494
               '<div style="text-align: center;">' +
495
                   '<p>' + this._progressMessage() + '</p>' +
496
                   '<div class="progress progress-striped active">' +
497
                       '<div class="progress-bar" role="progressbar" style="width: 100%;"></div>' +
498
                   '</div>' +
499
               '</div>');
500
501
           this._chartContainer.empty().append(progressBar);
502
           this._tableContainer.empty();
503
504
           return this;
505
       },
506
       
507
       /**
508
        * Method: _initializeHighcharts
509
        *      Intialize highcharts with Axis definitions.
510
        *
511
        * Parameters:
512
        *    {Array[Object]} axis_defititions  - A set of definitions used in Highcharts initialisation.
513
        *
514
        * Returns:
515
        *      This {<AnalysisResultMenu>} for chaining.
516
        */
517
       _initializeHighcharts: function(axis_definitions) { 
518
           var self = this;
519
                       
520
           this._chart = new Highcharts.Chart({
521
               chart: {
522
                   renderTo: this._chartContainer[0],
523
                   type:     'line',
524
                   height:   Factory.getModule('Config').AnalysisMenu.HIGHCHARTS_MIN_HEIGHT,
525
526
               },
527
               title: {
528
                   text: null
529
               },
530
               credits: {
531
                   style: {
532
                       fontSize: Factory.getModule('Config').AnalysisMenu.HIGHCHARTS_CREDIT_LABEL_SIZE
533
                   }
534
               },
535
               xAxis: axis_definitions.xAxis,
536
               yAxis: axis_definitions.yAxis,
537
               tooltip: {
538
                   formatter: this._chartTooltipFormatter
539
               },
540
               plotOptions: {
541
                   series: {
542
                       marker: {
543
                           radius: Factory.getModule('Config').AnalysisMenu.HIGHCHARTS_POINT_RADIUS
544
                       },
545
                       events: {
546
                           mouseOver : function () {
547
                               var config_id = this.name
548
                               var row = self._table.fnFindCellRowNodes(config_id, 0);
549
                               jQuery(row).addClass('tr_hover');
550
                           },
551
                           mouseOut  : function () {
552
                               var config_id = this.name
553
                               var row = self._table.fnFindCellRowNodes(config_id, 0);
554
                               jQuery(row).removeClass('tr_hover');
555
                           },
556
                       }
557
                   }
558
               },
559
               data: []
560
           });
561
           
562
           return this;
563
       },
564
       
565
       /**
566
        * Method: _displaySeriesWithHighcharts
567
        *      Draw series within the Highcharts diagram. 
568
        *
569
        * Parameters:
570
        *    {Array[Object]} defititions  - A set of one or more data series to display in the Highchart.
571
        *
572
        * Returns:
573
        *      This {<AnalysisResultMenu>} for chaining.
574
        */
575
       _displaySeriesWithHighcharts: function(data){
576
           // remove series from the last draw
577
           while(this._chart.series.length > 0){
578
               this._chart.series[0].remove(false);
579
           }
580
           
581
           var series = [];
582
           _.each(data, function(cutset, name) {
583
               series.push({
584
                   name: name,
585
                   data: cutset
586
               });
587
           });
588
           
589
           // draw series
590
           _.each(series, function(config){
591
               this._chart.addSeries(config, false, false)
592
           }.bind(this));
593
           
594
           this._chart.redraw();
595
           
596
           return this;
597
       },
598
             
599
       /**
600
        * Method: _displayResultWithDataTables
601
        *      Display the job's result with DataTables Plugin. Configuration Issues are printed inside the table as collapsed row. 
602
        *
603
        * Parameters:
604
        *     {Array[Object]}  columns        - A set of columns that shall be displayed within the table.
605
        *     {String}         job_result_url - URL under which the server delivers configurations for a specific analysis result (using ajax and pagingation)
606
        *    
607
        *
608
        * Returns:
609
        *      This {<AnalysisResultMenu>} for chaining.
610
        */
611
       _displayResultWithDataTables: function(columns, job_result_url) {
612
           
613
           // clear container
614
           this._tableContainer.html('<table id="results_table" class="results_table table table-hover content"></table>');
615
616
           
617
           var collapse_column = {
618
                                   "class":          'details-control',
619
                                   "orderable":      false,
620
                                   "data":           null,
621
                                   "defaultContent": '',
622
                                   "bSortable":      false
623
                                 };
624
                                 
625
           columns.push(collapse_column);
626
           
627
           //formating function for displaying configuration warnings/errors
628
           var format = function (issues) {
629
                   
630
                   var errors   = issues['errors'] || [];
631
                   var warnings = issues['warnings'] || [];
632
                   
633
                   return this._displayIsussuesList(errors, warnings);
634
           }.bind(this);
635
           
636
           this._table = jQuery('#results_table').dataTable({
637
                           "bProcessing":   true,
638
                           "bFilter":       false,
639
                           "bServerSide":   true,
640
                           "sAjaxSource":   job_result_url,
641
                           "aoColumns":     columns,
642
                           "bLengthChange": false,
643
                           "iDisplayLength": Factory.getModule('Config').AnalysisMenu.RESULTS_TABLE_MAX_ROWS,
644
                           "fnDrawCallback": function(oSettings) {
645
                               
646
                               var serverData = oSettings['json'];
647
                               var totalRecords = serverData['iTotalRecords'];
648
                              
649
                               if(totalRecords < 2){
650
                                    // unbind sorting events if there are less than 2 rows
651
                                   this._tableContainer.find('th').removeClass().unbind('click.DT');    
652
                               }
653
                               
654
                               if(totalRecords <= Factory.getModule('Config').AnalysisMenu.RESULTS_TABLE_MAX_ROWS){
655
                                    // remove pagination elements if only one page is displayed
656
                                    this._tableContainer.find('div.dataTables_paginate').css('display', 'none');
657
                                    this._tableContainer.find('div.dataTables_info').css('display', 'none');
658
                               }
659
                               
660
                               // display points with highchart after table was rendered
661
                               var configurations = serverData['aaData'];
662
                               var chartData = {}; 
663
                                _.each(configurations, function(config) {
664
                                    var configID = config['id'] || '';
665
                                    
666
                                    // collect chart data if given
667
                                    if (typeof config['points'] !== 'undefined') {
668
                                        chartData[configID] = _.sortBy(config['points'], function(point){ return point[0] });
669
                                    }
670
                                }.bind(this));
671
                                
672
                                if (_.size(chartData) != 0) {
673
                                    this._displaySeriesWithHighcharts(chartData);
674
                                }
675
                                else{
676
                                    this._chart.setSize (0, 0, false);
677
                                }     
678
                               }.bind(this),
679
                           "fnRowCallback": function( nRow, aData, iDisplayIndex, iDisplayIndexFull) {
680
                               
681
                               // Callback is executed for each row (each row is one configuration)
682
                                               
683
                               var current_config = aData;
684
                               var configID = current_config['id'];
685
                                                   
686
                               if ('choices' in current_config ){
687
                                   var choices  = aData['choices'];
688
                                   
689
                                   // check if config meta data is alredy cached 
690
                                   if (this._configMetaDataCached[configID] !== true){
691
                                       // remember the nodes and edges involved in this config for later highlighting
692
                                       this._collectNodesAndEdgesForConfiguration(configID, choices);
693
                                   
694
                                   
695
                                       // remember the redundancy settings for this config for later highlighting
696
                                       this._redundancyNodeMap[configID] = {};
697
                                       _.each(choices, function(choice, node) {
698
                                           if (choice.type == 'RedundancyChoice') {
699
                                               this._redundancyNodeMap[configID][node] = choice['n'];
700
                                           }
701
                                       }.bind(this));
702
                                       
703
                                       this._configMetaDataCached[configID] = true;
704
                                   }
705
                                   
706
                                   jQuery(nRow).on("mouseover", function(){    
707
                                           this._highlightConfiguration(configID);                                                
708
                                    }.bind(this));
709
                                    
710
                                    
711
                                    jQuery(nRow).on("mouseleave", function(){    
712
                                           this._unhighlightConfiguration();                                                
713
                                    }.bind(this));
714
                               }
715
                               
716
                               /* Sample Configuration issues
717
                               if (iDisplayIndex == 0){
718
                                   current_config["issues"] = { "errors": [{"message": "map::at", "issueId": 0, "elementId": ""}]};
719
                               } else if (iDisplayIndex == 1){
720
                                   current_config["issues"] = { "warnings": [{"message": "Ignoring invalid redundancy configuration with k=-2 N=0", "issueId": 0, "elementId": "3"}]};
721
                               } else if (iDisplayIndex == 2){
722
                                    current_config["issues"] = { "errors": [{"message": "map::at", "issueId": 0, "elementId": ""},{"message": "error error error", "issueId": 0, "elementId": ""}, {"message": "another error", "issueId": 0, "elementId": ""}], "warnings": [{"message": "Ignoring invalid redundancy configuration with k=-2 N=0", "issueId": 0, "elementId": "3"}, {"message": "another warning", "issueId": 0, "elementId": "3"}] };
723
                               }*/
724
                               
725
                               if ('issues' in current_config){
726
                                   var issues = current_config['issues'];
727
                                   
728
                                   jQuery(nRow).find('td.details-control').append('<i class="fa fa-exclamation-triangle"></i>');
729
                                   // Add event listener for opening and closing details
730
                                   jQuery(nRow).on('click', function () {
731
                                       var tr  = jQuery(nRow);
732
                                       var row = this._table.api().row(tr);
733
734
                                       if ( row.child.isShown() ) {
735
                                           // This row is already open - close it
736
                                           row.child.hide();
737
                                       }
738
                                       else {
739
                                           // Open this row
740
                                           row.child(format(issues)).show();
741
                                       }
742
                                   }.bind(this));
743
                                   
744
                                   if ('errors' in issues){
745
                                       jQuery(nRow).addClass('danger');
746
                                   }
747
                                   else if ('warnings' in issues){
748
                                       jQuery(nRow).addClass('warning');
749
                                   }
750
                               }
751
                               else{
752
                                   // if row is not collapsable show default pointer
753
                                   jQuery(nRow).css('cursor', 'default');
754
                               } 
755
                           }.bind(this),
756
                                            
757
                           "fnInitComplete": function(oSettings, json) {  
758
                                   this._setupResizing();
759
                                   // set minumum height of grid as the height of the first draw of the grid
760
                                   this._tableContainer.css('min-height', this._tableContainer.height());
761
                                   // keep container width when switching the page (-> otherwise jumping width when switching)
762
                                   this.container.css('width', this.container.width());
763
                                   
764
                               }.bind(this)    
765
                           });
766
            
767
           return this;
768
       },
769
       
770
       /**
771
        *  Method: _highlightConfiguration
772
        *      Highlights all nodes, edges and n-values that are part of the given configuration on hover.
773
        *
774
        *  Parameters:
775
        *      {String} configID - The ID of the configuration that should be highlighted.
776
        *
777
        * Returns:
778
        *      This {<AnalysisResultMenu>} for chaining.
779
        */
780
       _highlightConfiguration: function(configID) {
781
           // prevents that node edge anchors are being displayed
782
           Canvas.container.addClass(Factory.getModule('Config').Classes.CANVAS_NOT_EDITABLE);
783
784
           // highlight nodes
785
           _.invoke(this._configNodeMap[configID], 'highlight');
786
           // highlight edges
787
           _.invoke(this._configEdgeMap[configID], 'highlight');
788
           // show redundancy values
789
           _.each(this._redundancyNodeMap[configID], function(value, nodeID) {
790
               var node = this._editor.graph.getNodeById(nodeID);
791
               node.showBadge('N=' + value, 'info');
792
           }.bind(this));
793
794
           return this;
795
       },
796
797
       /**
798
        *  Method: _unhighlightConfiguration
799
        *      Remove all hover highlights handler currently attached.
800
        *
801
        * Returns:
802
        *      This {<AnalysisResultMenu>} for chaining.
803
        */
804
       _unhighlightConfiguration: function() {
805
           // make the anchors visible again
806
           Canvas.container.removeClass(Factory.getModule('Config').Classes.CANVAS_NOT_EDITABLE);
807
808
           // unhighlight all nodes
809
           _.invoke(this._editor.graph.getNodes(), 'unhighlight');
810
           // unhighlight all edges
811
           _.invoke(this._editor.graph.getEdges(), 'unhighlight');
812
           // remove all badges
813
           _.invoke(this._editor.graph.getNodes(), 'hideBadge');
814
815
           return this;
816
       },
817
818
       /**
819
        * Method: _displayStaticInfo
820
              Display unparsed static information from the backend
821
        *
822
        * Returns:
823
        *      This {<AnalysisResultMenu>} for chaining.
824
        */
825
        _displayStaticInfo: function(static_info) {
826
           this._staticInfoContainer.append(jQuery('<p>'+static_info+'</p>'));
827
        },
828
829
       /**
830
        * Method: _displayGraphIssues
831
        *      Display all warnings/errors that are thrown during graph validation.
832
        *
833
        * Parameters:
834
        *      {Object} warnings - An array of warning objects.
835
        *      {Object} errors   - An array of error objects.
836
        *
837
        * Returns:
838
        *      This {<AnalysisResultMenu>} for chaining.
839
        */
840
       _displayGraphIssues: function(errors, warnings) {
841
842
           var num_errors   = _.size(errors);
843
           var num_warnings = _.size(warnings);
844
           
845
           var alert_container = jQuery('<div class="alert"><i class="fa fa-exclamation-triangle"></i></div>')
846
           
847
           if (num_errors > 0){
848
               alert_container.addClass('alert-error');                
849
           } else if(num_warnings >0){
850
               alert_container.addClass('alert-warning');  
851
           } else{
852
               alert_container.addClass('alert-success');  
853
           }
854
           
855
           var issues_heading   = jQuery('<a class="alert-link">\
856
                                           Errors: ' + num_errors + '&nbsp;&nbsp;&nbsp;\
857
                                           Warnings: ' + num_warnings +
858
                                         '</a>');
859
                                           
860
           var issues_details   = jQuery('<div class="collapse">' + this._displayIsussuesList(errors, warnings) + '</div>');
861
           
862
           alert_container.append(issues_heading).append(issues_details);
863
           
864
           // collapse error/warning messages details after clicking on the heading
865
           issues_heading.click(function(){
866
               issues_details.collapse('toggle');
867
           });
868
           
869
          this._graphIssuesContainer.append(alert_container);
870
871
          return this;
872
       },
873
       
874
       
875
       /**
876
        * Method: _displayIsussuesList
877
        *      Display all errors/warnings in a HTML unordered list.
878
        *
879
        * Parameters:
880
        *      {Object} warnings - An array of warning objects.
881
        *      {Object} errors   - An array of error objects.
882
        *
883
        * Returns:
884
        *     {String} contains HTML with error/warning messages.
885
        */
886
       _displayIsussuesList: function(errors, warnings){
887
           var html_errors   = '';
888
           var html_warnings = '';
889
                  
890
           
891
           if(_.size(errors) > 0){
892
               _.each(errors, function(error){
893
                   html_errors += '<li>' + error['message'] + '</li>';
894
               });
895
               html_errors = '<li><strong>Errors:</strong></li><ul>' + html_errors + '</ul>';  
896
           }
897
           
898
            if(_.size(warnings) > 0){        
899
               _.each(warnings, function(warning){
900
                   html_warnings += '<li>' + warning['message'] + '</li>';
901
               });
902
               html_warnings = '<li><strong>Warnings:</strong></li><ul>' + html_warnings + '</ul>';
903
           }
904
                           
905
           return '<ul>' + 
906
                       html_errors + 
907
                       html_warnings + 
908
                  '</ul>';
909
                     
910
       },
911
       
912
       /**
913
        * Method: _displayJobError
914
        *      Display an error massage resulting from a job error.
915
        *
916
        * Returns:
917
        *      This {<AnalysisResultMenu>} for chaining.
918
        */
919
       _displayJobError: function(xhr) {
920
           Alerts.showErrorAlert(
921
               'An error occurred!', xhr.responseText ||
922
               'We were trying to trigger a computational job, but this crashed on our side. A retry may help. The developers are already informed, sorry for the inconvinience.');
923
           this.hide();
924
       }
925
   });
926
927
928
   /**
929
    *  Class: AnalyticalProbabilityMenu
930
    *      The menu responsible for displaying analysis results.
931
    *
932
    *  Extends: {<AnalyticalResultMenu>}
933
    */
934
   var AnalyticalProbabilityMenu = AnalysisResultMenu.extend({
935
       /**
936
        * Method: _containerID
937
        *      Override of the abstract base class method.
938
        */
939
       _containerID: function() {
940
           return Factory.getModule('Config').IDs.ANALYTICAL_PROBABILITY_MENU;
941
       },
942
       
943
       /**
944
        * Method: _progressMessage
945
        *      Override of the abstract base class method.
946
        */
947
       _progressMessage: function() {
948
           return 'Running probability analysis...';
949
       },
950
       
951
       /**
952
        * Method: _menuHeader
953
        *      Override of the abstract base class method.
954
        */
955
       _menuHeader: function() { 
956
           return 'Analysis Results';
957
       }
958
   });
959
960
961
   /**
962
    * Class: SimulatedProbabilityMenu
963
    *      The menu responsible for displaying simulation results.
964
    *
965
    * Extends: AnalysisResultMenu
966
    */
967
   var SimulatedProbabilityMenu = AnalysisResultMenu.extend({      
968
       /**
969
        * Method: _containerID
970
        *      Override of the abstract base class method.
971
        */
972
       _containerID: function() {
973
           return Factory.getModule('Config').IDs.SIMULATED_PROBABILITY_MENU;
974
       },
975
       
976
       /**
977
        * Method: _progressMessage
978
        *      Override of the abstract base method.
979
        */
980
       _progressMessage: function() {
981
           return 'Running simulation...';
982
       },
983
       
984
       /**
985
        * Method: _menuHeader
986
        *      Override of the abstract base class method.
987
        */
988
       _menuHeader: function() { 
989
           return 'Simulation Results';
990
       }
991
   });
992
    
993
    return{
994
        AnalyticalProbabilityMenu : AnalyticalProbabilityMenu,
995
        SimulatedProbabilityMenu  : SimulatedProbabilityMenu,
996
        CutsetsMenu               : CutsetsMenu
997
    } 
998
});
999