Spring & Web
Content Management
Tobias Mattsson & Daniel Lipp
Magnolia International
© 2013 SpringOne 2GX. All rights rese...
2
3
4
Tobias Mattsson

Sr. Software Engineer, Magnolia
Lead developer of Magnolia’s Spring integration
Spring Framework user sin...
Daniel Lipp

Sr. Software Engineer, Magnolia
17 years of experience as software architect and Java developer
Grateful to S...
®

7
8
@magnolia_cms
#magnolia_cms
9
Spring
Spring MVC
Code, beautiful code
Dependency Injection
Integrations
Business logic

10
CMS
Security
Multi-lingual
User interface
Image manipulation
Content versioning

11
Spring
Spring MVC
Code, beautiful code
Dependency Injection
Integrations
Business logic

CMS
Security
Multi-lingual
User i...
USES IFRAMES

LIKE IT'S 1999

13
Magnolia + Spring = Blossom

14
@Template

or, "How Blossom is Built"

15
CMS
REQUEST

CONTENT

TEMPLATE

16
CMS + Blossom
REQUEST

CONTENT

TEMPLATE
CONTROLLER
MODEL
VIEW

17
@Controller
@Template(id="myModule:pages/main", title="Main")
public class MainTemplate {
   @RequestMapping("/main")
   p...
PAGES CONTAIN 0:n AREAS

PAGE
19
PAGES CONTAIN 0:n AREAS

AREA
A
R
E
A

PAGE

AREA

19
PAGES CONTAIN 0:n AREAS

AREA

COMPONENT
AREAS HAVE 0:n COMPONENTS
C
O
M
P
O
N
E
N
T

A
R
E
A

COMPONENT

PAGE

Etiam port...
@Controller
@Template(id="myModule:pages/main",title="Main Template")
public class MainTemplate {
   @Controller
   @Area(...
@Controller
@Template(id="myModule:components/shoppingCart",
         title="Shopping Cart")
@TemplateDescription("Shoppin...
Views

22
FreeMarker
[@cms.area name="main" /]

JSP
<cms:area name="main" />

Rendering an Area

23
FreeMarker
[#list components as component]
   [@cms.component content=component /]
[/#list]

JSP
<c:forEach items="${compo...
About Magnolia CMS
100% Java/J2EE compliant
Best of breed open technology stack, including:
JSR-283 / JCR 2.0
Vaadin / GWT...
Java Content Repository
“… defines an abstract model and a Java
API for data storage and related services
commonly used by...
Magnolia
JCR API

Jackrabbit

Hierarchical
content repository

Persistence Manager

or
File system

or
Database

In memory...
Schema-less or
Strongly-typed
Hierarchical
Structured
Queries
Full-text
Search

Transactional
Referential
Integrity

Locki...
Dialogs

29
GWT with server-side state
Component-based UI
Cross-browser compatibility (via GWT)
Rich component library
Fast developmen...
@TabFactory("Content")
public void contentTab(UiConfig cfg, TabBuilder tab) {
tab.fields(
cfg.fields.text("heading").label...
@Controller
@Template(title="Article", id="myModule:pages/article")
public class ArticleTemplate {
   @TabFactory("Content...
@Controller
@Template(title="Article", id="myModule:pages/article")
public class ArticleTemplate {
   @TabFactory("Content...
@Controller
@Template(title="Article", id="myModule:pages/article")
public class ArticleTemplate {
@Area("main")
@Controll...
@Controller
@Template(title="Article", id="myModule:pages/article")
public class ArticleTemplate {
@Area("main")
@Controll...
@Controller
@Template(title="Article", id="myModule:pages/article")
public class ArticleTemplate {
@Area("main")
@Controll...
@Controller
@Template(title="Text", id="myModule:components/text")
public class TextComponent {
@RequestMapping("/text")
p...
@Controller
@Template(title="Text", id="myModule:components/text")
public class TextComponent {
@RequestMapping("/text")
p...
@Controller
@Template(title="YouTube Video", id="myModule:components/youtube")
@TemplateDescription("Embed a YouTube video...
@Controller
@Template(title="YouTube Video", id="myModule:components/youtube")
@TemplateDescription("Embed a YouTube video...
public abstract class BasePageTemplate {
@TabFactory("Meta")
public void metaTab(UiConfig cfg, TabBuilder tab) {
tab.field...
public abstract class BasePageTemplate {
@TabFactory("Meta")
public void metaTab(UiConfig cfg, TabBuilder tab) {
tab.field...
@Template(id="myModule:components/bookCategory", title="Book category")
@TemplateDescription("A list of books in a given c...
...
@TabFactory("Content")
public void contentTab(UiConfig cfg, TabBuilder tab) {
Collection<String> categories = service....
...
public void contentTab(UiConfig cfg, TabBuilder tab) {
tab.fields(
cfg.fields.text("name").label("Name").required(),
c...
@Controller
@Template(title="Article", id="myModule:/pages/article")
public class ArticleTemplate {
   ...
   @Available
 ...
@Controller
@Area("promos")
@AvailableComponentClasses({TextComponent.class,
                           ShoppingCartCompon...
@Controller
@Area("promos")
@Inherits
@AvailableComponentClasses({TextComponent.class,
                           Shopping...
SERVLET
CONTAINER

RENDERING
FILTER

RENDERING
ENGINE

How Blossom Works
BLOSSOM

DISPATCHER
SERVLET

CONTROLLER

43
Magnolia
Server

Modules

STK

Custom
Theme

Custom
module

Custom
module

Imaging

Workflow

DMS

Data

Module JAR
- temp...
Web container (Tomcat)
Web Archive
Magnolia CMS
Filter chain
Request

Context

CMS
Security

Custom
filter

Cache

Aggrega...
/* Standard annotations omitted */
public class ContactFormComponent {
@RequestMapping(value="/contact", method=RequestMet...
But wait a minute …

Caused by: org.springframework.web.util.NestedServletException: Reque
java.lang.IllegalStateException...
Controller Pre-execution
PAGE
C

C M V

Area

Component

C M V

C M V

Component

Component

C M V

C M V

48
Code in View
<form>
<blossom:pecid-input />
<input type=”text” name=”email” />
...
HTML Output
<form>
<input type=”hidden”...
Spring Web MVC
+
Content
50
Spring Web Flow

REQUEST

LANDING
PAGE

SPRING
WEB FLOW

51
Spring Web Flow

REQUEST

LANDING
PAGE

FORM
SUBMIT?

SPRING
WEB FLOW

52
Deployment & Scaling

53
Internal Network

Internet

Public A

Magnolia

JCR

Activate
Author

JCR

Magnolia
Public B

Firewall

Load Balancer

Mag...
Clustering
Magnolia
Author
Backend
Application

DB

JCR

Magnolia
Magnolia
Public
Public

JCR
JCR

55
Dealing with Change

56
Filter Chain in JCR

57
<blossom:observed-bean
default-class="com.sample.DiscountService"
path="/modules/myModule/beans/discountService" />

Confi...
example.com/vanity
59
Changing Resources Live
CSS
FreeMarker
Images
Javascript
Views

60
What Do You Win?

61
62
63
Security
Maglev

Shorter
Learning
Curve

Scalability

Spring
Idioms
Test-driven
Development

Staging
Mobile
& Touch
Faster...
Security
Maglev

Shorter
Learning
Curve

Scalability

Staging
Mobile
& Touch
Faster
Development

Spring
Idioms
Test-driven...
Want to Learn More?
Talk to us in the exhibit hall
Attend Blossom Q & A webinar on Sept. 26
http://magnolia-cms.com/blosso...
Thank You!
66
Image Credits
#2: "Long Drive" by Nicholas A. Tonelli (CC-BY 2.0)
#3: "Sluggish" by Nicholas A. Tonelli (CC-BY 2.0)
#4: St...
Trademarks
Magnolia
The Pulse

}

are trademarks of
Magnolia International Limited.

Other trademarks are the property of ...
Upcoming SlideShare
Loading in …5
×

Spring and Web Content Management

7,708 views

Published on

Speakers: Daniel Lipp and Tobias Mattsson
Want Spring seamlessly available inside a CMS? How about being able to integrate existing Spring apps into your CMS without rewriting a bunch of code? What about a robust CMS solution for Grails? Meet Magnolia, a mature open source CMS written in Java on the best of the Java stack (including Spring and Groovy.)
This session will introduce Magnolia's Spring integration and give you a tour of its architecture, key features and use. Along the way, you'll also get insights into the development of Magnolia's Spring integration, an overview of Magnolia's key features (like workflows, innovative multi-channel support and a damn fine user experience that includes touch devices), and brief tutorials on solving some key content management challenges faced by Spring developers. There will also be a quick detour into Magnolia's Groovy shell and MagLev, a Grails plugin for Magnolia.

Published in: Technology, Education
0 Comments
1 Like
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
7,708
On SlideShare
0
From Embeds
0
Number of Embeds
1
Actions
Shares
0
Downloads
14
Comments
0
Likes
1
Embeds 0
No embeds

No notes for slide

Spring and Web Content Management

  1. 1. Spring & Web Content Management Tobias Mattsson & Daniel Lipp Magnolia International © 2013 SpringOne 2GX. All rights reserved.
  2. 2. 2
  3. 3. 3
  4. 4. 4
  5. 5. Tobias Mattsson Sr. Software Engineer, Magnolia Lead developer of Magnolia’s Spring integration Spring Framework user since 2005 5
  6. 6. Daniel Lipp Sr. Software Engineer, Magnolia 17 years of experience as software architect and Java developer Grateful to Spring for inspiring improvements to JEE 6
  7. 7. ® 7
  8. 8. 8
  9. 9. @magnolia_cms #magnolia_cms 9
  10. 10. Spring Spring MVC Code, beautiful code Dependency Injection Integrations Business logic 10
  11. 11. CMS Security Multi-lingual User interface Image manipulation Content versioning 11
  12. 12. Spring Spring MVC Code, beautiful code Dependency Injection Integrations Business logic CMS Security Multi-lingual User interface Image manipulation Content versioning 12
  13. 13. USES IFRAMES LIKE IT'S 1999 13
  14. 14. Magnolia + Spring = Blossom 14
  15. 15. @Template or, "How Blossom is Built" 15
  16. 16. CMS REQUEST CONTENT TEMPLATE 16
  17. 17. CMS + Blossom REQUEST CONTENT TEMPLATE CONTROLLER MODEL VIEW 17
  18. 18. @Controller @Template(id="myModule:pages/main", title="Main") public class MainTemplate {    @RequestMapping("/main")    public String render(ModelMap model) {        return "pages/main";    } } Page Template 18
  19. 19. PAGES CONTAIN 0:n AREAS PAGE 19
  20. 20. PAGES CONTAIN 0:n AREAS AREA A R E A PAGE AREA 19
  21. 21. PAGES CONTAIN 0:n AREAS AREA COMPONENT AREAS HAVE 0:n COMPONENTS C O M P O N E N T A R E A COMPONENT PAGE Etiam porta sem malesuada magna mollis euismod. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. AREA COMPONENT Etiam 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. 19
  22. 22. @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";        }    ... Area Template 20
  23. 23. @Controller @Template(id="myModule:components/shoppingCart",          title="Shopping Cart") @TemplateDescription("Shopping cart") public class ShoppingCartComponent {    @RequestMapping("/shoppingCart")    public String handleRequest() {        ...        return "components/shoppingCart";    } ... Component Template 21
  24. 24. Views 22
  25. 25. FreeMarker [@cms.area name="main" /] JSP <cms:area name="main" /> Rendering an Area 23
  26. 26. FreeMarker [#list components as component]    [@cms.component content=component /] [/#list] JSP <c:forEach items="${components}" var="component">    <cms:component content="${component}" /> </c:forEach> Rendering Components in an Area 24
  27. 27. About Magnolia CMS 100% Java/J2EE compliant Best of breed open technology stack, including: JSR-283 / JCR 2.0 Vaadin / GWT Servlet API HTML5 Highly Customizable 25
  28. 28. Java Content Repository “… defines an abstract model and a Java API for data storage and related services commonly used by content-oriented applications.” – JSR-283 26
  29. 29. Magnolia JCR API Jackrabbit Hierarchical content repository Persistence Manager or File system or Database In memory 27
  30. 30. Schema-less or Strongly-typed Hierarchical Structured Queries Full-text Search Transactional Referential Integrity Locking Why JCR? Fine-grained content and large binaries Versioning Role-based security Event Notification (Observation) 28
  31. 31. Dialogs 29
  32. 32. GWT with server-side state Component-based UI Cross-browser compatibility (via GWT) Rich component library Fast development pace 30
  33. 33. @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")  ); } Fluent Builder-style API 31
  34. 34. @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"));    } } Page Template with Dialog 32
  35. 35. @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 --> Page Template with Dialog 32
  36. 36. @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 33
  37. 37. @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 33
  38. 38. @Controller @Template(title="Article", id="myModule:pages/article") public class ArticleTemplate { @Area("main") @Controller public static <div id="main" class MainArea { style="border:${content.border}px solid #000"> @TabFactory("Content") <h2>${content.heading}</h2> public void contentTab(UiConfig cfg, TabBuilder tab) { <c:forEach items="${components}" var="component"> tab.fields( <cms:component content="${component}" /> cfg.fields.text("heading").label("Heading"), </c:forEach> </div> cfg.fields.text("border").label("Border width") <!-- In the view --> ); Area Template with Dialog 33
  39. 39. @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") Component Template with Dialog 34
  40. 40. @Controller @Template(title="Text", id="myModule:components/text") public class TextComponent { @RequestMapping("/text") public String render() { return "components/text"; } @TabFactory("Content") <h1>${content.heading}</h1> public void contentTab(UiConfig cfg, TabBuilder tab) { <p>${cmsfn:decode(content).body}</p> tab.fields( <!-- In the view --> cfg.fields.text("heading").label("Heading"), cfg.fields.richText("body").label("Text body") Component Template with Dialog 34
  41. 41. @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") ); } } YouTube Video Component 35
  42. 42. @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) { <iframe width="100%" height="400" src="//www.youtube.com/ tab.fields( embed/${videoId}" frameborder="0" allowfullscreen></iframe> cfg.fields.text("videoId").label("Video ID") <!--); the view --> In } } YouTube Video Component 35
  43. 43. 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")    );  } } Dialogs and the Class Hierarchy 36
  44. 44. 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")    ); <head>  } name="description" content="${content.metaDescription}"/> <meta } <meta name="keywords" content="${content.metaKeywords}" />  <meta name="author" content="${content.metaAuthor}" /> </head> <!-- In the view --> Dialogs and the Class Hierarchy 36
  45. 45. @Template(id="myModule:components/bookCategory", title="Book category") @TemplateDescription("A list of books in a given category.") @Controller public class BookCategoryComponent {    @Autowired    private SalesApplicationWebService service;    @RequestMapping("/bookcategory")    public String render(ModelMap model, Node content) 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)        );    } } Dynamic Dialog 37
  46. 46. ... @TabFactory("Content") public void contentTab(UiConfig cfg, TabBuilder tab) { Collection<String> categories = service.getBookCategories(); tab.fields( cfg.fields.select("category").label("Category").options(categories)  ); } ... Dynamic Dialog 38
  47. 47. ... 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()) ); } ... Input Validation 39
  48. 48. @Controller @Template(title="Article", id="myModule:/pages/article") public class ArticleTemplate {    ...    @Available    public boolean isAvailable(Node node) {        return node.getPath().startsWith("/articles/");    } } Page Template Availability 40
  49. 49. @Controller @Area("promos") @AvailableComponentClasses({TextComponent.class,                            ShoppingCartComponent.class}) public static class PromosArea {    @RequestMapping("/main/promos")    public String render() {        return "areas/promos";    } } Available Components 41
  50. 50. @Controller @Area("promos") @Inherits @AvailableComponentClasses({TextComponent.class,                            ShoppingCartComponent.class}) public static class PromosArea {    @RequestMapping("/main/promos")    public String render() {        return "areas/promos";    } } Area Inheritance 42
  51. 51. SERVLET CONTAINER RENDERING FILTER RENDERING ENGINE How Blossom Works BLOSSOM DISPATCHER SERVLET CONTROLLER 43
  52. 52. Magnolia Server Modules STK Custom Theme Custom module Custom module Imaging Workflow DMS Data Module JAR - templates - resources Magnolia Core 44
  53. 53. Web container (Tomcat) Web Archive Magnolia CMS Filter chain Request Context CMS Security Custom filter Cache Aggregation Rendering Response Content Hierarchical content repository Rendering engine 45
  54. 54. /* Standard annotations omitted */ public class ContactFormComponent { @RequestMapping(value="/contact", method=RequestMethod.GET)  public String viewForm(@ModelAttribute ContactForm contactForm) { return "components/contactForm"; } @RequestMapping(value="/contact", method=RequestMethod.POST) public String handleSubmit(@ModelAttribute ContactForm contactForm, ⏎ BindingResult result) { new ContactFormValidator().validate(contactForm, result); if (result.hasErrors()) { return "components/contactForm"; } return "redirect:/home/contact/thankyou.html"; } Form Submission 46
  55. 55. But wait a minute … Caused by: org.springframework.web.util.NestedServletException: Reque java.lang.IllegalStateException at org.springframework.web.servlet.FrameworkServlet.processRequest(F at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkS at javax.servlet.http.HttpServlet.service(HttpServlet.java:617) at info.magnolia.module.blossom.render.BlossomDispatcherServlet.forw at info.magnolia.module.blossom.render.BlossomTemplateRenderer.rende ... 92 more Caused by: java.lang.IllegalStateException … the response has been sent. 47
  56. 56. Controller Pre-execution PAGE C C M V Area Component C M V C M V Component Component C M V C M V 48
  57. 57. Code in View <form> <blossom:pecid-input /> <input type=”text” name=”email” /> ... HTML Output <form> <input type=”hidden” name=”_pecid” value=”ff6cefa6-d958-47b1-af70-c82a414f17e1” /> <input type=”text” name=”email” /> ... CPE Implementation 49
  58. 58. Spring Web MVC + Content 50
  59. 59. Spring Web Flow REQUEST LANDING PAGE SPRING WEB FLOW 51
  60. 60. Spring Web Flow REQUEST LANDING PAGE FORM SUBMIT? SPRING WEB FLOW 52
  61. 61. Deployment & Scaling 53
  62. 62. Internal Network Internet Public A Magnolia JCR Activate Author JCR Magnolia Public B Firewall Load Balancer Magnolia JCR 54
  63. 63. Clustering Magnolia Author Backend Application DB JCR Magnolia Magnolia Public Public JCR JCR 55
  64. 64. Dealing with Change 56
  65. 65. Filter Chain in JCR 57
  66. 66. <blossom:observed-bean default-class="com.sample.DiscountService" path="/modules/myModule/beans/discountService" /> Configuring Spring Beans 58
  67. 67. example.com/vanity 59
  68. 68. Changing Resources Live CSS FreeMarker Images Javascript Views 60
  69. 69. What Do You Win? 61
  70. 70. 62
  71. 71. 63
  72. 72. Security Maglev Shorter Learning Curve Scalability Spring Idioms Test-driven Development Staging Mobile & Touch Faster Development Code-driven Dialogs @Annotations MVC Loose Coupling Use Existing Dependency Spring Apps & Injection Components 64
  73. 73. Security Maglev Shorter Learning Curve Scalability Staging Mobile & Touch Faster Development Spring Idioms Test-driven Development @Annotations MVC Questions? Code-driven Dialogs Loose Coupling Use Existing Dependency Spring Apps & Injection Components 64
  74. 74. Want to Learn More? Talk to us in the exhibit hall Attend Blossom Q & A webinar on Sept. 26 http://magnolia-cms.com/blossom-qa Visit http://magnolia-cms.com/spring Dashboard for all things Spring in Magnolia 65
  75. 75. Thank You! 66
  76. 76. Image Credits #2: "Long Drive" by Nicholas A. Tonelli (CC-BY 2.0) #3: "Sluggish" by Nicholas A. Tonelli (CC-BY 2.0) #4: Still from "The Matrix" #7: "South Beach Miami Sunset" by Justin Ornellas (CC-BY 2.0) #14: "HBW - Magnolia Edition" by Nana B Agyei (CC-BY 2.0) #16: "Worker" designed by James Fenton from The Noun Project #16: "Database" designed by Ed Jones from The Noun Project #16: "Document" designed by Timur Zima from The Noun Project #22: "Three levels" by Paolo Fefe (CC-ND 2.0) #43:"Headset" designed by Benoît Bâlon from The Noun Project #43: "Engine" released into the Public Domain #43: "Flower" designed by Danilo Gusmão Silveira from The Noun Project #43: "Video Game Controller" designed by "Michael Rowe" from The Noun Project #51: "Anvil" designed by Masrur Mahmood from The Noun Project #52: "Hand" designed by Mikhail Bazilevsky from The Noun Project #53: "NYC Steam" by Don O'Brien (CC-BY 2.0) #56: "Floods 2013: Riverfront Ave" by Ryan Quan (CC-SA 2.0) #61: "3 Strikes for £2.50" by Julian Frost (CC-BY 2.0) 67
  77. 77. Trademarks Magnolia The Pulse } are trademarks of Magnolia International Limited. Other trademarks are the property of their respective owners. 68

×