From the Atlassian Labs: FedEx Champions - Atlassian Summit 2010 - Lightning Talks
Better front-end development in Atlassian plugins
-
Upload
atlassian -
Category
Technology
-
view
3.232 -
download
4
description
Transcript of Better front-end development in Atlassian plugins
Tuesday, April 3, 2012
The road from back-end to front-end programming
Wojciech SeligaJIRA Development Team Lead, AtlassianCo-founder, Spartez
Better front-end development in Atlassian plugins
2Tuesday, April 3, 2012
• 4+ years with Atlassian• 6+ years doing Atlassian plugin development:
• JIRA Importers Plugin
• JIRA Drag & Drop Attachments Plugin
• JIRA Mail Plugin
• ScreenSnipe for JIRA, ScreenSnipe for Confluence ...
• Veteran of old-school web development (Java)
About me
3Tuesday, April 3, 2012
A bit of history
4Tuesday, April 3, 2012
• 2002 - 2006 - Awesome UI, Web 1.0
A bit of history
4Tuesday, April 3, 2012
• 2002 - 2006 - Awesome UI, Web 1.0WebWork, XWork, JSP, Velocity, Freemarker
A bit of history
4Tuesday, April 3, 2012
• 2002 - 2006 - Awesome UI, Web 1.0WebWork, XWork, JSP, Velocity, Freemarker
• 2006 - 2009 - Features, features, features!
A bit of history
4Tuesday, April 3, 2012
• 2002 - 2006 - Awesome UI, Web 1.0WebWork, XWork, JSP, Velocity, Freemarker
• 2006 - 2009 - Features, features, features!Mostly back-end technologies
A bit of history
4Tuesday, April 3, 2012
• 2002 - 2006 - Awesome UI, Web 1.0WebWork, XWork, JSP, Velocity, Freemarker
• 2006 - 2009 - Features, features, features!Mostly back-end technologies
• 2009 - now - new Atlassian UI, Web 2.0+
A bit of history
4Tuesday, April 3, 2012
• Confluence 3.0+, JIRA 3.13.5+• Forms, Controls, Tabs, Inline Dialogs, ..., AJS
Evolution Step 1 - AUI
5Tuesday, April 3, 2012
• Confluence 3.0+, JIRA 3.13.5+• Forms, Controls, Tabs, Inline Dialogs, ..., AJS
Evolution Step 1 - AUI
5Tuesday, April 3, 2012
• Confluence 3.0+, JIRA 3.13.5+• Forms, Controls, Tabs, Inline Dialogs, ..., AJS
Evolution Step 1 - AUI
5Tuesday, April 3, 2012
• Confluence 3.0+, JIRA 3.13.5+• Forms, Controls, Tabs, Inline Dialogs, ..., AJS
Evolution Step 1 - AUI
5Tuesday, April 3, 2012
• Plugin Framework v2 only (JIRA 4+, Confluence 3.1+)• Easier AJAX for plugin developers unleashed
Evolution Step 2 - REST
6
<rest key="helloWorldRest" path="/helloworld" version="1.0"> <description>Hello world sample.</description></rest>
• World of Java annotations (Jersey)
Tuesday, April 3, 2012
7
@Path ("priority")@AnonymousAllowed@Consumes ({ MediaType.APPLICATION_JSON })@Produces ({ MediaType.APPLICATION_JSON })public class PriorityResource { // injected dependencies and the constructor here ... @GET @Path ("{id}") public Response getPriority(@PathParam ("id") final String id){ final Priority priority = constManager.getPriorityObject(id); if (priority == null) { throw new NotFoundWebException(ErrorCollection.of( i18n.getText("rest.priority.error.not.found", id))); }
return Response.ok(PriorityJsonBean.fullBean(priority, baseUrls)) .cacheControl(never()).build(); }}
Tuesday, April 3, 2012
injectable JS & CSS + REST = WIN
8Tuesday, April 3, 2012
• Confluence 2.10+, JIRA 4.2+• Easy resource injection to popular destinations• Easy to define own contexts
Evolution Step 3 (2010)web resource contexts
9
<web-resource key="quick-edit-issue"> <context>jira.view.issue</context> <context>jira.navigator.advanced</context> <context>jira.navigator.simple</context>
<!-- ... --> </web-resource>
Tuesday, April 3, 2012
Evolution Step 4 (2010)web-resource-transformer
10
<web-resource-transformer key="my-key" class="fqcn.must.implement.WebResourceTransformer"/>
Tuesday, April 3, 2012
Evolution Step 4 (2010)web-resource-transformer
10
<web-resource-transformer key="my-key" class="fqcn.must.implement.WebResourceTransformer"/>
public interface WebResourceTransformer { DownloadableResource transform(Element configElement, ResourceLocation location, String filePath, DownloadableResource nextResource);}
Tuesday, April 3, 2012
web-resource-transformers
11Tuesday, April 3, 2012
• I18n
web-resource-transformers
11Tuesday, April 3, 2012
• I18n• L&F
web-resource-transformers
11Tuesday, April 3, 2012
• I18n• L&F• context path
web-resource-transformers
11Tuesday, April 3, 2012
• I18n• L&F• context path• SASS, LESS
web-resource-transformers
11Tuesday, April 3, 2012
• I18n• L&F• context path• SASS, LESS• Soy
web-resource-transformers
11Tuesday, April 3, 2012
I18N
12Tuesday, April 3, 2012
I18N
12
hello.world=Hello Worldfrom.atlascamp=from AtlasCamp {0}
i18n resource file defined in atlassian-plugin.xml
Tuesday, April 3, 2012
I18N - web resource transformation definition
13Tuesday, April 3, 2012
I18N - web resource transformation definition
13
<resource name="ourname" type="i18n" location="path/to/i18n/properties/file/no/ext"/>
<web-resource key="our-key"> <dependency>com.atlassian.auiplugin:ajs</dependency> <transformation extension="js"> <transformer key="jsI18n"/> </transformation> <resource type="download" name="filename.js" location="path/to/filename.js"/></web-resource>
Tuesday, April 3, 2012
I18N transformation
14Tuesday, April 3, 2012
I18N transformation
14
var helloText = AJS.I18n.getText("hello.world") + " " + AJS.I18n.getText("from.atlascamp", 2012)
Tuesday, April 3, 2012
I18N transformation
14
var helloText = AJS.I18n.getText("hello.world") + " " + AJS.I18n.getText("from.atlascamp", 2012)
Tuesday, April 3, 2012
I18N transformation
14
var helloText = "Hello World" + " " + AJS.format("from AtlasCamp {0}", 2012)
var helloText = AJS.I18n.getText("hello.world") + " " + AJS.I18n.getText("from.atlascamp", 2012)
Tuesday, April 3, 2012
15
Times of Hacking
Tuesday, April 3, 2012
15
Times of Hacking
JQuery
Prototype
underscore.js Javascript hacks
JQuery JQuery
JQueryJQuery
Javascript hacks
Javascript hacks
JQuery
JQuery
underscore.js
freestyle AJAX
freestyle AJAX
JQuery JQuery
JQuery
JQuery
JQuery
Javascript hacks
JQuery
JQuery
JQuery
JQuery
JQueryJQuery
JQuery
JQuery
JQueryJQuery
JQueryJQuery
JQuery JQuery
Javascript hacksJQueryJQuery
JQuery
JQuery
JQuery
JQuery JQuery
JQuery
underscore.js
Javascript hacks
JQuery
JQuery
JQuery
JQuery
JQuery
Tuesday, April 3, 2012
It's all too easy to create JavaScript applications
that end up as tangled piles of jQuery selectors and
callbacks, all trying frantically to keep data in sync
between the HTML UI, your JavaScript logic, and
the database on your server. For rich client-side
applications, a more structured approach is often
helpful..
Introduction to Backbone.js”
“
16Tuesday, April 3, 2012
Evolution Step 5 (2011)
17
Structure on the client side
Tuesday, April 3, 2012
Evolution Step 5 (2011)
17
Structure on the client side
For speed, beauty and maintainability
Tuesday, April 3, 2012
Evolution Step 5 (2011)
17
Structure on the client side
For speed, beauty and maintainability
FTW
Tuesday, April 3, 2012
• SproutCore• Sammy.js• Spine.js• Cappucino• Javascript MVC
• Ember.js• Angular.js• Batman.js• Mustache• Handlebars
18
More structured approach in JS
• Soy (Google Closure Templates)
• Backbone.js
Tuesday, April 3, 2012
19
More structured approach in JS
• Soy (Google Closure Templates)
• Backbone.js
Tuesday, April 3, 2012
19
More structured approach in JS
• Soy (Google Closure Templates)
• Backbone.js
MVC
Tuesday, April 3, 2012
19
More structured approach in JS
• Soy (Google Closure Templates)
• Backbone.jsM
VC
Tuesday, April 3, 2012
19
More structured approach in JS
• Soy (Google Closure Templates)
• Backbone.jsMV
C
Tuesday, April 3, 2012
19
More structured approach in JS
• Soy (Google Closure Templates)
• Backbone.jsMV
C
Tuesday, April 3, 2012
19
More structured approach in JS
• Soy (Google Closure Templates)
• Backbone.js
Tuesday, April 3, 2012
19
More structured approach in JS
• Soy (Google Closure Templates)
Tuesday, April 3, 2012
19
More structured approach in JS
Tuesday, April 3, 2012
Our Road to Soy
20Tuesday, April 3, 2012
• AJS.template
Our Road to Soy
20Tuesday, April 3, 2012
• AJS.template• Mustache
Our Road to Soy
20Tuesday, April 3, 2012
• AJS.template• Mustache• Soy
Our Road to Soy
20Tuesday, April 3, 2012
Soy Features
21Tuesday, April 3, 2012
• Simplicity
Soy Features
21Tuesday, April 3, 2012
• Simplicity• Logic and display separation
Soy Features
21Tuesday, April 3, 2012
• Simplicity• Logic and display separation• Client and server side (Javascript and Java)
Soy Features
21Tuesday, April 3, 2012
• Simplicity• Logic and display separation• Client and server side (Javascript and Java)• Client-side speed
Soy Features
21Tuesday, April 3, 2012
• Simplicity• Logic and display separation• Client and server side (Javascript and Java)• Client-side speed• Security (auto-escaping)
Soy Features
21Tuesday, April 3, 2012
• Simplicity• Logic and display separation• Client and server side (Javascript and Java)• Client-side speed• Security (auto-escaping)• Battle-tested by Google
Soy Features
21Tuesday, April 3, 2012
Soy - Example
22
{namespace examples.simple}/** * Greets a person using "Hello" by default. * @param name The name of the person. * @param? greetingWord Optional greeting word to use instead of "Hello". */{template .helloName} {if not $greetingWord} Hello {$name}! {else} {$greetingWord} {$name}! {/if}{/template}
Tuesday, April 3, 2012
Soy Syntax - Types
23
Type Examples
null null
Boolean false, true
Integer 123, -857, 0x123
Float 0.5, 123.0, 10.1e4
String 'Atlassian', '', 'foo-bar'
List [], [1, 'two', 3, [4, 'five']]
Map [:], ['key': 'value', 'key2': 'value2']
Tuesday, April 3, 2012
Soy Syntax - Operators
24
• - (unary) not• * / %• + - (binary)• < > <= >=• == !=• and• or• ?: (ternary)
Tuesday, April 3, 2012
Soy - Commands
25Tuesday, April 3, 2012
• {template}{/template}
Soy - Commands
25Tuesday, April 3, 2012
• {template}{/template}• {literal}{/literal}
Soy - Commands
25Tuesday, April 3, 2012
• {template}{/template}• {literal}{/literal}• {print <expression>}
Soy - Commands
25Tuesday, April 3, 2012
• {template}{/template}• {literal}{/literal}• {print <expression>}• {<expression>}
Soy - Commands
25Tuesday, April 3, 2012
• {template}{/template}• {literal}{/literal}• {print <expression>}• {<expression>}• {if <expression>},
{elseif}, {else}, {/if}
Soy - Commands
25Tuesday, April 3, 2012
• {template}{/template}• {literal}{/literal}• {print <expression>}• {<expression>}• {if <expression>},
{elseif}, {else}, {/if}
• {foreach}, {ifempty}, {/foreach}
Soy - Commands
25Tuesday, April 3, 2012
• {template}{/template}• {literal}{/literal}• {print <expression>}• {<expression>}• {if <expression>},
{elseif}, {else}, {/if}
• {foreach}, {ifempty}, {/foreach}• {for}, {/for}
Soy - Commands
25Tuesday, April 3, 2012
• {template}{/template}• {literal}{/literal}• {print <expression>}• {<expression>}• {if <expression>},
{elseif}, {else}, {/if}
• {foreach}, {ifempty}, {/foreach}• {for}, {/for}• {call}, {/call}, {param}, {/param}
Soy - Commands
25Tuesday, April 3, 2012
• {template}{/template}• {literal}{/literal}• {print <expression>}• {<expression>}• {if <expression>},
{elseif}, {else}, {/if}
• {foreach}, {ifempty}, {/foreach}• {for}, {/for}• {call}, {/call}, {param}, {/param}• {sp}, {\n}, {lb}, {rb}
Soy - Commands
25Tuesday, April 3, 2012
Soy - defining variables
26Tuesday, April 3, 2012
Soy - defining variables
26Tuesday, April 3, 2012
Soy - defining variables
26Keep business logic away from view!
Not Supported!Tuesday, April 3, 2012
•{getText('i18n-key', ....)
•{contextPath}
•{$data|truncate:30}
Useful functions
27Tuesday, April 3, 2012
28
Soy Javascript Compilation
{namespace JIRA.Templates.Demo}/*** Simplest Hello world demo* @param name*/{template .helloWorld}<div>Hello World, {$name}</div>{/template}
Tuesday, April 3, 2012
28Tuesday, April 3, 2012
29Tuesday, April 3, 2012
29
// This file was automatically generated from demo.soy.// Please don't edit this file by hand.
if (typeof JIRA == 'undefined') { var JIRA = {}; }if (typeof JIRA.Templates == 'undefined') { JIRA.Templates = {}; }if (typeof JIRA.Templates.Demo == 'undefined') { JIRA.Templates.Demo = {}; }
JIRA.Templates.Demo.helloWorld = function(opt_data, opt_sb) { var output = opt_sb || new soy.StringBuilder(); output.append('<div>Hello World, ',
soy.$$escapeHtml(opt_data.name), '</div>'); return opt_sb ? '' : output.toString();};
Tuesday, April 3, 2012
29
// This file was automatically generated from demo.soy.// Please don't edit this file by hand.
if (typeof JIRA == 'undefined') { var JIRA = {}; }if (typeof JIRA.Templates == 'undefined') { JIRA.Templates = {}; }if (typeof JIRA.Templates.Demo == 'undefined') { JIRA.Templates.Demo = {}; }
JIRA.Templates.Demo.helloWorld = function(opt_data, opt_sb) { var output = opt_sb || new soy.StringBuilder(); output.append('<div>Hello World, ',
soy.$$escapeHtml(opt_data.name), '</div>'); return opt_sb ? '' : output.toString();};
Tuesday, April 3, 2012
• implicit by default to HTML escaping• {namespace com.example autoescape="XXX"}
XXX may be true, false, contextual• disable for a single case with {$templateData|noAutoescape}
• sanitized data
Auto-escaping
30Tuesday, April 3, 2012
Contextual Auto-escaping
31Tuesday, April 3, 2012
Contextual Auto-escaping
31
/** * @param name */{template .helloWorld autoescape="contextual"}<a href="/Demo?name={$name}" onclick="var x = {$name}">{$name}</a>{/template}
Tuesday, April 3, 2012
Contextual Auto-escaping
31
/** * @param name */{template .helloWorld autoescape="contextual"}<a href="/Demo?name={$name}" onclick="var x = {$name}">{$name}</a>{/template}
Tuesday, April 3, 2012
Contextual Auto-escaping
31
/** * @param name */{template .helloWorld autoescape="contextual"}<a href="/Demo?name={$name}" onclick="var x = {$name}">{$name}</a>{/template}
{“name”: "><script>alert(\"x&xx\")</script>"}
Tuesday, April 3, 2012
Contextual Auto-escaping
31
<a href="/Demo?name=%3E%3Cscript%3Ealert%28%22x%26xx%22%29%3C%2Fscript%3E" onclick="var x = '\x3e\x3cscript\x3ealert(\x22x\x26xx\x22)\x3c\/script\x3e'">><script>alert("x&xx")</script></a>
/** * @param name */{template .helloWorld autoescape="contextual"}<a href="/Demo?name={$name}" onclick="var x = {$name}">{$name}</a>{/template}
{“name”: "><script>alert(\"x&xx\")</script>"}
Tuesday, April 3, 2012
Contextual Auto-escaping
31
<a href="/Demo?name=%3E%3Cscript%3Ealert%28%22x%26xx%22%29%3C%2Fscript%3E" onclick="var x = '\x3e\x3cscript\x3ealert(\x22x\x26xx\x22)\x3c\/script\x3e'">><script>alert("x&xx")</script></a>
/** * @param name */{template .helloWorld autoescape="contextual"}<a href="/Demo?name={$name}" onclick="var x = {$name}">{$name}</a>{/template}
{“name”: "><script>alert(\"x&xx\")</script>"}
Tuesday, April 3, 2012
Contextual Auto-escaping
31
<a href="/Demo?name=%3E%3Cscript%3Ealert%28%22x%26xx%22%29%3C%2Fscript%3E" onclick="var x = '\x3e\x3cscript\x3ealert(\x22x\x26xx\x22)\x3c\/script\x3e'">><script>alert("x&xx")</script></a>
/** * @param name */{template .helloWorld autoescape="contextual"}<a href="/Demo?name={$name}" onclick="var x = {$name}">{$name}</a>{/template}
{“name”: "><script>alert(\"x&xx\")</script>"}
Tuesday, April 3, 2012
Contextual Auto-escaping
31
<a href="/Demo?name=%3E%3Cscript%3Ealert%28%22x%26xx%22%29%3C%2Fscript%3E" onclick="var x = '\x3e\x3cscript\x3ealert(\x22x\x26xx\x22)\x3c\/script\x3e'">><script>alert("x&xx")</script></a>
/** * @param name */{template .helloWorld autoescape="contextual"}<a href="/Demo?name={$name}" onclick="var x = {$name}">{$name}</a>{/template}
{“name”: "><script>alert(\"x&xx\")</script>"}
Tuesday, April 3, 2012
Contextual Auto-escaping
31
<a href="/Demo?name=%3E%3Cscript%3Ealert%28%22x%26xx%22%29%3C%2Fscript%3E" onclick="var x = '\x3e\x3cscript\x3ealert(\x22x\x26xx\x22)\x3c\/script\x3e'">><script>alert("x&xx")</script></a>
/** * @param name */{template .helloWorld autoescape="contextual"}<a href="/Demo?name={$name}" onclick="var x = {$name}">{$name}</a>{/template}
{“name”: "><script>alert(\"x&xx\")</script>"}
Tuesday, April 3, 2012
• JIRA (gadgets, new project administration, mails, ...)• GreenHopper• PAC• ...
Soy @ Atlassian
32Tuesday, April 3, 2012
Soy and Atlassian Plugins
33
<web-resource key="my-key">
<transformation extension="soy"> <transformer key="soyTransformer"/> </transformation>
<resource type="download" name="my-name.js" location="path/to/my/template.soy"/></web-resource>
Tuesday, April 3, 2012
• Atlassian Plugin Framework favours Velocity• SoyTemplateRenderer/SoyTemplateRendererProvider • DefaultVelocityContextProvider (jira-core)• SoyData, SoyDataList, SoyDataMap,
SoyData.createFromExistingData()
Server-side Soy
34Tuesday, April 3, 2012
Soy - Coding Example
35Tuesday, April 3, 2012
• {call} in Soy• web-resource <dependency>
Soy - Template Library
36Tuesday, April 3, 2012
Backbone.js
37Tuesday, April 3, 2012
• Event-driven• Models• Views (responsible for keeping markup in sync with
model)
Backbone.js
38Tuesday, April 3, 2012
39
Our story with backbone.js
Tuesday, April 3, 2012
• Version/Component/People management in JIRA (Ignite) - 2011
39
Our story with backbone.js
Tuesday, April 3, 2012
• Version/Component/People management in JIRA (Ignite) - 2011
• JIRA Importers Plugin, JIRA Mail Plugin - 2011
39
Our story with backbone.js
Tuesday, April 3, 2012
• Version/Component/People management in JIRA (Ignite) - 2011
• JIRA Importers Plugin, JIRA Mail Plugin - 2011• RAB - 2011
39
Our story with backbone.js
Tuesday, April 3, 2012
• Version/Component/People management in JIRA (Ignite) - 2011
• JIRA Importers Plugin, JIRA Mail Plugin - 2011• RAB - 2011• GH RapidBoard, New Issue Nav - 2011, 2012
39
Our story with backbone.js
Tuesday, April 3, 2012
Why Backbone
40Tuesday, April 3, 2012
Why Backbone
40
backbone.js
Tuesday, April 3, 2012
JIRA is a backbone
Why Backbone
40
backbone.js
Tuesday, April 3, 2012
JIRA is a backbone
Why Backbone
40
backbone.js
Tuesday, April 3, 2012
Why Backbone.js
41Tuesday, April 3, 2012
• Small
Why Backbone.js
41Tuesday, April 3, 2012
• Small• Flexible
Why Backbone.js
41Tuesday, April 3, 2012
• Small• Flexible• Does not impose any templating technologies
Why Backbone.js
41Tuesday, April 3, 2012
• Small• Flexible• Does not impose any templating technologies• Well documented
Why Backbone.js
41Tuesday, April 3, 2012
• Small• Flexible• Does not impose any templating technologies• Well documented• Popular
Why Backbone.js
41Tuesday, April 3, 2012
• Small• Flexible• Does not impose any templating technologies• Well documented• Popular• Liked by us
Why Backbone.js
41Tuesday, April 3, 2012
42Tuesday, April 3, 2012
42
DOM / Markup
Tuesday, April 3, 2012
42
DOM / Markup
Javascript
Tuesday, April 3, 2012
42
DOM / Markup
Javascript
Tuesday, April 3, 2012
42
DOM / Markup
Javascript
JQuery
Tuesday, April 3, 2012
42
DOM / Markup
Javascript
JQuery
Tuesday, April 3, 2012
42
DOM / Markup
Javascript
JQueryAUI
Tuesday, April 3, 2012
42
DOM / Markup
Javascript
JQueryAUI
Tuesday, April 3, 2012
42
DOM / Markup
Javascript
JQueryAUI
Tuesday, April 3, 2012
42
DOM / Markup
Javascript
JQueryAUI
Tuesday, April 3, 2012
42
DOM / Markup
Soy (client)
Javascript
JQueryAUI
Tuesday, April 3, 2012
42
DOM / Markup
Soy (client)
Javascript
JQueryAUI
Tuesday, April 3, 2012
42
DOM / Markup
Soy (client)
Javascript
JQueryAUI
Tuesday, April 3, 2012
42
DOM / Markup
Soy (client)
Javascript
JQueryAUI
Backbone.js
Tuesday, April 3, 2012
42
DOM / Markup
Soy (client)
Javascript
JQueryAUI
Backbone.js
Tuesday, April 3, 2012
42
DOM / Markup
Soy (client)
Javascript
JQueryAUI
Backbone.js
Tuesday, April 3, 2012
42
DOM / Markup
Soy (client)
Javascript
JQueryAUI
Backbone.js
Tuesday, April 3, 2012
42
DOM / Markup
Soy (client)
Javascript
JQueryAUI
Backbone.js
Tuesday, April 3, 2012
42
DOM / Markup
Soy (client)
Javascript
JQueryAUI
REST API Backbone.js
Tuesday, April 3, 2012
42
DOM / Markup
Soy (client)
Javascript
JQueryAUI
REST API Backbone.js
Tuesday, April 3, 2012
42
DOM / Markup
Soy (client)
Javascript
JQueryAUI
REST API Backbone.js
ServicesManagers
Tuesday, April 3, 2012
42
DOM / Markup
Soy (client)
Javascript
JQueryAUI
REST API Backbone.js
ServicesManagers
Tuesday, April 3, 2012
• Easier, more powerful and efficient web resource transformations
• Better support for Soy on the server side (like Velocity or FreeMarker)
• (?) Dynamic injection of needed resources on-the-fly (inline dialogs)
Possible Future
43Tuesday, April 3, 2012
44
Looking back...
Tuesday, April 3, 2012
44
Looking back...
2.5 years ago...
Tuesday, April 3, 2012
44
Looking back...
most of this stuff was not possible2.5 years ago...
Tuesday, April 3, 2012
Don’t underestimate the power of the client-side
programming. Time to learn Javascript and
related frameworks, you old Java fellow
Master Joda, Javascript convert
”“
45Tuesday, April 3, 2012
#atlascamp
TAKE-AWAYS
Atlassian is moving fast to client-side programming.
Technology is there. Are you ready?“ ”
46Tuesday, April 3, 2012
Thank you!
Tuesday, April 3, 2012