Github user sjcorbett commented on a diff in the pull request:

    https://github.com/apache/incubator-brooklyn/pull/156#discussion_r17303050
  
    --- Diff: usage/jsgui/src/main/webapp/assets/js/view/entity-config.js ---
    @@ -22,146 +22,473 @@
      * @type {*}
      */
     define([
    -    "underscore", "jquery", "backbone", "brooklyn-utils",
    -    "view/viewutils", "model/config-summary", "text!tpl/apps/config.html",
    +    "underscore", "jquery", "backbone", "brooklyn-utils", "zeroclipboard", 
"view/viewutils", 
    +    "model/config-summary", "text!tpl/apps/config.html", 
"text!tpl/apps/config-name.html",
         "jquery-datatables", "datatables-extensions"
    -], function (_, $, Backbone, Util, ViewUtils, ConfigSummary, ConfigHtml) {
    +], function (_, $, Backbone, Util, ZeroClipboard, ViewUtils, 
ConfigSummary, ConfigHtml, ConfigNameHtml) {
     
    +    // TODO consider extracting all such usages to a shared ZeroClipboard 
wrapper?
    +    ZeroClipboard.config({ moviePath: 'assets/js/libs/ZeroClipboard.swf' 
});
    +
    +    var configHtml = _.template(ConfigHtml),
    +        configNameHtml = _.template(ConfigNameHtml);
    +
    +    // TODO refactor to share code w entity-sensors.js
    +    // in meantime, see notes there!
         var EntityConfigView = Backbone.View.extend({
    -        template:_.template(ConfigHtml),
    +        template: configHtml,
             configMetadata:{},
             refreshActive:true,
    +        zeroClipboard: null,
    +        
             events:{
                 'click .refresh':'refreshNow',
                 'click .filterEmpty':'toggleFilterEmpty',
    -            'click .toggleAutoRefresh':'toggleAutoRefresh'
    +            'click .toggleAutoRefresh':'toggleAutoRefresh',
    +
    +            'mouseup .valueOpen':'valueOpen',
    +            'mouseover #config-table tbody tr':'noteFloatMenuActive',
    +            'mouseout #config-table tbody tr':'noteFloatMenuSeemsInactive',
    +            'mouseover .floatGroup':'noteFloatMenuActive',
    +            'mouseout .floatGroup':'noteFloatMenuSeemsInactive',
    +            'mouseover .clipboard-item':'noteFloatMenuActiveCI',
    +            'mouseout .clipboard-item':'noteFloatMenuSeemsInactiveCI',
    +            'mouseover .hasFloatLeft':'showFloatLeft',
    +            'mouseover .hasFloatDown':'enterFloatDown',
    +            'mouseout .hasFloatDown':'exitFloatDown',
    +            'mouseup .light-popup-menu-item':'closeFloatMenuNow',
             },
    +        
             initialize:function () {
    -            _.bindAll(this)
    -            this.$el.html(this.template({ }));
    +            _.bindAll(this);
    +            this.$el.html(this.template());
    +            
                 var that = this,
                     $table = this.$('#config-table');
                 that.table = ViewUtils.myDataTable($table, {
                     "fnRowCallback": function( nRow, aData, iDisplayIndex, 
iDisplayIndexFull ) {
    -                    $(nRow).attr('id', aData[0])
    +                    $(nRow).attr('id', aData[0]);
                         $('td',nRow).each(function(i,v){
    -                        if (i==1) $(v).attr('class','config-actions');
    -                        if (i==2) $(v).attr('class','config-value');
    -                    })
    +                        if (i==1) $(v).attr('class','config-value');
    +                    });
                         return nRow;
                     },
                     "aoColumnDefs": [
    -                                 { // name, with tooltip
    +                                 { // name (with tooltip)
                                          "mRender": function ( data, type, row 
) {
    -                                         // name (column 1) should have 
tooltip title
    -                                         return '<span class="config-name" 
'+ 
    -                                             'rel="tooltip" title='+
    -                                             (data['description'] ? 
    -                                                     
'"<b>'+Util.escape(data['description'])+'</b><br/>' : '')+
    -                                             
'('+Util.escape(data['type'])+')" data-placement="left">'+
    -                                             
Util.escape(data['name'])+'</span>';
    +                                         var actions = 
that.getConfigActions(data.name);
    +                                         var context = _.extend(data, { 
    +                                             description: 
data['description'], type: data['type']});
    +                                         return configNameHtml(context);
                                          },
                                          "aTargets": [ 1 ]
                                      },
    -                                 { // actions (just one, json link hard 
coded here apart from url)
    -                                     "mRender": function ( link, type, row 
) {
    -                                         if (link=="") return "";
    -                                         var text=""
    -                                         var icon="icon-file"
    -                                         var title="JSON direct link"
    -                                         var actionsText = 
    -                                             "<a 
href='"+Util.escape(link)+"'"+
    -                                             " 
class='"+Util.escape(icon)+"'"+
    -                                             " 
title='"+Util.escape(title)+"'>"+
    -                                                 
Util.escape(text)+"</a>\n";
    -                                         //just one action here
    -                                         return actionsText;
    -                                     },
    -                                     "aTargets": [ 2 ]
    -                                 },
                                      { // value
                                          "mRender": function ( data, type, row 
) {
    -                                         return Util.toDisplayString(data)
    +                                         var escapedValue = 
Util.toDisplayString(data);
    +                                         if (type!='display')
    +                                             return escapedValue;
    +                                         
    +                                         var hasEscapedValue = 
(escapedValue!=null && (""+escapedValue).length > 0);
    +                                             configName = row[0],
    +                                             actions = 
that.getConfigActions(configName);
    +                                         
    +                                         // datatables doesn't seem to 
expose any way to modify the html in place for a cell,
    +                                         // so we rebuild
    +                                         
    +                                         var result = "<span 
class='value'>"+(hasEscapedValue ? escapedValue : '')+"</span>";
    +                                         if (actions.open)
    +                                             result = "<a 
href='"+actions.open+"'>" + result + "</a>";
    +                                         if (escapedValue==null || 
escapedValue.length < 3)
    +                                             // include whitespace so we 
can click on it, if it's really small
    +                                             result += 
"&nbsp;&nbsp;&nbsp;&nbsp;";
    +
    +                                         var $row = 
$('tr[id="'+configName+'"]');
    +                                         var existing = 
$row.find('.dynamic-contents');
    +                                         // for the json url, use the full 
url (relative to window.location.href)
    +                                         var jsonUrl = actions.json ? new 
URI(actions.json).resolve(new URI(window.location.href)).toString() : null;
    +                                         // prefer to update in place, so 
menus don't disappear, also more efficient
    +                                         // (but if menu is changed, we do 
recreate it)
    +                                         if (existing.length>0) {
    +                                             if 
(that.checkFloatMenuUpToDate($row, actions.open, '.actions-open', 
'open-target') &&
    +                                                 
that.checkFloatMenuUpToDate($row, escapedValue, '.actions-copy') &&
    +                                                 
that.checkFloatMenuUpToDate($row, actions.json, '.actions-json-open', 
'open-target') &&
    +                                                 
that.checkFloatMenuUpToDate($row, jsonUrl, '.actions-json-copy', 'copy-value')) 
{
    +//                                                 log("updating in place 
"+configName)
    +                                                 existing.html(result);
    +                                                 return 
$row.find('td.config-value').html();
    +                                             }
    +                                         }
    +                                         
    +                                         // build the menu - either 
because it is the first time, or the actions are stale
    +//                                         log("creating "+configName);
    +                                         
    +                                         var downMenu = "";
    +                                         if (actions.open)
    +                                             downMenu += "<div 
class='light-popup-menu-item valueOpen actions-open' 
open-target='"+actions.open+"'>" +
    +                                                    "Open</div>";
    +                                         if (hasEscapedValue) downMenu +=
    +                                             "<div 
class='light-popup-menu-item handy valueCopy actions-copy clipboard-item'>Copy 
Value</div>";
    +                                         if (actions.json) downMenu +=
    +                                             "<div 
class='light-popup-menu-item handy valueOpen actions-json-open' 
open-target='"+actions.json+"'>" +
    +                                                 "Open REST Link</div>";
    +                                         if (actions.json && 
hasEscapedValue) downMenu +=
    +                                             "<div 
class='light-popup-menu-item handy valueCopy actions-json-copy clipboard-item' 
copy-value='"+
    +                                                 jsonUrl+"'>Copy REST 
Link</div>";
    +                                         if (downMenu=="") {
    +//                                             log("no actions for 
"+configName);
    +                                             downMenu += 
    +                                                 "<div 
class='light-popup-menu-item'>(no actions)</div>";
    +                                         }
    +                                         downMenu = "<div 
class='floatDown'><div class='light-popup'><div class='light-popup-body'>"
    +                                             + downMenu +
    +                                             "</div></div></div>";
    +                                         result = "<span 
class='hasFloatLeft handy dynamic-contents'>" + result +
    +                                                "</span>" +
    +                                                "<div 
class='floatLeft'><span class='icon-chevron-down hasFloatDown'></span>" +
    +                                                downMenu +
    +                                                "</div>";
    +                                         result = "<div 
class='floatGroup'>" + result + "</div>";
    +                                         // also see updateFloatMenus 
which wires up the JS for these classes
    +                                         
    +                                         return result;
                                          },
    -                                     "aTargets": [ 3 ]
    +                                     "aTargets": [ 2 ]
                                      },
                                      // ID in column 0 is standard (assumed in 
ViewUtils)
    -                                 { "bVisible": false,  "aTargets": [ 0 ] 
/* hide id column */ }
    +                                 { "bVisible": false,  "aTargets": [ 0 ] }
                                  ]            
                 });
    -            ViewUtils.addFilterEmptyButton(that.table);
    -            ViewUtils.addAutoRefreshButton(that.table);
    -            ViewUtils.addRefreshButton(that.table);
    -            that.loadConfigMetadata();
    -            that.updateConfigPeriodically();
    -            that.toggleFilterEmpty();
    -        },
    -        render:function () {
    -            this.updateConfigNow();
    +            
    +            this.zeroClipboard = new ZeroClipboard();
    +            this.zeroClipboard.on( "dataRequested" , function(client) {
    +                var text = $(this).attr('copy-value');
    +                if (!text) text = 
$(this).closest('.floatGroup').find('.value').html();
    +                try {
    +//                    log("Copying text '"+text+"' to clipboard");
    +                    client.setText(text);
    +                    
    +                    var $widget = $(this);
    +                    var oldHtml = $widget.html();
    +                    var fnRestore = _.once(function() { 
$widget.html(oldHtml); });
    +                    // show the word copied for feedback;
    +                    // NB this occurs on mousedown, due to how flash 
plugin works
    +                    // (same style of feedback and interaction as github)
    +                    // the other "clicks" are now triggered by *mouseup*
    +                    $widget.html('<b>Copied!</b>');
    +                    setTimeout(fnRestore, 3000);
    +                    
    +                    // these listeners stay registered until page is 
reloaded
    +                    // but they do nothing after first run, due to use of 
_.once
    +                    // however the timeout is good enough, and actually 
desired
    +                    // because on corner case of 
mousedown-moveaway-mouseup,
    +                    // we want to keep the feedback; so they work, but are 
disabled for now.
    +                    // (remove once we are happy with this behaviour, 
since Feb 2014)
    +//                    that.zeroClipboard.on( "mouseout", fnRestore);
    +//                    that.zeroClipboard.on( "mouseup", fnRestore);
    +                } catch (e) {
    +                    log("Zeroclipboard failure; falling back to prompt 
mechanism");
    +                    log(e);
    +                    Util.promptCopyToClipboard(text);
    +                }
    +            });
    +            // these seem to arrive delayed sometimes, so we also work 
with the clipboard-item class events
    +            this.zeroClipboard.on( "mouseover", function() { 
that.noteFloatMenuZeroClipboardItem(true, this); } );
    +            this.zeroClipboard.on( "mouseout", function() { 
that.noteFloatMenuZeroClipboardItem(false, this); } );
    +            this.zeroClipboard.on( "mouseup", function() { 
that.closeFloatMenuNow(); } );
    +
    +            ViewUtils.addFilterEmptyButton(this.table);
    +            ViewUtils.addAutoRefreshButton(this.table);
    +            ViewUtils.addRefreshButton(this.table);
    +            this.loadConfigMetadata();
    +            this.updateConfigPeriodically();
    +            this.toggleFilterEmpty();
    +            return this;
    +        },
    +        
    +        floatMenuActive: false,
    +        lastFloatMenuRowId: null,
    +        lastFloatFocusInTextForEventUnmangling: null,
    +        updateFloatMenus: function() { this.zeroClipboard.clip( 
$('.valueCopy') ); },
    +        showFloatLeft: function(event) {
    +            this.noteFloatMenuFocusChange(true, event, "show-left");
    +            this.showFloatLeftOf($(event.currentTarget));
    +        },
    +        showFloatLeftOf: function($hasFloatLeft) {
    +            $hasFloatLeft.next('.floatLeft').show(); 
    +        },
    +        enterFloatDown: function(event) {
    +            this.noteFloatMenuFocusChange(true, event, "show-down");
    +//            log("entering float down");
    +            var fdTarget = $(event.currentTarget);
    +//            log( fdTarget );
    +            this.floatDownFocus = fdTarget;
    +            var that = this;
    +            setTimeout(function() {
    +                that.showFloatDownOf( fdTarget );
    +            }, 200);
    +        },
    +        exitFloatDown: function(event) {
    +//            log("exiting float down");
    +            this.floatDownFocus = null;
    +        },
    +        showFloatDownOf: function($hasFloatDown) {
    +            if ($hasFloatDown != this.floatDownFocus) {
    +//                log("float down did not hover long enough");
    +                return;
    +            }
    +            var down = $hasFloatDown.next('.floatDown');
    +            down.show();
    +            $('.light-popup', down).show(2000); 
    +        },
    +        noteFloatMenuActive: function(focus) { 
    +            this.noteFloatMenuFocusChange(true, focus, "menu");
    +            
    +            // remove dangling zc events (these don't always get removed, 
apparent bug in zc event framework)
    +            // this causes it to flash sometimes but that's better than 
leaving the old item highlighted
    +            if (focus.toElement && 
$(focus.toElement).hasClass('clipboard-item')) {
    +                // don't remove it
    +            } else {
    +                var zc = 
$(focus.target).closest('.floatGroup').find('div.zeroclipboard-is-hover');
    +                zc.removeClass('zeroclipboard-is-hover');
    +            }
    +        },
    +        noteFloatMenuSeemsInactive: function(focus) { 
this.noteFloatMenuFocusChange(false, focus, "menu"); },
    +        noteFloatMenuActiveCI: function(focus) { 
this.noteFloatMenuFocusChange(true, focus, "menu-clip-item"); },
    +        noteFloatMenuSeemsInactiveCI: function(focus) { 
this.noteFloatMenuFocusChange(false, focus, "menu-clip-item"); },
    +        noteFloatMenuZeroClipboardItem: function(seemsActive,focus) { 
    +            this.noteFloatMenuFocusChange(seemsActive, focus, "clipboard");
    +            if (seemsActive) {
    +                // make the table row highlighted (as the default hover 
event is lost)
    +                // we remove it when the float group goes away
    +                $(focus).closest('tr').addClass('zeroclipboard-is-hover');
    +            } else {
    +                // sometimes does not get removed by framework - though 
this doesn't seem to help
    +                // as you can see by logging this before and after:
    +//                log(""+$(focus).attr('class'))
    +                // the problem is that the framework seems sometime to 
trigger this event before adding the class
    +                // see in noteFloatMenuActive where we do a different check
    +                $(focus).removeClass('zeroclipboard-is-hover');
    +            }
    +        },
    +        noteFloatMenuFocusChange: function(seemsActive, focus, caller) {
    +//            log(""+new Date().getTime()+" note active "+caller+" 
"+seemsActive);
    +            var delayCheckFloat = true;
    +            var focusRowId = null;
    +            var focusElement = null;
    +            if (focus) {
    +                focusElement = focus.target ? focus.target : focus;
    +                if (seemsActive) {
    +                    this.lastFloatFocusInTextForEventUnmangling = 
$(focusElement).text();
    +                    focusRowId = focus.target ? 
$(focus.target).closest('tr').attr('id') : $(focus).closest('tr').attr('id');
    +                    if (this.floatMenuActive && 
focusRowId==this.lastFloatMenuRowId) {
    +                        // lastFloatMenuRowId has not changed, when moving 
within a floatgroup
    +                        // (but we still get mouseout events when the 
submenu changes)
    +//                        log("redundant mousein from "+ focusRowId );
    +                        return;
    +                    }
    +                } else {
    +                    // on mouseout, skip events which are bogus
    +                    // first, if the toElement is in the same floatGroup
    +                    focusRowId = focus.toElement ? 
$(focus.toElement).closest('tr').attr('id') : null;
    +                    if (focusRowId==this.lastFloatMenuRowId) {
    +                        // lastFloatMenuRowId has not changed, when moving 
within a floatgroup
    +                        // (but we still get mouseout events when the 
submenu changes)
    +//                        log("skipping, internal mouseout from "+ 
focusRowId );
    +                        return;
    +                    }
    +                    // check (a) it is the 'out' event corresponding to 
the most recent 'in'
    +                    // (because there is a race where it can say  in1, 
in2, out1 rather than in1, out2, in2
    +                    if ($(focusElement).text() != 
this.lastFloatFocusInTextForEventUnmangling) {
    +//                        log("skipping, not most recent mouseout from "+ 
focusRowId );
    +                        return;
    +                    }
    +                    if (focus.toElement) {
    +                        if 
($(focus.toElement).hasClass('global-zeroclipboard-container')) {
    +//                            log("skipping out, as we are moving to 
clipboard container");
    +                            return;
    +                        }
    +                        if (focus.toElement.name && 
focus.toElement.name=="global-zeroclipboard-flash-bridge") {
    +//                            log("skipping out, as we are moving to 
clipboard movie");
    +                            return;                            
    +                        }
    +                    }
    +                } 
    +            }           
    +//            log( "moving to "+focusRowId );
    +            if (seemsActive && focusRowId) {
    +//                log("setting lastFloat when "+this.floatMenuActive + ", 
from "+this.lastFloatMenuRowId );
    +                if (this.lastFloatMenuRowId != focusRowId) {
    +                    if (this.lastFloatMenuRowId) {
    +                        // the floating menu has changed, hide the old
    +//                        log("hiding old menu on float-focus change");
    +                        this.closeFloatMenuNow();
    +                    }
    +                }
    +                // now show the new, if possible (might happen multiple 
times, but no matter
    +                if (focusElement) {
    +//                    log("ensuring row "+focusRowId+" is showing on 
change");
    +                    
this.showFloatLeftOf($(focusElement).closest('tr').find('.hasFloatLeft'));
    +                    this.lastFloatMenuRowId = focusRowId;
    +                } else {
    +                    this.lastFloatMenuRowId = null;
    +                }
    +            }
    +            this.floatMenuActive = seemsActive;
    +            if (!seemsActive) {
    +                this.scheduleCheckFloatMenuNeedsHiding(delayCheckFloat);
    +            }
    +        },
    +        scheduleCheckFloatMenuNeedsHiding: function(delayCheckFloat) {
    +            if (delayCheckFloat) {
    +                this.checkTime = new Date().getTime()+299;
    +                setTimeout(this.checkFloatMenuNeedsHiding, 300);
    +            } else {
    +                this.checkTime = new Date().getTime()-1;
    +                this.checkFloatMenuNeedsHiding();
    +            }
    +        },
    +        closeFloatMenuNow: function() {
    +//            log("closing float menu due do direct call (eg click)");
    +            this.checkTime = new Date().getTime()-1;
    +            this.floatMenuActive = false;
    +            this.checkFloatMenuNeedsHiding();
    +        },
    +        checkFloatMenuNeedsHiding: function() {
    --- End diff --
    
    All of this float menu logic is crying out to be extracted to a separate 
object that is mixed in.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at [email protected] or file a JIRA ticket
with INFRA.
---

Reply via email to