Blossom Q&A Webinar

60
Blossom Q&A Webinar 1 Monday, September 30, 13

description

Webinar with Tobias Mattsson, Lead developer of Blossom, the Spring integration for Magnolia CMS. Tobias answers submitted questions and gives a short introduction to the module and outlines what's new in the 3.0 update.

Transcript of Blossom Q&A Webinar

Page 1: Blossom Q&A Webinar

Blossom Q&A Webinar

1Monday, September 30, 13

Page 2: Blossom Q&A Webinar

2

Zak GreantCommunity Manager, Magnolia

Monday, September 30, 13

Page 3: Blossom Q&A Webinar

Questions?Use the questions toolbuilt into the GoToWebinarclient.

3Monday, September 30, 13

Page 4: Blossom Q&A Webinar

4

forums.magnolia-cms.com

Monday, September 30, 13

Page 5: Blossom Q&A Webinar

#blossomqa

5Monday, September 30, 13

Page 6: Blossom Q&A Webinar

Sr. Software Engineer, MagnoliaLead developer of Magnolia’s Spring integration

Spring Framework user since 2005

Tobias Mattsson

6Monday, September 30, 13

Page 7: Blossom Q&A Webinar

Blossom 3.0

7

Update for Magnolia 5 seriesRequires Magnolia 5.1Release candidate availableFinal release next week

Monday, September 30, 13

Page 8: Blossom Q&A Webinar

Magnolia + Spring = Blossom 8Monday, September 30, 13

Page 9: Blossom Q&A Webinar

@Template

9Monday, September 30, 13

Page 10: Blossom Q&A Webinar

TEMPLATEREQUEST CONTENT

CMS

10Monday, September 30, 13

Page 11: Blossom Q&A Webinar

TEMPLATEREQUEST CONTENT

CMS + Blossom

11

CONTROLLER

MODEL

VIEW

Monday, September 30, 13

Page 12: Blossom Q&A Webinar

Page Template

@Controller

@Template(id="myModule:pages/main", title="Main")public class MainTemplate {

   @RequestMapping("/main")

   public String render(ModelMap model) {

       return "pages/main";

   }

}

12Monday, September 30, 13

Page 13: Blossom Q&A Webinar

13

PAGE

Monday, September 30, 13

Page 14: Blossom Q&A Webinar

PAGES CONTAIN 0:n AREAS

14

PAGE

AREA

AREA

AREA

Monday, September 30, 13

Page 15: Blossom Q&A Webinar

PAGES CONTAIN 0:n AREAS

15

PAGE

AREA

AREA

AREA

AREAS HAVE 0:n COMPONENTS

COMPONENT

COMPONENTEtiam porta sem malesuada magna mollis euismod. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit.

COMPONENTEtiam porta sem malesuada magna mollis euismod. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Aenean lacinia bibendum nulla sed consectetur. Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed posuere consectetur est at lobortis.

COMPONENT

Monday, September 30, 13

Page 16: Blossom Q&A Webinar

Area Template

@Controller

@Template(id="myModule:pages/main",title="Main Template")

public class MainTemplate {

   @Controller

   @Area("main")    public static class MainArea {

       @RequestMapping("/main/mainArea")

       public String render() {

           return "areas/main";

       }

   ...

16Monday, September 30, 13

Page 17: Blossom Q&A Webinar

Component Template

@Controller

@Template(id="myModule:components/shoppingCart",          title="Shopping Cart")@TemplateDescription("Shopping cart")

public class ShoppingCartComponent {

   @RequestMapping("/shoppingCart")

   public String handleRequest() {

       ...

       return "components/shoppingCart";

   }

...

17Monday, September 30, 13

Page 18: Blossom Q&A Webinar

SERVLETCONTAINER

RENDERINGFILTER

RENDERINGENGINE

BLOSSOM DISPATCHERSERVLET CONTROLLER

18

How Blossom Works

Monday, September 30, 13

Page 19: Blossom Q&A Webinar

Blossom 3.0

19

What’s new?

Monday, September 30, 13

Page 20: Blossom Q&A Webinar

20

Dialogs

Monday, September 30, 13

Page 21: Blossom Q&A Webinar

Fluent Builder-style API

@TabFactory("Content")public void contentTab(UiConfig cfg, TabBuilder tab) {

tab.fields( cfg.fields.text("heading").label("Heading"), cfg.fields.richText("body").label("Text body"), cfg.fields.websiteLink("categoryLink").label("Link"), cfg.fields.basicUpload("image").label("Image"), cfg.fields.checkbox("inlineImage").label("Inline Image")  );}

21Monday, September 30, 13

Page 22: Blossom Q&A Webinar

Blossom 2 Style Dialog

@TabFactory("Content")

public void contentTab(TabBuilder tab) {

DialogEdit dialogEdit = tab.addEdit("title", "Title", "");

dialogEdit.setRequired(true);

dialogEdit.setConfig("i18n", "true");

dialogEdit.setConfig("rows", 5);

}

22Monday, September 30, 13

Page 23: Blossom Q&A Webinar

Page Template with Dialog

@Controller

@Template(title="Article", id="myModule:pages/article")public class ArticleTemplate {

   @TabFactory("Content")    public void contentTab(UiConfig cfg, TabBuilder tab) {

       tab.fields(cfg.fields.text("title").label("Title"));    }

}

23Monday, September 30, 13

Page 24: Blossom Q&A Webinar

Page Template with Dialog

@Controller

@Template(title="Article", id="myModule:pages/article")public class ArticleTemplate {

   @TabFactory("Content")    public void contentTab(UiConfig cfg, TabBuilder tab) {

       tab.fields(cfg.fields.text("title").label("Title"));    }

}

23Monday, September 30, 13

Page 25: Blossom Q&A Webinar

Page Template with Dialog

@Controller

@Template(title="Article", id="myModule:pages/article")public class ArticleTemplate {

   @TabFactory("Content")    public void contentTab(UiConfig cfg, TabBuilder tab) {

       tab.fields(cfg.fields.text("title").label("Title"));    }

} <title>${content.title}</title><!-- In the view -->

23Monday, September 30, 13

Page 26: Blossom Q&A Webinar

@Controller@Template(title="Article", id="myModule:pages/article")public class ArticleTemplate {

@Area("main") @Controller public static class MainArea {

@TabFactory("Content") public void contentTab(UiConfig cfg, TabBuilder tab) {

tab.fields(

cfg.fields.text("heading").label("Heading"), cfg.fields.text("border").label("Border width") );

Area Template with Dialog 24Monday, September 30, 13

Page 27: Blossom Q&A Webinar

@Controller@Template(title="Article", id="myModule:pages/article")public class ArticleTemplate {

@Area("main") @Controller public static class MainArea {

@TabFactory("Content") public void contentTab(UiConfig cfg, TabBuilder tab) {

tab.fields(

cfg.fields.text("heading").label("Heading"), cfg.fields.text("border").label("Border width") );

Area Template with Dialog 24Monday, September 30, 13

Page 28: Blossom Q&A Webinar

@Controller@Template(title="Article", id="myModule:pages/article")public class ArticleTemplate {

@Area("main") @Controller public static class MainArea {

@TabFactory("Content") public void contentTab(UiConfig cfg, TabBuilder tab) {

tab.fields(

cfg.fields.text("heading").label("Heading"), cfg.fields.text("border").label("Border width") );

Area Template with Dialog 24

<div id="main" style="border:${content.border}px solid #000"> <h2>${content.heading}</h2> <c:forEach items="${components}" var="component"> <cms:component content="${component}" /> </c:forEach></div> <!-- In the view -->

Monday, September 30, 13

Page 29: Blossom Q&A Webinar

Component Template with Dialog

@Controller

@Template(title="Text", id="myModule:components/text")

public class TextComponent {

@RequestMapping("/text")

public String render() {

return "components/text";

}

@TabFactory("Content")

public void contentTab(UiConfig cfg, TabBuilder tab) {

tab.fields(

cfg.fields.text("heading").label("Heading"), cfg.fields.richText("body").label("Text body")

25Monday, September 30, 13

Page 30: Blossom Q&A Webinar

Component Template with Dialog

@Controller

@Template(title="Text", id="myModule:components/text")

public class TextComponent {

@RequestMapping("/text")

public String render() {

return "components/text";

}

@TabFactory("Content")

public void contentTab(UiConfig cfg, TabBuilder tab) {

tab.fields(

cfg.fields.text("heading").label("Heading"), cfg.fields.richText("body").label("Text body")

25Monday, September 30, 13

Page 31: Blossom Q&A Webinar

Component Template with Dialog

@Controller

@Template(title="Text", id="myModule:components/text")

public class TextComponent {

@RequestMapping("/text")

public String render() {

return "components/text";

}

@TabFactory("Content")

public void contentTab(UiConfig cfg, TabBuilder tab) {

tab.fields(

cfg.fields.text("heading").label("Heading"), cfg.fields.richText("body").label("Text body")

25

<h1>${content.heading}</h1><p>${cmsfn:decode(content).body}</p><!-- In the view -->

Monday, September 30, 13

Page 32: Blossom Q&A Webinar

YouTube Video Component

@Controller@Template(title="YouTube Video", id="myModule:components/youtube")@TemplateDescription("Embed a YouTube video")

public class YoutubeComponent {

@RequestMapping("/youtube") public String render(Node node, ModelMap model) throws RepositoryException { model.put("videoId", node.getProperty("videoId").getString()); return "components/youtube"; }

@TabFactory("Content") public void contentTab(UiConfig cfg, TabBuilder tab) { tab.fields( cfg.fields.text("videoId").label("Video ID") ); }}

26Monday, September 30, 13

Page 33: Blossom Q&A Webinar

YouTube Video Component

@Controller@Template(title="YouTube Video", id="myModule:components/youtube")@TemplateDescription("Embed a YouTube video")

public class YoutubeComponent {

@RequestMapping("/youtube") public String render(Node node, ModelMap model) throws RepositoryException { model.put("videoId", node.getProperty("videoId").getString()); return "components/youtube"; }

@TabFactory("Content") public void contentTab(UiConfig cfg, TabBuilder tab) { tab.fields( cfg.fields.text("videoId").label("Video ID") ); }}

26Monday, September 30, 13

Page 34: Blossom Q&A Webinar

YouTube Video Component

@Controller@Template(title="YouTube Video", id="myModule:components/youtube")@TemplateDescription("Embed a YouTube video")

public class YoutubeComponent {

@RequestMapping("/youtube") public String render(Node node, ModelMap model) throws RepositoryException { model.put("videoId", node.getProperty("videoId").getString()); return "components/youtube"; }

@TabFactory("Content") public void contentTab(UiConfig cfg, TabBuilder tab) { tab.fields( cfg.fields.text("videoId").label("Video ID") ); }}

26

<iframe width="100%" height="400" src="//www.youtube.com/embed/${videoId}" frameborder="0" allowfullscreen></iframe><!-- In the view -->

Monday, September 30, 13

Page 35: Blossom Q&A Webinar

Dialogs and the Class Hierarchy

public abstract class BasePageTemplate { @TabFactory("Meta") public void metaTab(UiConfig cfg, TabBuilder tab) { tab.fields(

cfg.fields.text("metaAuthor").label("Author"),     cfg.fields.text("metaKeywords").label("Keywords"),     cfg.fields.text("metaDescription").label("Description")    );  }}

27Monday, September 30, 13

Page 36: Blossom Q&A Webinar

Input Validation

...

public void contentTab(UiConfig cfg, TabBuilder tab) {

tab.fields(

cfg.fields.text("name").label("Name").required(), cfg.fields.text("email").label("Email")⏎ .validator(cfg.validators.email()) );

}

...

28Monday, September 30, 13

Page 37: Blossom Q&A Webinar

Dynamic Dialog

@Template(id="myModule:components/bookCategory", title="Book category")

@TemplateDescription("A list of books in a given category.")@Controllerpublic class BookCategoryComponent {    @Autowired    private SalesApplicationWebService service;

   @RequestMapping("/bookcategory")    public String render(Node content, ModelMap model) throws RepositoryException {        String category = content.getProperty("category").getString();        model.put("books", service.getBooksInCategory(category));        return "components/bookCategory";    } @TabFactory("Content")    public void contentTab(UiConfig cfg, TabBuilder tab) {        Collection<String> categories = service.getBookCategories();        tab.fields( cfg.fields.select("category").label("Category").options(categories)        );    }}

29Monday, September 30, 13

Page 38: Blossom Q&A Webinar

Dynamic Dialog

@Template(id="myModule:components/bookCategory", title="Book category")@TemplateDescription("A list of books in a given category.")

@Controllerpublic class BookCategoryComponent {    @Autowired    private SalesApplicationWebService service;

   @RequestMapping("/bookcategory")    public String render(Node content, ModelMap model) throws RepositoryException {        String category = content.getProperty("category").getString();        model.put("books", service.getBooksInCategory(category));        return "components/bookCategory";    } @TabFactory("Content")    public void contentTab(UiConfig cfg, TabBuilder tab) {        Collection<String> categories = service.getBookCategories();        tab.fields( cfg.fields.select("category").label("Category").options(categories)        );    }}

30Monday, September 30, 13

Page 39: Blossom Q&A Webinar

Dynamic Dialog

@Template(id="myModule:components/bookCategory", title="Book category")

@TemplateDescription("A list of books in a given category.")@Controllerpublic class BookCategoryComponent {    @Autowired    private SalesApplicationWebService service;

   @RequestMapping("/bookcategory")

   public String render(Node content, ModelMap model) throws RepositoryException {        String category = content.getProperty("category").getString();        model.put("books", service.getBooksInCategory(category));        return "components/bookCategory";    }

@TabFactory("Content")    public void contentTab(UiConfig cfg, TabBuilder tab) {        Collection<String> categories = service.getBookCategories();        tab.fields( cfg.fields.select("category").label("Category").options(categories)        );    }}

31Monday, September 30, 13

Page 40: Blossom Q&A Webinar

Questions

32Monday, September 30, 13

Page 41: Blossom Q&A Webinar

How do I get started on my project?

33Monday, September 30, 13

Page 42: Blossom Q&A Webinar

mvn archetype:generate -DarchetypeCatalog=http://nexus.magnolia-cms.com/

content/groups/public/

choose magnolia-blossom-module-archetype

Define value for property 'groupId': : com.acme

Define value for property 'artifactId': : acme-module

Define value for property 'version': 1.0-SNAPSHOT:

Define value for property 'package': com.acme:

Define value for property 'magnolia-version': : 4.5.11

Define value for property 'module-class-name': : AcmeModule

Define value for property 'module-name': acme-module: acmeModule

http://wiki.magnolia-cms.com/display/WIKI/Creating+a+new+Blossom+project+using+maven+archetypes

Monday, September 30, 13

Page 43: Blossom Q&A Webinar

How would I build a REST interface to my CMS content using Blossom?

35Monday, September 30, 13

Page 44: Blossom Q&A Webinar

REST servlet in module descriptor

<servlets>

  <servlet>

    <name>rest</name>

    <class>org.springframework.web.servlet.DispatcherServlet</class>

    <mappings>

      <mapping>/rest/*</mapping>

    </mappings>

    <params>

      <param>

        <name>contextConfigLocation</name>

        <value>classpath:/rest-servlet.xml</value>

      </param>

    </params>

  </servlet>

</servlets>

36Monday, September 30, 13

Page 45: Blossom Q&A Webinar

REST Controller

@Controller

public class ProductsRestController {

@RequestMapping("/products/{productId}")

public Product findProduct(@PathVariable String productId) {

// implementation omitted

}

}

37Monday, September 30, 13

Page 46: Blossom Q&A Webinar

Can I leverage Magnolia's caching functionality to improve the performance of my Spring app?

38Monday, September 30, 13

Page 47: Blossom Q&A Webinar

Influencing caching 39

@Controller

@Template(title="Text", id="myModule:components/text")

public class TextComponent {

@RequestMapping("/text")

public String render(HttpResponse response) {

response.setHeader("Cache-Control", "no-cache"); return "components/text.jsp";

}

}

Monday, September 30, 13

Page 48: Blossom Q&A Webinar

How can I present dialogs in different languages using Blossom?

40Monday, September 30, 13

Page 49: Blossom Q&A Webinar

@Controller

@Template(title = "Text", id = "blossomSampleModule:components/text")

@I18nBasename("info.magnolia.blossom.sample.messages")

public class TextComponent {

@TabFactory("textComponent.contentTab.label")

public void contentTab(UiConfig cfg, TabBuilder tab) {

tab.fields(

cfg.fields.text("heading").label("textComponent.contentTab.heading")

);

}

}

41I18n Blossom DialogMonday, September 30, 13

Page 50: Blossom Q&A Webinar

/info/magnolia/blossom/sample/messages_en.properties

textComponent.contentTab.label = Content

textComponent.contentTab.heading = Heading

/info/magnolia/blossom/sample/messages_sv.properties

textComponent.contentTab.label = Innehåll

textComponent.contentTab.heading = Rubrik

42I18n Blossom DialogMonday, September 30, 13

Page 51: Blossom Q&A Webinar

How do I migrate my existing site to Magnolia/Blossom?

43Monday, September 30, 13

Page 52: Blossom Q&A Webinar

How can I integrate Spring Security?

44Monday, September 30, 13

Page 53: Blossom Q&A Webinar

45Monday, September 30, 13

Page 54: Blossom Q&A Webinar

How can I dependency inject spring beans into RenderingModels?

46Monday, September 30, 13

Page 55: Blossom Q&A Webinar

Autowired RenderingModel

public class MyRenderingModel extends RenderingModelImpl<TemplateDefinition> {

@Autowired

private MyService service;

public MyRenderingModel(ServletContext servletContext, Node content, TemplateDefinition definition, RenderingModel<?> parent) {

super(content, definition, parent);

WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(servletContext);

AutowireCapableBeanFactory beanFactory = wac.getAutowireCapableBeanFactory();

beanFactory.autowireBean(this);

}

47Monday, September 30, 13

Page 56: Blossom Q&A Webinar

Autowired RenderingModel

public class MyRenderingModel extends AbstractAutowiredRenderingModel<TemplateDefinition> {

@Autowired

private MyService service;

public MyRenderingModel(ServletContext servletContext, Node content, TemplateDefinition definition, RenderingModel<?> parent) {

super(content, definition, parent, servletContext);

}

48Monday, September 30, 13

Page 57: Blossom Q&A Webinar

More questions?

49Monday, September 30, 13

Page 58: Blossom Q&A Webinar

Magnolia + Spring = Blossom 50Monday, September 30, 13

Page 59: Blossom Q&A Webinar

Thank You!

51Monday, September 30, 13

Page 60: Blossom Q&A Webinar

52

magnolia-cms.com/spring

Monday, September 30, 13