Displaying a custom dialog in Jira

Hello,

I’m trying to port a Cloud plugin I made, and to do that I need to display a dialog in response to both a web item and a normal link.

What I’m trying to achieve

I need to display a custom dialog with custom forms inside it. The “Submit” button should store custom JS-defined data in an issue. (see below for a screenshot)

So a basic use case would be:

  • The user clicks on a normal link (which is in an issue webpanel) or a webitem (which is in opsbar-operations)
  • The dialog opens
  • The user can filter on a specific DataGalaxy project/version
  • The user has to search for a DataGalaxy object and select one (pressing Enter should search, not close the dialog)
  • The user clicks Submit, and the new object is linked to the issue

I’m currently working on displaying the dialog properly, data storage will come later.

First try with webwork

I tried to use a webwork to achieve my goal:

	<webwork1 key="dg-issue-button-action" name="Link DG Objects" class="java.lang.Object">
		<actions>
			<action name="com.datagalaxy.app.webwork.LinkObjects" alias="LinkObjects">
				<view name="input">/templates/issue_dialog.vm</view>
			</action>
		</actions>
	</webwork1>

	<web-section key="dg-issue-button-section" location="opsbar-operations" weight="900">
		<label key="datagalaxy.issue.section.label" />
	</web-section>

	<web-item key="dg-issue-button" section="dg-issue-button-section" weight="10">
		<label key="datagalaxy.issue.button.label" />
		<tooltip key="datagalaxy.issue.button.tooltip" />
		<link linkId="dg-issue-dialog-link">/secure/LinkObjects!default.jspa?id=${issue.id}</link>
		<styleClass>trigger-dialog</styleClass>
	</web-item>

This is my JiraWebActionSupport subclass:

package com.datagalaxy.app.webwork;

import com.atlassian.jira.web.action.JiraWebActionSupport;

@SuppressWarnings("serial")
public class LinkObjects extends JiraWebActionSupport
{
	public LinkObjects() {
	}

	@Override
	public String doDefault() throws Exception {
		return INPUT;
	}

	// @Override
	// protected void doValidation() {
	// }

	@Override
	public String doExecute() throws Exception {
		// return returnCompleteWithInlineRedirect("/browse/" + getIssueObject().getKey());
		return INPUT; // INPUT doesn't close the dialog, but if i use SUCCESS instead, the "Submit" button will cause a failure
	}
}

And this is the JS code needed to display the dialog properly (but only for the webitem, the normal link works properly without it, even if both uses “trigger-dialog” CSS class, maybe related to this issue):

(function ($) {
	new AJS.FormPopup({
		id: "dg-issue-dialog",
		trigger: "#dg-issue-dialog-link"
	});
})(AJS.$ || jQuery);

But even if my dialog is rendering properly now, I have many issues with the “Submit” button.

My dialog uses two different forms, let’s call them “Subform 1” and “Subform 2”, one is used to filter on projects and versions, the other is used to search objects over DataGalaxy API.

But the dialog itself is a form, and nested forms won’t work, so how am I supposed to handle this?

My dialog renders like so:

screenshot-20190405160712
The first encountered button will be the submit button for all the form, so here the “Search…” button will act as the “Submit” button of the dialog form (“Form 1”), which isn’t the behaviour I expected.

Is there a way to display a non-form Dialog with a webwork?

I tried to move “Form 1” to the bottom of the template, so that Subforms 1 and 2 aren’t nested anymore. That fixes the “Submit” button style, but the submit button fails to find my action. This is the form:

<form class="aui" action="LinkObjects.jspa" method="post">
	<input type="hidden" name="atl_token" value="${atl_token}" />
	<input type="hidden" name="id" value="$action.id" />

	<div class="buttons-container content-footer">
		<div class="buttons">
			<a href="#" class="cancel">$i18n.getText('datagalaxy.issue.dialog.cancel')</a>
			<input class="button" type="submit" value="$i18n.getText('datagalaxy.issue.dialog.submit')" />
		</div>
	</div>
</form>

When all my content is inside the form, it manages to recognize “LinkObjects.jspa” as “{baseurl}/secure/LinkObjects.jspa", but when I separate this form and put it at the bottom of my template, the submit button will try to send me to "{baseurl}/browse/LinkObjects.jspa”. It’s weird, but ok.

If replace  action="LinkObject.jspa" by action="${baseurl}/secure/LinkObjects.jspa" and click on “Submit”, I get a 404…

And there’s better: the “Search…” button will now act as a “Submit” button, but it won’t do any action beside blocking both buttons. And it won’t run the JS function contained in its “onclick” attribute.

Second try with servlets + AUI Dialog2

Code

The relevant part in atlassian-plugin.xml:

	<servlet key="dg-issue-dialog-servlet" class="com.datagalaxy.app.servlet.DialogServlet">
		<url-pattern>/datagalaxy/dialog</url-pattern>
	</servlet>

And this is my servlet:

package com.datagalaxy.app.servlet;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import java.net.URI;

import com.atlassian.sal.api.auth.LoginUriProvider;
import com.atlassian.sal.api.user.UserManager;
import com.atlassian.templaterenderer.TemplateRenderer;

import javax.inject.Inject;
import com.atlassian.plugin.spring.scanner.annotation.component.Scanned;
import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport;

@Scanned
@SuppressWarnings("serial")
public class DialogServlet extends HttpServlet
{
	@ComponentImport
	private final UserManager userManager;
	@ComponentImport
	private final LoginUriProvider loginUriProvider;
	@ComponentImport
	private final TemplateRenderer renderer;

	@Inject
	public DialogServlet(UserManager userManager, LoginUriProvider loginUriProvider, TemplateRenderer renderer)
	{
		this.userManager = userManager;
		this.loginUriProvider = loginUriProvider;
		this.renderer = renderer;
	}

	@Override
	public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
	{
		String username = userManager.getRemoteUsername(request);
		if (username == null) {
			redirectToLogin(request, response);
			return;
		}

		response.setContentType("text/html;charset=utf-8");
		renderer.render("templates/issue_dialog.vm", response.getWriter());
	}

	private void redirectToLogin(HttpServletRequest request, HttpServletResponse response) throws IOException
	{
		response.sendRedirect(loginUriProvider.getLoginUri(getUri(request)).toASCIIString());
	}

	private URI getUri(HttpServletRequest request)
	{
		StringBuffer builder = request.getRequestURL();
		if (request.getQueryString() != null) {
			builder.append("?");
			builder.append(request.getQueryString());
		}
		return URI.create(builder.toString());
	}
}

Actual behaviour

I tried to use the example for AUI Dialog 2 from here: https://docs.atlassian.com/aui/7.8.3/docs/dialog2.html

What I’m expecting:

screenshot-20190405170545

What I get with the same code (notice the close link is outside the dialog):

screenshot-20190405170537

What I get when I remove the search bar + the close button and add my content in the dialog:

screenshot-20190405171045

Why is the rendering cropped like this? Why is there two distinct headers in the dialog?

Conclusion

I really didn’t think it would be so hard to display a custom dialog in Jira Server, compared to Jira Cloud. Why things need to be so complex for such a basic task?

Thanks in advance,
Quentin Bazin

1 Like

Hi, do I understand correctly that you are trying to do server side “filtering/searching” in a dialog before submitting (and thereby closing) the dialog?

  1. Are the dropdowns supposed to be pre-populated (not recommended for large scale) /on-demand?
  2. Is the “search” button supposed to dynamically update the dialog contents?

You can achieve

  1. pre-populate - using webwork action while rendering velocity template.
    on-demand (asynchronous): use AUI select src attribute with a rest resource for loading of options.

  2. you’ll need to write JavaScript that renders the required content using REST endpoint response upon search.

Once this is done you can use “Submit” button to hit doExecute() of your webwork action that saves the data and closes dialog.

For the above flow, there should be only one form and one “submit” type button (if any other buttons trigger form submission you’ll have to capture form submit event using Javascript and preventDefault()).
Let me know if you need more info regarding any of the above.

P.S: Webwork is an old framework and it’s not primarily designed to update contents/fetch data without reloading the page (form submission).
So basically you can use it for the initial rendering of the dialog and submission of the form data to server. You’ll have to bridge the gap between that using JavaScript, in case of the dropdowns it’s provided from AUI, in case of “search results” it seems like your plugins specific logic :slight_smile:

Hi,

Actually, since I’m making a port of my own Cloud plugin, all the JS required for the dialog is already written.

The only thing I’m looking for is: how to replicate the same dialog behaviour than in my Cloud plugin?

In my Cloud plugin things happens this way:

  • The user clicks on DataGalaxy logo
  • The dialog opens
  • The user can filter (if the user presses Enter while one aui-select is focused, this will send the filtering form, so the filtering will take action)
  • The user can search (if the user presses Enter while the search field is focused, this will process the search and display the results)
  • If the user clicks on “Submit” without selecting an object in the search results, the dialog won’t close
  • If the user clicks on “Submit” after selecting an object, the selected object is stored against the issue

I’ve almost replicated the dialog in Jira, but as my first message explained, I’m unable to do it correctly.

With webwork, since the dialog is a form itself, pressing Enter will act on “Submit” button instead of “Search…” when the search field is selected, and that’s a big problem.

That’s why I tried to use AUI Dialog2 instead since I can handle everything from JS that way, but when I tried, as I explained above, the dialog won’t render properly.

So to summarize:

  • I can’t use a webwork since it needs the dialog to be a form, and, as a consequence, will break pressing Enter in the search field for my users
  • I can’t use a servlet + AUI Dialog2 since the dialog isn’t rendering properly for an obscure reason

If I can use a webwork without the dialog being a form, or if I find a way to fix dialog rendering with AUI Dialog2, this would fix my issue, but I haven’t found a way to do any of this yet.

Thanks in advance

Hi,

Cloud and server use different frontend components, they are not fully compatible with each other…

Webwork doesn’t require its view to be a form. A form will help you easily submit the data to webwork action though, but you can also do the same with AJAX or a simple link.

while working with a form, you can prevent from submission from enter key using something like this:

$('#my-form-id').find('.input').keypress(function(e) {
    if ( e.which == 13 ) // Enter key = keycode 13
    {
        event.preventDefault();

        $(this).next().focus(); // or do something else you want

        return false;
    }
});

Or the equivalent in whichever JS library your’re using :wink:
Let me know if it helps,
Best regards

1 Like

Hello @tjoy,

I managed to get my dialog working by using only AUI Dialog2, without webwork or servlet classes.

I had to merge my issue panel + my dialog in the same velocity template though.

Anyway, thanks for your help,
Best regards

1 Like

Hi quentin.bazin,

Please explain how you merge issue panel with dialog , it is really help me a lot open my dialog in same URL .

Looking for a positive feedback :slight_smile:
Thanks & Regards,
Amit