class: splash
Thibault Duchateau
@tduchateau
http://dandelion.github.io
--- class: agenda, right * # Quick introduction * Brief history * Dandelion-Core * Dandelion-Datatables * Some statistics * Future * Demo --- # Quick introduction * Free & Open Source Java framework * Focused on two aspects of Web development: * __Assets management__ (js, css): organization in bundles, HTML injection, soon asset minification and merging * __Integration of powerful Javascript librairies__ thanks to a set of components * Licensed under
BSD 3-Clause
* Hosted on
Github
* Current version: 0.10.0 --- # Thanks to... .center[
] --- class: agenda, right * Quick introduction * # Brief history * Dandelion-Core * Dandelion-Datatables * Some statistics * Future * Demo --- class: no-margin # Brief history
.footnote.ref[
History
] --- class: agenda, right * Quick introduction * Brief history * # Dandelion-Core * Dandelion-Datatables * Some statistics * Future * Demo --- .soft[ # Overview ]
--- # Asset bundles ### Declarative approach, via JSON (soon XML and JavaConfig) ````javascript { "bundle" : "jquery", "assets": [ { "name": "jquery", "version": "1.11.1", "type": "js", "locations": { "webapp": "/assets/js/jquery-1.11.1.js" } } ] } ```` Several locations are allowed for each asset:
webapp
,
classpath
,
CDN
,
JAR
,
WebJar
and
API
! .footnote.ref[
See the docs
] --- # Dependencies between bundles ### You can declare one or more bundles as dependencies ````javascript { "bundle" : "select2", "dependencies": [ "jquery" ], "assets": [ { "name": "select2", "version": "3.4.8", "type": "js", "locations": { "webapp": "/assets/js/select2.js" } }, { "name": "select2", "version": "3.4.8", "type": "css", "locations": { "webapp": "/assets/css/select2.css" } } ] } ```` --- # Asset locators * ### Internal components used by Dandelion to **fetch assets** in different ways * ### Used via the corresponding **location key** in the bundle declaration .footnote.ref[
See the docs
] --- # Asset locators — webapp .left-column[ ### Project ] .right-column[ ````json your-project |__ src |__ main |__ webapp |__ assets |__ js |__ app.js ```` ] --- # Asset locators — webapp .left-column[ ### Project ### Bundle ] .right-column[ ````json { "bundle" : "my-bundle", "assets": [{ "name": "my-application", "version": "1.0.0", "type": "js", "locations": { "webapp": "/assets/js/app.js" } }] } ```` ] --- # Asset locators — webapp .left-column[ ### Project ### Bundle ### HTML ] .right-column[ ````xml ```` ] --- # Asset locators — classpath .left-column[ ### Project ] .right-column[ ````json your-project |__ src |__ main |__ resources |__ js |__ app.js ```` ] --- # Asset locators — classpath .left-column[ ### Project ### Bundle ] .right-column[ ````json { "bundle" : "my-bundle", "assets": [{ "name": "my-application", "version": "1.0.0", "type": "js", "locations": { "classpath": "js/app.js" } }] } ```` ] --- # Asset locators — classpath .left-column[ ### Project ### Bundle ### HTML ] .right-column[ ````xml ```` ] --- # Asset locators — cdn .left-column[ ### Bundle ] .right-column[ ````json { "bundle" : "jquery", "assets": [{ "name": "jquery", "version": "1.11.0", "type": "js", "locations": { "cdn": "//cdnjs.cloudflare.com/.../jquery.js" } }] } ```` ] --- # Asset locators — cdn .left-column[ ### Bundle ### HTML ] .right-column[ ````xml ```` ] --- # Asset locators — jar .left-column[ ### JAR ] .right-column[ ````json datatables-core |__ src |__ main |__ resources |__ META-INF |__ resources |__ folder |__ js |__ app.js ```` ] --- # Asset locators — jar .left-column[ ### JAR ### Bundle ] .right-column[ ````json { "bundle" : "my-bundle", "assets": [{ "name": "my-app", "version": "1.0.0", "type": "js", "locations": { "jar": "folder/js/app.js" } }] } ```` ] --- # Asset locators — jar .left-column[ ### JAR ### Bundle ### HTML ] .right-column[ Inside a Servlet 2.0+ container: ````xml ```` Inside a Servlet 3.0+ container: ````xml ```` ### Dandelion automatically detects whether the running server is using the Servlet 3.x API or lower ] --- # Asset locators — webjar .left-column[ ### New dependency ] .right-column[ ````xml
com.github.dandelion
dandelion-webjars
0.10.0
```` This artifact brings a new dependency to the [webjars-locator](https://github.com/webjars/webjars-locator) project, which is internally used by the locator to locate assets inside WebJars ] --- # Asset locators — webjar .left-column[ ### New dependency ### WebJar ] .right-column[ ````xml
org.webjars
bootstrap
3.2.0
```` ] --- # Asset locators — webjar .left-column[ ### New dependency ### WebJar ### Bundle ] .right-column[ ````json { "bundle" : "my-bundle", "assets": [{ "name": "bootstrap", "version": "3.2.0", "type": "css", "locations": { "webjar": "bootstrap.css" } }] } ```` ] --- # Asset locators — webjar .left-column[ ### New dependency ### WebJar ### Bundle ### HTML ] .right-column[ Inside a Servlet 2.0+ container: ````xml
```` Inside a Servlet 3.0+ container: ````xml
```` ### Dandelion automatically detects whether the running server is using the Servlet 3.x API or lower ] --- # Asset locators — delegate * Reads the content of an asset from a **special parameter** stored inside the `AssetRequestContext` * This special parameter: * must be stored under the `DelegateLocator.DELEGATED_CONTENT_PARAM` key * must be a class that implements `DelegatedContent` and where the `getContent()` method must return the asset content to be injected ] --- # Asset locators — delegate .left-column[ ### Bundle ] .right-column[ ````json { "bundle": "my-bundle", "assets": [{ "name": "my-generated-asset", "version": "1.0.0", "type": "js", "locations": { "delegate": "app.js" } }] } ```` ] --- # Asset locators — delegate .left-column[ ### Bundle ### Generator ] .right-column[ ````java public class SomeJavascriptGenerator implements DelegatedContent { @Override public String getContent(HttpServletRequest request) { return "alert('Hey!');"; } } ```` ] --- # Asset locators — delegate .left-column[ ### Bundle ### Generator ### ARC ] .right-column[ ````java AssetRequestContext .get(request) .addParameter("my-generated-asset", DelegateLocator.DELEGATED_CONTENT_PARAM, new SomeJavascriptGenerator()); ```` ] --- # Asset locators — delegate .left-column[ ### Bundle ### Generator ### ARC ### HTML ] .right-column[ ````java ```` Where `app.js`: ````javascript alert('Hey!'); ```` ] --- # Bundle loaders ### Scan for bundles in the classpath, in the following order:
__VendorBundleLoader__: intended to load _vendor bundles_ (e.g. jquery.json) __ComponentBundleLoader__: intended to load _components bundles_ (e.g. ddl-dt.json) __DandelionBundleLoader__: intended to load _user bundles_ (e.g. custom-bundle.json) .footnote.ref[
See the docs
] --- # Bundle graph On the application startup, Dandelion creates a `Context` that will be injected into each request. This `Context` provide several information: * all configuration options (coming from the properties files) * a bundle graph (
DAG
), built from all scanned bundles Example: ````xml src |__ main |__ resources |__ dandelion |__ jquery.json |__ select2.json ```` .center[
] .footnote.ref[
See the docs
] --- # Interacting with the bundle graph ### 1/4) Using the JSP taglib ````xml <%@ taglib prefix="dandelion" uri="http://github.com/dandelion" %>
```` ### 2/4) Using the Thymeleaf dialect ````xml ... ```` .footnote.ref[
See the docs
] --- # Interacting with the bundle graph ### 3/4) Using the fluent API ````java AssetRequestContext .get((HttpServletRequest) request) .addBundle("select2"); ```` ### 4/4) Using the configuration file ("permanent" bundles) ````xml bundle.includes=select2 ```` .footnote.ref[
See the docs
] --- # Interacting with the bundle graph ### Whatever the technology used, Dandelion automatically injects all requested assets into the HTML code: * in the right order * in the configured position, by default CSS in `` and JS just before `` * and (soon) applies the right HTTP headers, therefore optimizing browser caching ````html
... ```` --- class: beta # Asset processors ### Intended to process assets, in different ways... Built-in processors: * __jsMinYui__:JS minification based on
Yahoo's YUI Compressor
* __cssMinYui__:CSS minification based on
Yahoo's YUI Compressor
* __cssMin__:CSS minification based on a
fork of YUI
* __jsMin__:JS minification based on a
fork of YUI
* __cssUrlRewriting__:Image URLs rewriting in CSS Soon: * __cssLess__: Convert Less resources to CSS * __cssSass__: Convert Sass resources to CSS * __cssImport__: Processes @import * __coffeeScript__: Convert CoffeeScript to JavaScript * ... .footnote.ref[
See the docs
] --- class: beta # Development/production modes ## Development mode * Built-in dev tools available * Server-side caching disabled * Browser caching disabled ## Production mode * No bundle graph viewer, no bundle reloading * Minification enabled * Server-side caching enabled * (soon) Browser caching enabled * (soon) GZIP compression * (soon) Asset versioning * (soon) JMX monitoring --- .left-column[ ## Dev tools ### Bundle graph viewer ] .right-column[ ### Dev tool allowing to visualize how Dandelion-Core handles assets, in the current request .center[ ````html http://example.com/context/some/uri?showGraph ````
] .footnote.ref[ Available in
development mode
only
See the docs
] ] --- .left-column[ ## Dev tools ### Bundle graph viewer ### Asset reloading ] .right-column[ During development, both server-side and client-side caching are disabled. Client-side, Dandelion-Core applies the following HTTP headers: * __Cache-Control__ = `no-cache, no-store, must-revalidate` (HTTP 1.1) * __Pragma__ = `no-cache` (HTTP 1.0) * __Expires__ = `0` (Proxies) ] --- .left-column[ ## Dev tools ### Bundle graph viewer ### Asset reloading ### Bundle reloading ] .right-column[ During development, all changes made in asset bundles can be reflected. Just manually append a new `reloadBundles` request parameter to the current URL to perform a __full bundle reloading__. .center[ ````html http://example.com/context/some/uri?reloadBundles ```` ] .footnote[ Available in
development mode
only ] ] --- # Configuration options .left-column[ ## Properties ] .right-column[ .center[ ### dandelion.properties ] ````prop dandelion.mode=production ```` ] --- # Configuration options .left-column[ ## Properties ## Filter init param ] .right-column[ .center[ ### filter init param > dandelion.properties ] ````xml
dandelion
...DandelionFilter
dandelion.mode
production
```` ] --- # Configuration options .left-column[ ## Properties ## Filter init param ## System property ] .right-column[ .center[ ### system property > filter init param > dandelion.properties ] ````shell -Ddandelion.mode=production ```` ] .footnote.ref[
See the docs
] --- class: agenda, right * Quick introduction * Brief history * Dandelion-Core * # Dandelion-Datatables * Some statistics * Future * Demo --- # Introducing Dandelion-Datatables
* First and most advanced component of the Dandelion framework * Facilitates the integration of
DataTables
, an awesome jQuery plugin which will add advanced interaction controls to any HTML table * Easy to use * Easy to extend * Inspired by the excellent
DisplayTag
library --- # Key features * JSP &
Thymeleaf
support * Typical features such as paging, filtering and sorting * DOM sources, AJAX sources and server-side processing * Responsive design * Export in multiple formats * Several themes available:
Bootstrap 2
,
Bootstrap 3
,
jQueryUI
* I18n * Integration with Spring/Spring MVC and other projects --- # What it looks like with JSP? ````xml <%@ taglib prefix="datatables" uri="http://github.com/dandelion/datatables" %>
```` * `id`: HTML pass-through attribute * `data`: DOM source, collection of POJOs * `title`: sets the content of the column header * `property`: name of the object's attribute of the collection being iterated on --- # What it looks like with Thymeleaf? ````xml
Id
Firstname
Lastname
City
Mail
1
John
Doe
Nobody knows!
john@doe.com
```` * `dt:table`: enables the Dandelion-Datatables dialect in the current table --- .soft[ # How it works? ] .left-column[ ## HTML ] .right-column[ The HTML is generated: * either by Dandelion-Datatables (JSP) * or directly by Thymeleaf ````html
Id
Firstname
Lastname
City
Mail
1
Wing
Cunningham
ornare.libero@mail.edu
...
```` ] --- .soft[ # How it works? ] .left-column[ ## HTML ## JavaScript ] .right-column[ As well as the JS code that initializes DataTables ````javascript var oTable_myTableId = $('#myTableId'); var oTable_myTableId_params = { "aoColumns":[ { "mData":"id", "sDefaultContent":"" }, { "mData":"lastName", "sDefaultContent":"" }, { "mData":"firstName", "sDefaultContent":"" }, { "mData":"address.town.name", "sDefaultContent":"" }, { "mData":"mail", "sDefaultContent":"" } ] } $(document).ready(function(){ oTable_myTableId.dataTable(oTable_myTableId_params); }); ```` ] --- .soft[ # How it works? ] .left-column[ ## HTML ## JavaScript ## Required assets ] .right-column[ An `AssetRequestContext` is filled with the required assets, either by the JSP taglib or by the Thymeleaf dialect... ````java AssetRequestContext .get(request) .addBundle("datatables") .addBundle("ddl-dt") ...; ```` ... so that they will be injected by Dandelion-Core into the response ````html
... ```` ] --- class: center, middle # Usage examples --- # HTML (DOM) sources * Read data **directly from the [DOM](http://en.wikipedia.org/wiki/Document_Object_Model)** * Works well with **small/medium** data sources --- # HTML sources — example .left-column[ ## Controller ] .right-column[ Example with a Spring MVC controller ````java @Controller public class Controller { @ModelAttribute("persons") public List
populateTable() { return personService.findAll(); } } ```` ] --- # HTML sources — example .left-column[ ## Controller ## JSP ] .right-column[ ````html
...
```` ] --- # HTML sources — example .left-column[ ## Controller ## JSP ## Thymeleaf ] .right-column[ ````html
...
1
...
```` ] --- # AJAX sources * Read data from virtually **any JSON data source** that can be obtained **by AJAX** * **Client-side** processing * Works well with **small/medium** data sources --- # AJAX sources .left-column[ ## Controller ] .right-column[ Example with a Spring MVC controller ````java @Controller public class AjaxController { @RequestMapping(value = "/persons") public @ResponseBody List
findAll() { return personService.findAll(); } } ```` ] --- # AJAX sources .left-column[ ## Controller ## JSP ] .right-column[ ````html
...
```` Dandelion-Datatables rewrites URLs by appending the context path, if needed. ] --- # AJAX sources .left-column[ ## Controller ## JSP ## Thymeleaf ] .right-column[ ````html
...
1
...
```` ] --- # AJAX sources + server-side processing * Read data from virtually **any JSON data source** that can be obtained by **AJAX** * **Server-side** processing * Each draw of the table will result in a **new AJAX request being made to get the required data** * Works well with **small/medium/large** data sources --- # AJAX sources + server-side processing ### Dandelion-Datatables provides a convenient API * `DatatablesCriterias` * Stores all DataTables parameters (current page, entries to display, sorted columns, ...) ````java @RequestMapping(value = "/persons") public @ResponseBody DatatablesResponse
filter(HttpServletRequest request) { // Maps request parameter to a DatatablesCriterias instance DatatablesCriterias criterias = DatatablesCriterias.getFromRequest(request); ... ```` --- # AJAX sources + server-side processing ### Dandelion-Datatables provides a convenient API * `DataSet` * All entries returned by the data access layer should be wrapped in this class in order to build a `DataTablesResponse` ````java ... // Get filtered and paged data DataSet
persons = personService.findPersons(criterias); ... ```` --- # AJAX sources + server-side processing ### Dandelion-Datatables provides a convenient API * `DataTablesResponse` * Contains a builder that helps to return the data in the format required by DataTables * Must be converted into JSON ````java ... // Build the response that will be converted to JSON return DatatablesResponse.build(persons, criterias); } ```` --- # AJAX sources + server-side processing .left-column[ ### Controller ] .right-column-small[ Example with a Spring MVC AJAX controller ````java @RequestMapping(value = "/persons") public @ResponseBody DatatablesResponse
filter(HttpServletRequest request) { // Maps request parameter to a DatatablesCriterias instance DatatablesCriterias criterias = DatatablesCriterias.getFromRequest(request); // Get filtered and paged data DataSet
persons = personService.findPersons(criterias); // Build the response that will be converted to JSON return DatatablesResponse.build(persons, criterias); } ```` ] --- # AJAX sources + server-side processing .left-column[ ### Controller ### JSP ] .right-column[ ````xml
```` ] --- # AJAX sources + server-side processing .left-column[ ### Controller ### JSP ### Thymeleaf ] .right-column[ ````xml
Id
Firstname
Lastname
City
Mail
```` ] --- # Paging, sorting, filtering .left-column[ ## JSP ] .right-column[ ````html
...
```` ] --- # Paging, sorting, filtering .left-column[ ## JSP ## Thymeleaf ] .right-column[ ````html
Id
LastName
...
...
```` ] --- # Export — what formats? ## Text-based formats: CSV, XML Built-in support, no dependency or extra required. --- # Export — what formats? ## Binary-based formats: PDF, XLS, XLSX Some extras already exist and provide: * an export class that is used by default if the corresponding export format is enabled * the corresponding library in compile scope (e.g.
Apache POI
for the XLS format) Available extras: * PDF:
datatables-export-itext
* XLS:
datatables-export-poi
* XLSX:
datatables-export-poi-ooxml
--- # Export — 2 ways ## Filter-based * Fast to set up * Always export full data source * Only compatible with DOM sources ## Controller-based * Requires a bit more work to set up * WYSIWYE => What You See Is What You ... Export! * Compatible with DOM and AJAX sources --- # Export — filter-based .left-column[ ## New dependency ] .right-column[ ````xml
com.github.dandelion
datatables-export-itext
0.10.0
```` ] --- # Export — filter-based .left-column[ ## New dependency ## web.xml ] .right-column[ ````xml
datatables
...DatatablesFilter
datatables
/*
```` ] --- # Export — filter-based .left-column[ ## New dependency ## web.xml ## JSP ] .right-column[ ````xml
```` ] --- # Export — filter-based .left-column[ ## New dependency ## web.xml ## JSP ## Thymeleaf ] .right-column[ ````xml
Id
Firstname
Lastname
City
Mail
1
John
Doe
Nobody knows!
john@doe.com
```` ] --- # Export — controller-based .left-column[ ### New dependency ] .right-column[ ````xml
com.github.dandelion
datatables-export-itext
0.10.0
```` ] --- # Export — controller-based .left-column[ ### New dependency ### JSP ] .right-column[ ````xml
...
```` ] --- # Export — controller-based .left-column[ ### New dependency ### JSP ### Thymeleaf ] .right-column[ ````xml
Id
Firstname
Lastname
City
Mail
```` ] --- # Export — controller-based .left-column[ ### New dependency ### JSP ### Thymeleaf ### Controller (1/4) ] .right-column-small[ ````java @RequestMapping(value = "/export", produces = "application/pdf") public void pdf(@DatatablesParams DatatablesCriterias criterias, HttpServletRequest request, HttpServletResponse response) { // Get data to export using the provided criteria List
persons = personService.findPersons(criterias); ... ```` ] --- # Export — controller-based .left-column[ ### New dependency ### JSP ### Thymeleaf ### Controller (2/4) ] .right-column-small[ ````java ... // Build the export configuration ExportConf exportPdfConf = new ExportConf.Builder("pdf") .header(true) .exportClass(new PdfExport()) .build(); ... ```` ] --- # Export — controller-based .left-column[ ### New dependency ### JSP ### Thymeleaf ### Controller (3/4) ] .right-column-small[ ````java ... // Build the table to export using the above data // and the export configuration HtmlTable table = new HtmlTableBuilder
() .newBuilder("tableId", persons, request, exportPdfConf) .column().fillWithProperty("id").title("Id") .column().fillWithProperty("firstName").title("Firtname") .column().fillWithProperty("lastName").title("Lastname") .column().fillWithProperty("address.town.name").title("City") .column().fillWithProperty("mail").title("Mail") .column() .fillWithProperty("birthDate", "{0,date,dd-MM-yyyy}") .title("BirthDate") .build(); ... ```` ] --- # Export — controller-based .left-column[ ### New dependency ### JSP ### Thymeleaf ### Controller (4/4) ] .right-column-small[ ````java ... // Render the export file in the browser ExportUtils.renderExport(table, exportPdfConf, response); } ```` ] --- # I18n: DataTables' information **datatables.properties**: non-translatable properties ````xml global.css.class=table table-striped table-bordered table-condensed global.extra.theme=bootstrap2 ```` **datatables_EN.properties**: all EN translations ````xml global.i18n.msg.processing=Processing... global.i18n.msg.search=Search: global.i18n.msg.info=Showing _START_ to _END_ of _TOTAL_ entries ```` **datatables_FR.properties**: all FR translations ````xml global.i18n.msg.processing=Traitement en cours... global.i18n.msg.search=Rechercher : global.i18n.msg.info=Affichage de l'élément _START_ à _END_ sur _TOTAL_ éléments ```` --- # I18n: column headers * Based on `LocaleResolver` and `MessageResolver` interfaces * Built-in implementations: * `SpringLocaleResolver` / `SpringMessageResolver` * `JstlLocaleResolver` / `JstlMessageResolver` * `Struts1LocaleResolver` / `Struts1MessageResolver` * `Struts2LocaleResolver` / `Struts2MessageResolver` * Possible to plug-in custom locale and message resolvers --- # I18n: example with Spring .left-column[ ### Spring config ] .right-column[ ````xml
```` ] --- # I18n: example with Spring .left-column[ ### Spring config ### Resource bundles ] .right-column[ Location: ````xml src |__ main |__ resources |__ messages.properties |__ messages_FR.properties ```` messages.properties: ````xml table.header.lastname=Lastname table.header.firstname=Firstname ```` messages_FR.properties: ````xml table.header.lastname=Nom table.header.firstname=Prénom ```` ] --- # I18n: example with Spring .left-column[ ### Spring config ### Resource bundles ### JSP ] .right-column[ ````xml
...
...
```` ] --- # I18n: example with Spring .left-column[ ### Spring config ### Resource bundles ### JSP ### Thymeleaf ] .right-column[ ````xml
...
Firstname
Lastname
...
...
```` ] --- # Theming Supported themes: *
jQueryUI ThemeRoller
*
Bootstrap 2
*
Tablecloth
*
Responsive
*
Bootstrap 3
*
Responsive
--- # Theming .left-column[ ## JSP ] .right-column[ ````html
```` ] --- # Theming .left-column[ ## JSP ## Thymeleaf ] .right-column[ ````html
Id
Firstname
Lastname
Street
Mail
1
John
Doe
Nobody knows!
john@doe.com
```` ] --- class: agenda, right * Quick introduction * Brief history * Dandelion-Core * Dandelion-Datatables * # Some statistics * Future * Demo --- # Some statistics * 35 releases (source:
GitHub
, all repos) * 15 pull requests merged (source:
GitHub
, all repos) * 9 contributors from 6 countries (source:
GitHub
, all repos) * 1432 commits (source:
Ohloh
) * 121,770 LOC (source:
Ohloh
) * 98 GitHub stars (source:
GitHub
, all repos) * ~80 followers on Twitter (source:
Twitter
) * ~270 topics, ~10000 views in the forum (source:
Nabble
) * 3200+ redeploys/restarts prevented, saving about 106h (source:
JRebel
)
--- class: agenda, right * Quick introduction * Brief history * Dandelion-Core * Dandelion-Datatables * Some statistics * # Future * Demo --- # Future .left-column[ ### Dandelion-Core ] .right-column[ - Full support for asset minification and merging - Improvements on asset bundles: externalization, configuration via XML, JavaConfig - Add new processors for meta-frameworks (CoffeeScript, Less, ...) - Add support for conditionnal assets (e.g. IE8+, IE9+) - Add support for CSS sprites - New `AssetCache` implementation based on
Hazelcast
-
JBoss Forge
plugin allowing to scaffold a project based on Dandelion - Add support for
RequireJS
-
Bower
integration ] --- # Future .left-column[ ### Dandelion-Core ### Dandelion-Datatables ] .right-column[ * New extension for editable tables (
Editor
,
jEditable
, ...) * Add support for more
DataTables' extensions
* Add support for
DataTables 1.10.0
* New theme for
Foundation
* Add WebSocket support for continously-updating tables (
Atmosphere
, Spring MVC 4) * Add support for
Spring Data REST
] --- # Future .left-column[ ### Dandelion-Core ### Dandelion-Datatables ### New components! ] .right-column[ * Dandelion-GUA? (Google Universal Analytics) * Dandelion-Forms? *
All ideas are welcome
! ] --- class: agenda, right * Quick introduction * Brief history * Dandelion-Core * Dandelion-Datatables * Some statistics * Future * # Demo --- # Demo .center[ ## https://github.com/dandelion/dandelion-samples ]