Issue Tab Panel Duplicating Elements

Chien-Hao Chen April 6, 2018

Hi, the problem is that... If I'm doing render action like switching on issue tabs panels/processing workflow, some HTML elements of velocity will be duplicated:

duplicated.png

 

Here is my web-resource, issue-tabpanel in atlassian-plugin.xml:

<web-resource key="my_plugin-resources" name="my_plugin Web Resources">
<dependency>com.atlassian.auiplugin:ajs</dependency>
<dependency>jira.webresources:jira-global</dependency>
<dependency>com.atlassian.auiplugin:aui-experimental-iconfont</dependency>
<dependency>com.atlassian.auiplugin:aui-table-sortable</dependency>
<dependency>com.atlassian.auiplugin:aui-inline-dialog2</dependency>
<dependency>com.atlassian.auiplugin:aui-select2</dependency>
<dependency>com.atlassian.auiplugin:aui-toggle</dependency>
<dependency>com.atlassian.auiplugin:aui-flag</dependency>
<dependency>com.atlassian.auiplugin:aui-help</dependency>
<dependency>com.atlassian.auiplugin:dialog2</dependency>
<dependency>com.atlassian.auiplugin:aui-dropdown2</dependency>
<dependency>com.atlassian.auiplugin:java.lang.String</dependency>
<resource type="download" name="my_plugin.css" location="css/my_plugin.css"/>
<resource type="download" name="my_plugin.js" location="js/my_plugin.js">
<property key="content-type" value="text/javascript"/>
</resource>
<resource type="download" name="images/" location="/images"/>
<context>atl.general</context>
<context>my_plugin</context>
</web-resource>

<issue-tabpanel key="jira-issue-tab-panel" name="jira Issue Tab Panel" i18n-name-key="jira-issue-tab-panel.name" class="com.my.plugin.jira.tabpanels.JiraIssueTabPanel">
<description key="jira-issue-tab-panel.description">The jira Issue Tab Panel Plugin</description>
<label key="jira-issue-tab-panel.label"/>
<resource type="velocity" name="view" location="templates/tabpanels/jira-issue-tab-panel.vm"/>
<order>200</order>
<sortable>true</sortable>
<supports-ajax-load>true</supports-ajax-load>
</issue-tabpanel>

velocity file jira-issue-tab-panel.vm:

<script type="text/javascript">
#include("js/my_plugin.js");
</script>

<div id="issue_guide_body">
<textarea id="async" style="display:none">$async</textarea>
<textarea id="issueId" style="display:none">$issue.getId()</textarea>

<div class="aui-group">
<div class="aui-item" style="width:10%;">
<button id="create-guide-dialog-show-button" class="aui-button aui-button-primary">TEST</button>
</div>
</div>

<section role="dialog" id="create-guide-dialog" class="aui-layer aui-dialog2 aui-dialog2-xlarge" aria-hidden="true">
<header class="aui-dialog2-header">
<h2 class="aui-dialog2-header-main"></h2>
<a class="aui-dialog2-header-close">
<span class="aui-icon aui-icon-small aui-iconfont-close-dialog">Close</span>
</a>
</header>
<div class="aui-dialog2-content">
<form id="create-guide" class="aui">
<div class="aui-group">
<div class="aui-item" style="width:50%;">
<select style="width:100%; max-width:100%;" id="select1" name="select1" placeholder="">
#foreach( $row in $select1 )
<option value="$row.getName()">$row.getName()</option>
#end
</select>
</div>
<div class="aui-item" style="width:50%;">
<select style="width:100%; max-width:100%;" id="select2" name="select2" placeholder="" multiple="">
#foreach( $row in $select2 )
<option value="$row.getName()">$row.getName()</option>
#end
</select>
</div>
</div>
</form>
</div>
<footer class="aui-dialog2-footer">
<div class="aui-dialog2-footer-actions">
<button id="create-guide-dialog-submit-button" class="aui-button aui-button-primary">OK</button>
<button id="create-guide-dialog-close-button" class="aui-button">close</button>
</div>
<div class="aui-dialog2-footer-hint"></div>
</footer>
</section>
</div>

javascript file my_plugin.js:

var counter = 0;
var initialised = false;
AJS.$(document).ready(function() {
var async = AJS.$("#async").val();
console.log("async:"+async);

if(async === "true"){
initialised = false;
}

console.log("initialised:"+initialised);
if(!initialised && AJS.$("#issueId").length !== 0){
initialised = true;
counter++;
console.log("counter:"+counter);

if(typeof AJS.$("#select1").data('select2') === typeof undefined)
AJS.$("#select1").auiSelect2();

if(typeof AJS.$("#select2").data('select2') === typeof undefined)
AJS.$("#select2").auiSelect2();

AJS.$("#create-guide-dialog-show-button").off('click');
AJS.$("#create-guide-dialog-show-button").on('click', function(e){
e.preventDefault();
AJS.dialog2("#create-guide-dialog").show();
});
}
});

java class com.my.plugin.jira.tabpanels.JiraIssueTabPanel:

package com.my.plugin.jira.tabpanels;
import com.atlassian.jira.plugin.issuetabpanel.*;
import java.util.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.my.plugin.dao.SelectADAO;
import com.my.plugin.dao.SelectBDAO;
import com.my.plugin.entity.*;

public class JiraIssueTabPanel extends AbstractIssueTabPanel2 implements IssueTabPanel2{
private static final Logger log = LoggerFactory.getLogger(JiraIssueTabPanel.class);
public JiraIssueTabPanel(){ }

@Override
public GetActionsReply getActions(GetActionsRequest request) {
final List<SelectA> select1 = SelectADAO.getSelectAByIDJira(request.issue().getId().toString());
final List<SelectB> select2 = SelectBDAO.getSelectBByIDJira(request.issue().getId().toString());
return GetActionsReply.create(new JiraIssueAction(request.isAsynchronous(), descriptor(), request.issue(), request.remoteUser(), select1, select2));
}

@Override
public ShowPanelReply showPanel(ShowPanelRequest request) {
boolean isShowing = true;
return ShowPanelReply.create(isShowing);
}
}

java action class com.my.plugin.jira.tabpanels.JiraIssueAction:

package com.my.plugin.jira.tabpanels;
import com.atlassian.jira.component.ComponentAccessor;
import com.atlassian.jira.issue.Issue;
import com.atlassian.jira.plugin.issuetabpanel.AbstractIssueAction;
import com.atlassian.jira.plugin.issuetabpanel.IssueTabPanelModuleDescriptor;
import java.util.*;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import com.atlassian.jira.template.VelocityTemplatingEngine;
import com.atlassian.jira.user.ApplicationUser;
import com.my.plugin.entity.*;
import static com.atlassian.jira.template.TemplateSources.file;

public class JiraIssueAction extends AbstractIssueAction {
private static final String PLUGIN_TEMPLATES = "templates/tabpanels/";
List<SelectA> select1 = new ArrayList<SelectA>();
List<SelectB> select2 = new ArrayList<SelectB>();
Issue issue;
ApplicationUser remoteUser;
Boolean async;

public JiraIssueAction(Boolean async, IssueTabPanelModuleDescriptor descriptor, Issue issue, ApplicationUser remoteUser, List<SelectA> select1, List<SelectB> select2){
super(descriptor);
this.async = async;
this.issue = issue;
this.remoteUser = remoteUser;
this.select1=select1;
this.select2=select2;
}

public Date getTimePerformed(){
return issue.getCreated();
}

protected void populateVelocityParams(Map params){
params.put("async",async);
params.put("issue",issue);
params.put("remoteUser",remoteUser);
params.put("select1",select1);
params.put("select2",select2);
}

public String getHtml() {
final String templateName = "jira-issue-tab-panel.vm";
final VelocityTemplatingEngine templatingEngine = ComponentAccessor.getComponent(VelocityTemplatingEngine.class);
final Map<String, Object> params = new HashMap(5);
populateVelocityParams(params);
return templatingEngine.render(file(PLUGIN_TEMPLATES + templateName)).applying(params).asHtml();
}
}

 

I had to use "initialised" variable to avoid some repeat case, but it still appeared problem...

 JIRA version:

<jira.version>7.6.2</jira.version>
<amps.version>6.3.14</amps.version>

Need your advice and helps,

Sincerely

1 answer

1 accepted

1 vote
Answer accepted
Daz
Atlassian Team
Atlassian Team members are employees working across the company in a wide variety of roles.
May 9, 2018

Hi @Chien-Hao Chen,

I see a few potential problems.

The first is that you're using two methods to load your script. The two methods I can see for your javascript being loaded:

  • You use the `atl.general` context in your `<web-resource>`, which means it will load on every page in Jira
  • You add `#include("js/my_plugin.js")` to your velocity file, too. I'm not sure whether this is working, but the fact that your code is executing from "VM" instead of a filename indicates to me that it probably is.

Since you load the JavaScript code through your VM file, you can probably remove the same file from your web-resource. That will prevent the code from running twice on page load.

The second, and probably more likely cause of your problem: whenever you switch between issues on the view issue page, your issue panel will be re-rendered. That means creating your dialog HTML again, and including + running your JavaScript again. If you have previously opened a dialog, then switched issues, you will have two elements in your DOM with the same ID for the dialog. As a result, it is possible that your dialog's HTML contents is being created several times.

Before I could provide you with an idea of a guaranteed fix, you should check the following things when the problem occurs:

  • How many elements have the following IDs in the DOM? You can check by running `document.querySelectorAll('[id="<the-id-to-check>"]').length`
    • select1
    • select2
    • create-guide-dialog
    • create-guide
    • issue_guide_body
  • If you use Chrome or Firefox's devtools, add a breakpoint to <body> to "break on subtree modifications", then click your button to open the dialog. You should see the dialog be constructed, shown, and its contents appear.
    • Do the contents appear all at once, or does one set of select components appear first, then the second set?

My guess as to how to fix the problem is that you will need to first detect when the view issue page's tabs are being rendered, then clean up any old dialog that was created for the previous issue view when that happens. Jira provides a global value called `JIRA.ViewIssueTabs.onTabReady` to help you here. Read these docs for more info: https://developer.atlassian.com/server/jira/platform/loading-issue-tab-panels-with-ajax/

Finally, this seems like a plugin development related question. We have a dedicated community for developers at https://community.developer.atlassian.com/. In future, if you ask questions there, they will likely be answered faster, since there are more developers watching those forums.

Good luck!

Daz

Suggest an answer

Log in or Sign up to answer