AtlasCamp 2013: Modernizing your Plugin UI

Post on 14-Jan-2015

139 views 0 download

Tags:

description

 

transcript

#atlascamp@atlassian

Jonathon Creenaune, Front End Architect, Atlassian

Modernizing YourPlugin UI

Build more features

Build more features

Make features better

Make features better

Beauty

Beauty

Interactivity

Beauty

<header class="aui-page-header">

  <div class="aui-page-header-inner">

    my header text

  </div>

</header>

AUI

<header class="aui-page-header">

  <div class="aui-page-header-inner">

    my header text

  </div>

</header>

Side-by-side

<header class="aui-page-header">

  <div class="aui-page-header-inner">

    my header text

  </div>

</header>

Side-by-side

{call aui.page.pageHeader}

  {param content}

    my header text

  {/param}

{/call}

AUI 5.0 is in

JIRA 6.0Confluence 5.0

Bamboo 4.3Stash 2.0

FE/CRU 3.0

Targeting non-soy platforms

URLs should be designed for the user, not just a side-effect of the technology you used to create a page

“”

/secure/MemeAction.jspa

/plugins/servlet/memes

/memes

/memes/{x}

/memes/create/

/memes/create/{x}

/memes/create

/secure/MemeAction.jspa

/secure/MemeAction.jspa?key={x}

/secure/MemeAction.jspa?upload=

/secure/MemeAction.jspa?createBaseImageKey={x}

/secure/MemeAction.jspa?create=

=>

<routing key="meme-pretty-urls" path="/memes">

<get from="" to="/secure/MemeAction.jspa"/>

<get from="/create/" to="/secure/MemeAction.jspa?upload="/>

<get from="/create/{key}" to="/secure/MemeAction.jspa?createBaseImageKey={key}"/>

<get from="/create" to="/secure/MemeAction.jspa?create="/>

<get from="/{key}" to="/secure/MemeAction.jspa?key={key}"/>

</routing>

Pretty URLs

P2: YesOld products:Yes

Connect:Yes

Pretty URLs

Interactivity

Getting code closer to the user

history.pushState(stateObject, title, url);

// Using history.js

History.pushState(null, null, url);

Pushstate

gallery.onSelectImage(function(key) {

// Add it to the URL

History.pushState(null, null, AJS.contextPath() + “/memes” + key);

// Load the hero image

hero.load($container, key);

});

Pushstate

History.Adapter.bind(window, 'statechange', function() {

var match;

if (match = getUrl().match(/^$/)) { // gallery

gallery.load($container);

}

else if (match = getUrl().match(/^\/(.*)$/)) { // hero

hero.load($container, match[1]);

}

});

Pushstate - back / forward

P2: YesOld products:Yes

Connect:Yes

Pushstate

Soy renders on server

and on client

<webwork1 key="meme-webwork" class="java.lang.Object">

<actions>

<action name="com.atlassian.meme.action.MemeAction" ...>

<view name="hero" type="soy">

:server-templates/meme.page.hero

</view>

<view name="gallery" type="soy">

:server-templates/meme.page.gallery

</view>

...

Render on server

$(function() {

var $container = getContainer();

hero.initEvents($container);

});

Render on server

<web-resource key="hero">

<transformation extension="soy">

<transformer key="soyTransformer" />

</transformation>

<resource type="download" name="hero.js" location="hero.soy"/>

...

Render on client

function render($container, key) {

$.get(getMemeUrl(key)).done(function(memeData) {

$container.html(meme.hero.main({

meme: memeData

}));

initEvents($container);

...

Render on client

P2: YesOld products: If soy available

Connect:JVM server

Render on client + server

// Bad

<script>

var myData = “${getMyData}”;

</script>

// Why? Because it’s an XSS hole

<script>

var myData = “</script><script>alert(‘naughty’);””;

</script>

Injecting Page Data

// In action

public String getSelectData() {

return ImmutableMap.of(

“baseImages”, getAllBaseImages(),

“baseImagesJson”, marshalAsJson(getAllBaseImages())

);

}

Injecting Page Data

// In template

<div class="base-images-json"

data-base-images-json="{$baseImagesJson}">

</div>

Injecting Page Data

// From javascript

var myData = AJS.$(".base-images-json").

data("base-images-json");

Accessing Page Data

P2: YesOld products:Yes

Connect:Yes

Page Data

Future ...

<web-resource key="blah">

  <data name="my-data" provider="com.acme.MyDataProvider" />

  <resource type="download" name="my-code.js" />

</web-resource>

Injecting Page Data

// pageBuilderService is injectable

pageBuilderService.getData().set("my-data-key", myJsonable);

Injecting Page Data

var data = AJS.data.get("my-plugin:blah:my-data");

Accessing Page Data

Main

Gallery

Hero

Create

Select

Form

Utils

Read files

Render Meme

define(“create/canvasDrawer”, function() {

...

return {

drawImage: function() {},

drawText: function() {},

getAsBase64: function() {}

}

});

Defining Modules

define(“create/main”, [“./formView”, “./canvasDrawer”],

function(formView, canvasDrawer) {

formView.onSubmit(function() {

canvasDrawer.drawImage(myImage);

save(canvasDrawer.getAsBase64);

});

});

Requiring Modules

almond.js

P2: YesOld products:Yes

Connect:Yes

JS modules

Amazon: For every 100ms increase in load time of Amazon.com decreased sales by 1%

Google: From 10 results in 0.4 seconds to to 30 results in 0.9 seconds decreased traffic and ad revenues by 20%

Google: An extra 500ms in loading time resulted in 20% drop in traffic.

Yahoo: 400ms slower page would see 5-9% more people leave before the page finished loading.

Main

Gallery

Hero

Create

Select

Form

Utils

Read Files

Render Meme

Main

Gallery

Hero

Create

Select

Form

Utils

Read Files

Render Meme

<web-resource key="create">

<resource type="download" name="create/main.js" location="create/main.js" />

<resource type="download" name="create/create.css" location="create/create.css" />

<resource type="download" name="create/create.js" location="create/create.soy" />

...

</web-resource>

Async Resource Loading

require("gallery/main").onSelectCreate(function() { WRM.require([ "wr!com.atlassian.atlassian-meme-generator:create" ]).done(function() { showCreateView(); });});

Async Resource Loading

P2: YesOld products:Yes

Connect:Other libs

Async Resource Loading

Beauty:

SoyPretty URLs

Interactivity:

PushstateServer and Client rendering

DataJS modules

Async resource loading

Thank you!

Party Starts at 19.00!SkyLounge, Oosterdoksstraat 4, 11th Floor