DOJO:onClick 并不总是在自定义小部件中调用

Posted

技术标签:

【中文标题】DOJO:onClick 并不总是在自定义小部件中调用【英文标题】:DOJO: onClick not always called in custom widget 【发布时间】:2015-07-28 13:15:22 【问题描述】:

我遇到了 Dojo 1.10 的问题,需要一些关于如何找出罪魁祸首的建议。我有一个自定义小部件 TaskButton,它实现了 onMouseDown、onMouseUp 和 onClick 方法。这三个都有日志记录语句。 onMouseDown 和 onMouseUp 总是被调用,并且正确的时间和它们的日志语句显示在控制台中。但是,尽管反复单击 TaskButton,onClick 有时也不会被调用。大多数情况下,单击 TaskButton 外部然后返回内部会使 onClick 工作,但并非总是如此。当 onClick 没有被调用时,它的日志语句不会显示在控制台中。

TaskButton.js 自定义小部件

define([
    "dojo/_base/declare",
    "dojo/_base/event",
    "dojo/_base/lang",
    "dojo/dom-class",
    "dojo/dom-construct",
    "dojo/mouse",
    "dojo/on",
    "dojo/query",
    "dojo/topic",
    "dijit/Menu",
    "dijit/MenuItem",
    "dijit/MenuSeparator",
    "dijit/PopupMenuItem",
    "dijit/popup",
    "dijit/Tooltip",
    "dijit/Tree",
    "dijit/tree/ForestStoreModel",
    "dijit/registry",
    "dijit/form/Button",
    "dijit/_WidgetBase",
    "dijit/_OnDijitClickMixin",
    "dijit/_TemplatedMixin",
    "dijit/_WidgetsInTemplateMixin",
    "dojo/text!./templates/TaskButton.html"
], function(declare, event, lang, domClass, domConstruct, mouse, on, query, topic, Menu, MenuItem, MenuSeparator, PopupMenuItem,
        Popup, Tooltip, Tree, ForestStoreModel, registry, button, _WidgetBase, _OnDijitClickMixin, _TemplatedMixin, _WidgetsInTemplateMixin, template)
    return declare("TaskButton", [_WidgetBase, _OnDijitClickMixin, _TemplatedMixin, _WidgetsInTemplateMixin, Menu], 
      scene:0,
      sceneId:0,
      target:"",
      state:"pending",
      cloudCover: false,
      cloudPercentage: 0,
      targetInterest: false,
      hsv: false,
      previousState:"pending",
      backgroundcolor:"#414141",
      templateString:template,
      baseClass: "TaskButton",
      innerNode:undefined,
      cm:null,
      theTask:null,
      eventHandle:null,

postCreate: function()

    // Get a DOM node reference for the root of our widget
    var domNode = this.domNode;
    this.innerNode = domNode.firstChild.nextElementSibling.firstElementChild;

    domClass.replace(this.innerFill, "task"+this.state+"Background", "task"+this.state+"Background");
    if (this.cloudCover && ((this.state === "Ready") || (this.state === "Unassigned"))) 
        domClass.replace(this.innerFill, "task"+"Red"+"Background", "task"+this.state+"Background");
    
    this.previousState = this.state;
    console.log("getting context menu for Scene-" + this.scene + "ContextMenu");
    cm = registry.byId("Scene-" + this.scene + "ContextMenu");

    this.own(
      on(domNode, "contextmenu", lang.hitch(this, "_showContextMenu"))
    );

    this.inherited(arguments);
,

startup: function()

   //Turn off button icons if warranted Must do here after dom nodes built
   if (!this.cloudCover)
   
     dojo.style(dojo.byId("Scene-"+this.scene+"Cloud"), "display", "none");
   
   if (!this.targetInterest)
   
     dojo.style(dojo.byId("Scene-"+this.scene+"Target"), "display", "none");
   
   if (!this.hsv)
   
     dojo.style(dojo.byId("Scene-"+this.scene+"HSV"), "display", "none");
   

   this.inherited(arguments);
,

test: function(sceneId)

  console.log("testing");
              if (sceneId != this.scene)
              
                  domClass.replace("Scene-" + sceneId + "Fill", "taskInnerFill", "taskInnerFillSelected");
              
,

buildRendering: function()

  console.log("buildRendering scene:" + this.scene);
  this.inherited(arguments);
,

//
uninitialize: function()

  if (this.eventHandle != null)
  
    console.log("unsubscribing from event topic");
    eventHandle.remove();
    eventHandle = null;
  
  this.inherited(arguments);
,

//
_onMenuClick: function(event)

  console.log("menu item clicked");
,

_showContextMenu: function(event) 
  console.log("opening context menu for scene:" + this.scene);

  this.inherited(arguments);
,

// This is always called
_onMouseDown: function(e)

    var scene = e.currentTarget.attributes["scene"].value;
    if (e.button == 0)
    
      console.log("mouse left pressed, scene=" + scene + " button=" + e.button);
      domClass.replace("Scene-" + scene + "OuterBorder", "taskOuterBorderPressed", "taskOuterBorder");
    
    else if (e.button == 2)
    
      console.log("mouse right pressed, scene=" + scene + " button=" + e.button);
      domClass.replace("Scene-" + scene + "OuterBorder", "taskOuterBorderPressed", "taskOuterBorder");
    

    this.inherited(arguments);
,

// This is always called
_onMouseUp: function(e)

    var scene = e.currentTarget.attributes["scene"].value;
    if (e.button == 0)
    
      console.log("mouse left released, scene=" + scene + " button=" + e.button);
    
    else if (e.button == 2)
    
      console.log("mouse right released, scene=" + scene + " button=" + e.button);
    
  domClass.replace("Scene-" + this.scene + "OuterBorder", "taskOuterBorder", "taskOuterBorderPressed");
  dijit.hideTooltip(e.currentTarget);
  this.inherited(arguments);
,

//
_onMouseEnter: function(e)

    label = "Scene: " + this.scene + "<BR>State: " + this.state + "<BR>Target: " + this.target;
    dijit.showTooltip(label,e.currentTarget);
    dijit.popup.close();
    this.inherited(arguments);
,

//
_onMouseLeave: function(e)

  this._onMouseUp("");
  this.inherited(arguments);
  dijit.hideTooltip(e.currentTarget);
,

// This is what is not always called
_onClick: function(e)

  var scene = e.currentTarget.attributes["scene"].value;
  console.log("scene " + scene + " clicked");
  this._publishEvent(this.scene, "clicked");
  this.inherited(arguments);
,

//
_onBlur: function(e)

  dijit.popup.close();
  this.inherited(arguments);
,

//
_onContextMenu: function(e)

    this.inherited(arguments);
    this._publishEvent("scene":this.scene,"sceneId":this.sceneId, "clicked");
    dijit.hideTooltip(e.currentTarget);
    var widget = this;
    theNode = "TaskButtonContainer" + widget.scene;
    console.log("mouse right clicked, scene=" + widget.scene + " target: " + e.target + "current target");

                theTask = missionCache.query("sceneId" : this.scene).then( function(results) 
                theTask = results;
                if (widget.state === "Unassigned" || widget.state === "Ready") 
                    //The context menu should fire to allow assignment

                    var cb = new dijit.form.ComboBox(style:"width:96%;background-color:#414141;margin-top:4px;margin-bottom:4px;",
                        name:"usersByTask", placeholder:"Assign this task to: ", store:usersContextMenuCB,

                        labelAttr: 'name',
                        searchAttr: 'name',

                        onChange: function()
                            theTask[0].taskStatus = "Assigned";
                            theTask[0].taskOwner = this.item.userName;
                            missionCache.put(theTask[0]);
                            console.log("nothing");
                            widget.set("state", "Assigned");
                            assignTask(this.item.userName);
                            widget.domNode.classList.remove("Unassigned");
                            widget.domNode.classList.add("Assigned");
                            widget.domNode.setAttribute("dndtype", "Assigned");
                            this.destroy();

                        ,
                        onClose: function()  this.destroy();
                    );
                    cb.toggleDropDown();
                    dijit.popup.open(parent: widget, popup:cb, around:e.target,
                        onClose: function()
                            dijit.popup.close(cb);
                        
                    );
                
                );
,


_publishEvent: function(sceneNumber, eventName)

  console.log("publishing " + eventName + " for scene " + sceneNumber);
  topic.publish("TaskButton/tasks",  scene:sceneNumber, task:this, event:eventName );
,

_setStateAttr: function(newState)

    if (newState != "")
    
        console.log("setting state for scene:" + this.scene + " to " + newState);
        this._set("state", newState);
        if (this.innerNode !== undefined)
        
        domClass.replace(this.innerFill, "task"+newState+"Background", "task"+this.previousState+"Background");
        
        this.previousState = this.state;
        this.state = newState;
    
  this.inherited(arguments);
,

_changeTaskState: function(newState)
  
    require(["dijit/registry"], function(registry) 
      var node = registry.byId(clickedItem);
      if (node !== undefined)
      
        node.set("state", newState);
        console.log("changed task " + this.clickedItem + " state to " + newState);
      
    );
  this.inherited(arguments);
  ,

  _menuTaskDetails: function(e)
  
    console.log("do task deatils");
  

);
)

模板中的所有按钮动作都有附加事件。

TaskButton.html 模板:

    <li class="dojoDndItem" dndType="$state" style="border:none;padding:0" data-dojo-props="scene:$scene">
<div id="TaskButtonContainer-$scene" widgetid="TaskButtonContainer-$scene" class="$baseClass" data-dojo-attach-point="taskButtonContainer"
     data-dojo-attach-event="onContextMenu:_showContextMenu">
   <div widgetid="Scene-$sceneContextMenu" data-dojo-type="dijit/Menu" data-dojo-props="contextMenuForWindow:false"
       data-dojo-attach-point="contextMenu" targetNodeIds="Scene-$sceneFill" style="display: none;">
    <div data-dojo-type="dijit/MenuItem" data-dojo-attach-event="onClick:_menuTaskDetails">
      Task Details
    </div>
  </div>
  <div id="Scene-$sceneOuterBorder" widgetid="Scene-$sceneOuterBorder" class="taskOuterBorder" data-dojo-attach-point="outerBorder" scene="$scene">
    <div id="Scene-$sceneFill" class="taskInnerFill task$stateBackground" data-dojo-attach-point="innerFill" scene="$scene"
       data-dojo-attach-event="onMouseDown:_onMouseDown,onMouseUp:_onMouseUp,onDijitClick:_onClick,onMouseEnter:_onMouseEnter,onMouseLeave:_onMouseLeave,onContextMenu:_onContextMenu,onBlur:_onBlur">
      <div id="Scene-$sceneText" class="taskText" data-dojo-attach-point="text">
            <table style="margin:0;padding:0">
              <tr>
                <td>$scene</td>
              </tr>
              <tr>
                <td>
                  <img id="Scene-$sceneCloud" src="img/cloud.png"   >
                  <img id="Scene-$sceneTarget" src="img/target.png"   >
                  <img id="Scene-$sceneHSV" src="img/HSV.png"   >
                </td>
              </tr>
            </table>
         </div>
      </div>
    </div>
    </div>
    </li>

我还根据建议清理了代码,TaskButton 的 onClick 事件处理程序的行为没有变化。

【问题讨论】:

可以分享一下TaskButton的代码吗?没有它就很难猜出哪里出了问题。 【参考方案1】:

嗯, 有多个错误...

你不需要继承_WidgetBase_TemplatedMixin_OnDijitClickMixin,它们带有Menu Menu 应该是 Base(所以应该是继承列表中的第一个) 您不应该使用domNode.firstChild.nextElementSibling.firstElementChild,而是应该在模板中使用data-dojo-attach-point cm = registry.byId('Scene-' + this.scene + 'ContextMenu'); 应该是 this.cm = registry.byId('Scene-' + this.scene + 'ContextMenu'); 不应使用 dojo 命名空间。因此,dojo.style 应替换为 dojo/dom-styledomStyle.set() 的要求,dojo.byId 应替换为 dojo/domdom.byId() 的要求 不应使用 dijit 命名空间。因此,dijit.hideTooltip 应替换为 dijit/Tooltip 的要求,然后 Tooltip.hide()dijit.showTooltip 应替换为 Tooltip.show()dijit.popup.close() 应替换为 dijit/_base/popup 的要求,然后 popup.close() 和 @987654346 @ 应替换为 popup.open() 在方法_onMouseEnterlabel 之前缺少vardijit.form.ComboBoxonChange 中,您调用了一个不存在的方法assignTask_changeTaskState 方法中,您需要dijit/registry,但它已经可用。所以额外的要求是没有用的 可能还有更多,但我不会去重构你的代码 终于没有任何东西附加到您的_onClick 方法上。我不明白它有时是如何执行的......但也许它是使用data-dojo-attach-event 连接的?如果是,请同时提供您的模板。

尝试应用所有更改来清除一些错误,看看是否效果更好。 如果没有,请分享您的按钮模板。

define([
    'dojo/_base/declare',
    'dojo/_base/event',
    'dojo/_base/lang',
    'dojo/dom',
    'dojo/dom-class',
    'dojo/dom-construct',
    'dojo/dom-style',
    'dojo/mouse',
    'dojo/on',
    'dojo/query',
    'dojo/topic',
    'dijit/Menu',
    'dijit/MenuItem',
    'dijit/MenuSeparator',
    'dijit/PopupMenuItem',
    'dijit/popup',
    'dijit/Tooltip',
    'dijit/Tree',
    'dijit/tree/ForestStoreModel',
    'dijit/registry',
    'dijit/_base/popup',
    'dijit/form/Button',
    'dijit/_WidgetBase',
    'dijit/_OnDijitClickMixin',
    'dijit/_TemplatedMixin',
    'dijit/_WidgetsInTemplateMixin',
    'dojo/text!./templates/TaskButton.html'
], function(declare, event, lang, dom, domClass, domConstruct, domStyle, mouse, on, query, topic, Menu, MenuItem, MenuSeparator, PopupMenuItem,
    Popup, Tooltip, Tree, ForestStoreModel, registry, popup, button, _WidgetBase, _OnDijitClickMixin, _TemplatedMixin, _WidgetsInTemplateMixin, template) 
    return declare('TaskButton', [Menu, _WidgetsInTemplateMixin], 
        scene: 0,
        sceneId: 0,
        target: '',
        state: 'pending',
        cloudCover: false,
        cloudPercentage: 0,
        targetInterest: false,
        hsv: false,
        previousState: 'pending',
        backgroundcolor: '#414141',
        templateString: template,
        baseClass: 'TaskButton',
        innerNode: undefined,
        cm: null,
        theTask: null,
        eventHandle: null,

        postCreate: function() 
            // Get a DOM node reference for the root of our widget
            var domNode = this.domNode;
            this.innerNode = domNode.firstChild.nextElementSibling.firstElementChild;

            domClass.replace(this.innerFill, 'task' + this.state + 'Background', 'task' + this.state + 'Background');
            if (this.cloudCover && ((this.state === 'Ready') || (this.state === 'Unassigned'))) 
                domClass.replace(this.innerFill, 'task' + 'Red' + 'Background', 'task' + this.state + 'Background');
            
            this.previousState = this.state;
            console.log('getting context menu for Scene-' + this.scene + 'ContextMenu');
            cm = registry.byId('Scene-' + this.scene + 'ContextMenu');

            this.own(
                on(domNode, 'contextmenu', lang.hitch(this, '_showContextMenu'))
            );

            this.inherited(arguments);
        ,

        startup: function() 
            //Turn off button icons if warranted Must do here after dom nodes built
            if (!this.cloudCover) 
                domStyle.set(dom.byId('Scene-' + this.scene + 'Cloud'), 'display', 'none');
            
            if (!this.targetInterest) 
                domStyle.set(dom.byId('Scene-' + this.scene + 'Target'), 'display', 'none');
            
            if (!this.hsv) 
                domStyle.set(dom.byId('Scene-' + this.scene + 'HSV'), 'display', 'none');
            

            this.inherited(arguments);
        ,

        test: function(sceneId) 
            console.log('testing');
            if (sceneId != this.scene) 
                domClass.replace('Scene-' + sceneId + 'Fill', 'taskInnerFill', 'taskInnerFillSelected');
            
        ,

        buildRendering: function() 
            console.log('buildRendering scene:' + this.scene);
            this.inherited(arguments);
        ,

        //
        uninitialize: function() 
            if (this.eventHandle != null) 
                console.log('unsubscribing from event topic');
                eventHandle.remove();
                eventHandle = null;
            
            this.inherited(arguments);
        ,

        //
        _onMenuClick: function(event) 
            console.log('menu item clicked');
        ,

        _showContextMenu: function(event) 
            console.log('opening context menu for scene:' + this.scene);

            this.inherited(arguments);
        ,

        // This is always called
        _onMouseDown: function(e) 
            var scene = e.currentTarget.attributes['scene'].value;
            if (e.button == 0) 
                console.log('mouse left pressed, scene=' + scene + ' button=' + e.button);
                domClass.replace('Scene-' + scene + 'OuterBorder', 'taskOuterBorderPressed', 'taskOuterBorder');
             else if (e.button == 2) 
                console.log('mouse right pressed, scene=' + scene + ' button=' + e.button);
                domClass.replace('Scene-' + scene + 'OuterBorder', 'taskOuterBorderPressed', 'taskOuterBorder');
            

            this.inherited(arguments);
        ,

        // This is always called
        _onMouseUp: function(e) 
            var scene = e.currentTarget.attributes['scene'].value;
            if (e.button == 0) 
                console.log('mouse left released, scene=' + scene + ' button=' + e.button);
             else if(e.button == 2) 
                console.log('mouse right released, scene=' + scene + ' button=' + e.button);
            
            domClass.replace('Scene-' + this.scene + 'OuterBorder', 'taskOuterBorder', 'taskOuterBorderPressed');
            Tooltip.hide(e.currentTarget);
            this.inherited(arguments);
        ,

        //
        _onMouseEnter: function(e) 
            var label = 'Scene: ' + this.scene + '<BR>State: ' + this.state + '<BR>Target: ' + this.target;
            Tooltip.show(label, e.currentTarget);
            popup.close();
            this.inherited(arguments);
        ,

        //
        _onMouseLeave: function(e) 
            this._onMouseUp('');
            this.inherited(arguments);
            Tooltip.hide(e.currentTarget);
        ,

        // This is what is not always called
        _onClick: function(e) 
            var scene = e.currentTarget.attributes['scene'].value;
            console.log('scene ' + scene + ' clicked');
            this._publishEvent(this.scene, 'clicked');
            this.inherited(arguments);
        ,

        //
        _onBlur: function(e) 
            popup.close();
            this.inherited(arguments);
        ,

        //
        _onContextMenu: function(e) 
            this.inherited(arguments);
            this._publishEvent(
                'scene': this.scene,
                'sceneId': this.sceneId
            , 'clicked');
            Tooltip.hide(e.currentTarget);
            var widget = this;
            theNode = 'TaskButtonContainer' + widget.scene;
            console.log('mouse right clicked, scene=' + widget.scene + ' target: ' + e.target + 'current target');

            theTask = missionCache.query(
                'sceneId': this.scene
            ).then(function(results) 
                theTask = results;
                if (widget.state === 'Unassigned' || widget.state === 'Ready') 
                    //The context menu should fire to allow assignment

                    var cb = new dijit.form.ComboBox(
                        style: 'width:96%;background-color:#414141;margin-top:4px;margin-bottom:4px;',
                        name: 'usersByTask',
                        placeholder: 'Assign this task to: ',
                        store: usersContextMenuCB,

                        labelAttr: 'name',
                        searchAttr: 'name',

                        onChange: function() 
                            theTask[0].taskStatus = 'Assigned';
                            theTask[0].taskOwner = this.item.userName;
                            missionCache.put(theTask[0]);
                            console.log('nothing');
                            widget.set('state', 'Assigned');
                            //assignTask(this.item.userName);
                            widget.domNode.classList.remove('Unassigned');
                            widget.domNode.classList.add('Assigned');
                            widget.domNode.setAttribute('dndtype', 'Assigned');
                            this.destroy();

                        ,
                        onClose: function() 
                            this.destroy();
                        
                    );
                    cb.toggleDropDown();
                    popup.open(
                        parent: widget,
                        popup: cb,
                        around: e.target,
                        onClose: function() 
                            popup.close(cb);
                        
                    );
                
            );
        ,


        _publishEvent: function(sceneNumber, eventName) 
            console.log('publishing ' + eventName + ' for scene ' + sceneNumber);
            topic.publish('TaskButton/tasks', 
                scene: sceneNumber,
                task: this,
                event: eventName
            );
        ,

        _setStateAttr: function(newState) 
            if (newState != '') 
                console.log('setting state for scene:' + this.scene + ' to ' + newState);
                this._set('state', newState);
                if (this.innerNode !== undefined) 
                    domClass.replace(this.innerFill, 'task' + newState + 'Background', 'task' + this.previousState + 'Background');
                
                this.previousState = this.state;
                this.state = newState;
            
            this.inherited(arguments);
        ,

        _changeTaskState: function(newState) 

            var node = registry.byId(clickedItem);
            if (node !== undefined) 
                node.set('state', newState);
                console.log('changed task ' + this.clickedItem + ' state to ' + newState);
            

            this.inherited(arguments);
        ,

        _menuTaskDetails: function(e) 
            console.log('do task deatils');
        

    );
)

【讨论】:

以上是关于DOJO:onClick 并不总是在自定义小部件中调用的主要内容,如果未能解决你的问题,请参考以下文章

访问模板化小部件外部的 dojo 附加点

dojo 小部件不会发出事件

小部件上的 dojo 查询

自定义小部件内的 dojo 小部件的可寻址性

为啥连接的 onclick 事件不会触发?

自定义小部件中的 dojo 数据网格未呈现