SlideShare une entreprise Scribd logo
1  sur  36
Télécharger pour lire hors ligne
Datagrids with Symfony 2,
Backbone and Backgrid
Eugenio Pombi & Giorgio Cefaro
requirements - composer
http://getcomposer.org
Run this in your terminal to get the latest Composer version:
curl -sS https://getcomposer.org/installer | php
Or if you don't have curl:
php -r "eval('?>'.file_get_contents('https://getcomposer.org/installer'));"
requirements - symfony
http://symfony.com/download
Create a symfony 2.3.1 project in path/:
php composer.phar create-project symfony/framework-standard-edition path/ 2.3.1
requirements - dependencies
composer.json:
"require": {
[...]
"friendsofsymfony/rest-bundle": "0.12",
"jms/serializer-bundle": "dev-master",
"jms/di-extra-bundle": "dev-master",
"friendsofsymfony/jsrouting-bundle": "~1.1"
},
requirements - FOSRestBundle
https://github.com/FriendsOfSymfony/FOSRestBundle
app/config/config.yml:
fos_rest:
param_fetcher_listener: true
body_listener: true
format_listener: true
view:
view_response_listener: 'force'
requirements - javascript libs
Download the required libs:
http://backbonejs.org/
http://underscorejs.org/
http://jquery.com/
http://backgridjs.com/
http://twitter.github.io/bootstrap/
requirements - javascript libs
Place the libraries in src/Acme/MyBundle/Resources/public/js/ and include
them with Assetic:
base.html.yml:
{% block javascripts %}
{% javascripts
'bundles/mwtbrokertool/js/di-lite.js'
'bundles/mwtbrokertool/js/jquery.js'
'bundles/mwtbrokertool/js/underscore.js'
'bundles/mwtbrokertool/js/bootstrap.js'
'bundles/mwtbrokertool/js/backbone.js'
%}
<script src="{{ asset_url }}" type="text/javascript"></script>
{% endjavascripts %}
<script src="{{ asset('/js/fos_js_routes.js') }}"></script>
{% endblock %}
controllers - index
/**
* @ParamConverter("user", class="MyBundle:User", options={"id" = "userId"})
* @FosRestGet("/ticket.{_format}",
* name="mwt_brokertool_ticket",
* defaults={"_format": "json"},
* options={"expose"=true})
*/
public function indexAction(User $user)
{
$em = $this->getDoctrine()->getManager();
$repo = $em->getRepository('MyBundle:Ticket');
$tickets = $repo->findBySellerJoinAll($user);
return $tickets;
}
controllers - new
/**
* @ParamConverter("user", class="MyBundle:User", options={"id" = "userId"})
* @FosRestPost("/ticket.{_format}",
* name="My_bundle_ticket_new",
* defaults={"_format": "json"},
* options={"expose"=true}
* )
* @FosRestView
* @param User $user
*/
public function newAction(User $user)
{
[...]
}
controllers - new ticket
$ticket = new Ticket();
$form = $this->createForm(new TicketType(), $ticket);
$data = $this->getRequest()->request->all();
$children = $form->all();
$data = array_intersect_key($data, $children);
$form->submit($data);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($ticket);
$em->flush();
return View::create($ticket, 201);
}
return View::create($form, 400);
test index
public function testIndex()
{
$client = static::createClient();
$crawler = $client->request('GET','/'.$this->user1->getId().'/ticket');
$this->assertTrue($client->getResponse()->isSuccessful());
$json_response = json_decode($client->getResponse()->getContent(), true);
$this->assertTrue(is_array($json_response));
$this->assertTrue(isset($json_response[0]['event_id']));
$this->assertTrue(isset($json_response[1]['event_id']));
$this->assertTrue(isset($json_response[2]['event_id']));
}
test new ticket
$client = static::createClient();
$client->request(
'POST',
'/' . $this->user1->getId() . '/ticket',
array(),
array(),
array('CONTENT_TYPE' => 'application/json'),
'[aJsonString]'
);
$this->assertEquals(201, $client->getResponse()->getStatusCode());
json_response = json_decode($client->getResponse()->getContent(), true);
$this->assertTrue(is_array($json_response));
$ticket = $this->em->getRepository('ACMEMyBundle:Ticket')->findOneBy(array
(...);
$this->assertNotNull($ticket);
backgrid
backgrid
backgrid
backgridjs.com
The goal of Backgrid.js is to produce a set of
core Backbone UI elements that offer you all
the basic displaying, sorting and editing
functionalities you'd expect, and to create an
elegant API that makes extending Backgrid.js
with extra functionalities easy.
backgrid
Backgrid.js depends on 3 libraries to function:
● jquery >= 1.7.0
● underscore.js ~ 1.4.0
● backbone.js >= 0.9.10
backgrid
● Solid foundation. Based on Backbone.js.
● Semantic and easily stylable. Just style with plain CSS like
you would a normal HTML table.
● Low learning curve. Works with plain old Backbone models
and collections. Easy things are easy, hards things possible.
● Highly modular and customizable. Componenets are just
simple Backbone View classes, customization is easy if you
already know Backbone.
● Lightweight. Extra features are separated into extensions,
which keeps the bloat away.
di-lite.js
minimalistic dependency injection container
ctx.register("name", instance);
ctx.get("name");
My.Stuff = Backbone.Collection.extend({
dependencies: "name",
[...]
});
di-lite.js - example
var ctx = di.createContext();
var user = function () {
this.id = $("#grid").attr('data-user);
};
ctx.register("user", user);
var App.Collections.Articles = Backbone.Collection.extend({
dependencies: "user",
model: App.Models.Article,
url: function() {
return '/article?userId=' + this.user.id;
}
[...]
});
ctx.register("articles", App.Collections.Articles);
backbone model + collection
var Ticket = Backbone.Model.extend({});
var Tickets = Backbone.Collection.extend({
model: Territory,
url: Routing.generate('my_bundle_ticket', { userId: App.userId })
});
var tickets = new Tickets();
backbone associations
Associations allows Backbone applications to model 1:1 & 1:
N associations between application models and Collections.
https://github.com/dhruvaray/backbone-associations
var TicketGroup = Backbone.AssociatedModel.extend({
relations: [
{
type: Backbone.Many,
key: 'tickets',
relatedModel: 'Ticket'
}]
});
backgrid columns
var columns = [{
name: "event_name",
label: "Event",
cell: "string" ,
editable: false,
}, {
name: "event_datetime",
label: "Event Date",
cell: "datetime"
}];
backgrid initialize
var grid = new Backgrid.Grid({
columns: columns,
collection: tickets
});
$("#my-list").append(grid.render().$el);
// Fetch some tickets from the url
tickets.fetch({reset: true});
backgrid - computed fields
https://github.com/alexanderbeletsky/backbone-computedfields
var CartItem = Backbone.Model.extend({
initialize: function () {
this.computedFields = new Backbone.ComputedFields(this);
},
computed: {
grossPrice: {
depends: ['netPrice', 'vatRate'],
get: function (fields) {
return fields.netPrice * (1 + fields.vatRate / 100);
}
}
}
});
backgrid - computed fields
var columns = [{
name: "netPrice",
label: "Net Price",
cell: "number"
}, {
name: "vatRate",
label: "VAT Rate",
cell: "integer"
}, {
name: "grossPrice",
label: "Gross price",
cell: "number"
}];
backgrid - select editor
{
name: "country",
label: "Country",
cell: Backgrid.SelectCell.extend({
optionValues: ctx.get('countries').getAsOptions()
})
}
backgrid - select editor
App.Collections.Countries = Backbone.Collection.extend({
getAsOptions: function () {
var options = new Array();
this.models.forEach(function(item) {
options.push([item.get('name'), item.get('id')])
});
return options;
}
});
toggle cell - column definition
{
name: 'nonModelField',
label: 'Details',
editable: false,
cell: Backgrid.ToggleCell,
subtable: function(el, model) {
var subtable = new Backgrid.Grid({
columns: columns,
collection: model.get('tickets')
});
el.append(subtable.render().$el);
return subtable;
}
toggle cell - cell extension
Backgrid.ToggleCell = Backgrid.Cell.extend({
[...]
});
toggle cell - cell extension -
render
Backgrid.ToggleCell = Backgrid.Cell.extend({
[...]
render: function() {
this.$el.empty();
var new_el = $('<span class="toggle"></span>');
this.$el.append(new_el);
this.set_toggle().delegateEvents();
return this;
}
});
toggle cell - cell extension -
event
set_toggle: function() {
var self = this;
var td_el = this.$el;
td_el.find('.toggle').click( function() {
var details_row = td_el.closest('tr').next('.child-table');
if (details_row.length > 0) {
$(details_row).remove();
} else {
details_row = $('<tr class="child-table"><td colspan="100"></td></tr>');
$(this).closest('tr').after(details_row);
self.subtable = self.column.get('subtable')(details_row.find('td'), self.model);
}
});
return this;
}
retrieve data - model
App.Models.TicketGroup = Backbone.AssociatedModel.extend({
relations: [
{
type: Backbone.Many,
key: tickets,
relatedModel: 'App.Models.Ticket'
}
],
[...]
});
retrieve data - collection
App.Collections.TicketGroups = Backbone.Collection.extend({
model: App.Models.TicketGroup,
parse: function(tickets, options) {
[...]
return ticketGroups;
},
});
retrieve data - collection
var ticketGroups = [];
_.each(tickets, function (element, index, list) {
var foundElement = _.findWhere(
ticketGroups,
{event_id: element.event_id}
)
if (foundElement == null) {
ticketGroups.push({
"event_id": element.event_id,
"event_name": element.event_name,
"tickets": [element]
});
} else {
foundElement.tickets.push(element);
}
}, this);
testing!
describe("TicketGroups Collection", function () {
describe("parse", function () {
beforeEach(function () {
this.ticketGroupCollection = new App.Collections.TicketGroups();
});
it("parse should return a ticketGroup with nested tickets", function ()
{
var jsonWith3Records = [...];
var result = this.ticketGroupCollection.parse(jsonWith3Records, {});
result.should.have.length(2);
var firstResult = result[0];
firstResult.event_name.should.equal("Concerto Iron Maiden");
firstResult.tickets.should.have.length(2);
var secondResult = result[1];
secondResult.event_name.should.equal("Battle Hymns Tour");
secondResult.tickets.should.have.length(1);
//close brackets
thanks
@giorrrgio
giorgiocefaro.com
@euxpom
nerd2business.net

Contenu connexe

Tendances

"Mobage DBA Fight against Big Data" - NHN TE
"Mobage DBA Fight against Big Data" - NHN TE"Mobage DBA Fight against Big Data" - NHN TE
"Mobage DBA Fight against Big Data" - NHN TE
Ryosuke IWANAGA
 
Controlling The Cloud With Python
Controlling The Cloud With PythonControlling The Cloud With Python
Controlling The Cloud With Python
Luca Mearelli
 
Introducing Assetic (NYPHP)
Introducing Assetic (NYPHP)Introducing Assetic (NYPHP)
Introducing Assetic (NYPHP)
Kris Wallsmith
 

Tendances (20)

"Mobage DBA Fight against Big Data" - NHN TE
"Mobage DBA Fight against Big Data" - NHN TE"Mobage DBA Fight against Big Data" - NHN TE
"Mobage DBA Fight against Big Data" - NHN TE
 
Writing Redis in Python with asyncio
Writing Redis in Python with asyncioWriting Redis in Python with asyncio
Writing Redis in Python with asyncio
 
And now you have two problems. Ruby regular expressions for fun and profit by...
And now you have two problems. Ruby regular expressions for fun and profit by...And now you have two problems. Ruby regular expressions for fun and profit by...
And now you have two problems. Ruby regular expressions for fun and profit by...
 
Symfony2 revealed
Symfony2 revealedSymfony2 revealed
Symfony2 revealed
 
Advanced symfony Techniques
Advanced symfony TechniquesAdvanced symfony Techniques
Advanced symfony Techniques
 
A Functional Guide to Cat Herding with PHP Generators
A Functional Guide to Cat Herding with PHP GeneratorsA Functional Guide to Cat Herding with PHP Generators
A Functional Guide to Cat Herding with PHP Generators
 
Doctrine MongoDB ODM (PDXPHP)
Doctrine MongoDB ODM (PDXPHP)Doctrine MongoDB ODM (PDXPHP)
Doctrine MongoDB ODM (PDXPHP)
 
A Little Backbone For Your App
A Little Backbone For Your AppA Little Backbone For Your App
A Little Backbone For Your App
 
Kickin' Ass with Cache-Fu (without notes)
Kickin' Ass with Cache-Fu (without notes)Kickin' Ass with Cache-Fu (without notes)
Kickin' Ass with Cache-Fu (without notes)
 
Kickin' Ass with Cache-Fu (with notes)
Kickin' Ass with Cache-Fu (with notes)Kickin' Ass with Cache-Fu (with notes)
Kickin' Ass with Cache-Fu (with notes)
 
Controlling The Cloud With Python
Controlling The Cloud With PythonControlling The Cloud With Python
Controlling The Cloud With Python
 
Scaling Symfony2 apps with RabbitMQ - Symfony UK Meetup
Scaling Symfony2 apps with RabbitMQ - Symfony UK MeetupScaling Symfony2 apps with RabbitMQ - Symfony UK Meetup
Scaling Symfony2 apps with RabbitMQ - Symfony UK Meetup
 
Api Design
Api DesignApi Design
Api Design
 
Introducing Assetic (NYPHP)
Introducing Assetic (NYPHP)Introducing Assetic (NYPHP)
Introducing Assetic (NYPHP)
 
Introduction to Nodejs
Introduction to NodejsIntroduction to Nodejs
Introduction to Nodejs
 
Ansible leveraging 2.0
Ansible leveraging 2.0Ansible leveraging 2.0
Ansible leveraging 2.0
 
Reactive Access to MongoDB from Scala
Reactive Access to MongoDB from ScalaReactive Access to MongoDB from Scala
Reactive Access to MongoDB from Scala
 
Read, store and create xml and json
Read, store and create xml and jsonRead, store and create xml and json
Read, store and create xml and json
 
Psycopg2 - Connect to PostgreSQL using Python Script
Psycopg2 - Connect to PostgreSQL using Python ScriptPsycopg2 - Connect to PostgreSQL using Python Script
Psycopg2 - Connect to PostgreSQL using Python Script
 
Python mongo db-training-europython-2011
Python mongo db-training-europython-2011Python mongo db-training-europython-2011
Python mongo db-training-europython-2011
 

Similaire à Datagrids with Symfony 2, Backbone and Backgrid

WebNet Conference 2012 - Designing complex applications using html5 and knock...
WebNet Conference 2012 - Designing complex applications using html5 and knock...WebNet Conference 2012 - Designing complex applications using html5 and knock...
WebNet Conference 2012 - Designing complex applications using html5 and knock...
Fabio Franzini
 
JavaScript Growing Up
JavaScript Growing UpJavaScript Growing Up
JavaScript Growing Up
David Padbury
 
How and why i roll my own node.js framework
How and why i roll my own node.js frameworkHow and why i roll my own node.js framework
How and why i roll my own node.js framework
Ben Lin
 
Laurens Van Den Oever Xopus Presentation
Laurens Van Den Oever Xopus PresentationLaurens Van Den Oever Xopus Presentation
Laurens Van Den Oever Xopus Presentation
Ajax Experience 2009
 

Similaire à Datagrids with Symfony 2, Backbone and Backgrid (20)

using Mithril.js + postgREST to build and consume API's
using Mithril.js + postgREST to build and consume API'susing Mithril.js + postgREST to build and consume API's
using Mithril.js + postgREST to build and consume API's
 
Code Splitting in Practice - Shanghai JS Meetup May 2016
Code Splitting in Practice - Shanghai JS Meetup May 2016Code Splitting in Practice - Shanghai JS Meetup May 2016
Code Splitting in Practice - Shanghai JS Meetup May 2016
 
WebNet Conference 2012 - Designing complex applications using html5 and knock...
WebNet Conference 2012 - Designing complex applications using html5 and knock...WebNet Conference 2012 - Designing complex applications using html5 and knock...
WebNet Conference 2012 - Designing complex applications using html5 and knock...
 
Rails Engine | Modular application
Rails Engine | Modular applicationRails Engine | Modular application
Rails Engine | Modular application
 
Deep Learning for Computer Vision: Software Frameworks (UPC 2016)
Deep Learning for Computer Vision: Software Frameworks (UPC 2016)Deep Learning for Computer Vision: Software Frameworks (UPC 2016)
Deep Learning for Computer Vision: Software Frameworks (UPC 2016)
 
IOC + Javascript
IOC + JavascriptIOC + Javascript
IOC + Javascript
 
Lambdas puzzler - Peter Lawrey
Lambdas puzzler - Peter LawreyLambdas puzzler - Peter Lawrey
Lambdas puzzler - Peter Lawrey
 
Lecture: Webpack 4
Lecture: Webpack 4Lecture: Webpack 4
Lecture: Webpack 4
 
backend
backendbackend
backend
 
backend
backendbackend
backend
 
Webpack: your final module bundler
Webpack: your final module bundlerWebpack: your final module bundler
Webpack: your final module bundler
 
Designing REST API automation tests in Kotlin
Designing REST API automation tests in KotlinDesigning REST API automation tests in Kotlin
Designing REST API automation tests in Kotlin
 
JavaScript Growing Up
JavaScript Growing UpJavaScript Growing Up
JavaScript Growing Up
 
Symfony2 - from the trenches
Symfony2 - from the trenchesSymfony2 - from the trenches
Symfony2 - from the trenches
 
How and why i roll my own node.js framework
How and why i roll my own node.js frameworkHow and why i roll my own node.js framework
How and why i roll my own node.js framework
 
Laurens Van Den Oever Xopus Presentation
Laurens Van Den Oever Xopus PresentationLaurens Van Den Oever Xopus Presentation
Laurens Van Den Oever Xopus Presentation
 
RequireJS
RequireJSRequireJS
RequireJS
 
The Ring programming language version 1.8 book - Part 95 of 202
The Ring programming language version 1.8 book - Part 95 of 202The Ring programming language version 1.8 book - Part 95 of 202
The Ring programming language version 1.8 book - Part 95 of 202
 
Frontend JS workflow - Gulp 4 and the like
Frontend JS workflow - Gulp 4 and the likeFrontend JS workflow - Gulp 4 and the like
Frontend JS workflow - Gulp 4 and the like
 
Play 2.0
Play 2.0Play 2.0
Play 2.0
 

Plus de eugenio pombi (7)

Parlo al mio codice
Parlo al mio codiceParlo al mio codice
Parlo al mio codice
 
Processing one year of leading for Pug roma
Processing one year of leading for Pug romaProcessing one year of leading for Pug roma
Processing one year of leading for Pug roma
 
Codemotion workshop
Codemotion workshopCodemotion workshop
Codemotion workshop
 
Arduino - il mio primo sketch
Arduino - il mio primo sketchArduino - il mio primo sketch
Arduino - il mio primo sketch
 
PHPUnit elevato alla Symfony2
PHPUnit elevato alla Symfony2PHPUnit elevato alla Symfony2
PHPUnit elevato alla Symfony2
 
Appetite comes with testing
Appetite comes with testingAppetite comes with testing
Appetite comes with testing
 
breve introduzione a node.js
breve introduzione a node.jsbreve introduzione a node.js
breve introduzione a node.js
 

Dernier

Why Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire businessWhy Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire business
panagenda
 

Dernier (20)

Why Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire businessWhy Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire business
 
Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot TakeoffStrategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
 
MS Copilot expands with MS Graph connectors
MS Copilot expands with MS Graph connectorsMS Copilot expands with MS Graph connectors
MS Copilot expands with MS Graph connectors
 
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
 
Artificial Intelligence Chap.5 : Uncertainty
Artificial Intelligence Chap.5 : UncertaintyArtificial Intelligence Chap.5 : Uncertainty
Artificial Intelligence Chap.5 : Uncertainty
 
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWEREMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
 
Apidays Singapore 2024 - Scalable LLM APIs for AI and Generative AI Applicati...
Apidays Singapore 2024 - Scalable LLM APIs for AI and Generative AI Applicati...Apidays Singapore 2024 - Scalable LLM APIs for AI and Generative AI Applicati...
Apidays Singapore 2024 - Scalable LLM APIs for AI and Generative AI Applicati...
 
Strategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a FresherStrategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a Fresher
 
ICT role in 21st century education and its challenges
ICT role in 21st century education and its challengesICT role in 21st century education and its challenges
ICT role in 21st century education and its challenges
 
2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...
 
Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)
 
Apidays New York 2024 - Accelerating FinTech Innovation by Vasa Krishnan, Fin...
Apidays New York 2024 - Accelerating FinTech Innovation by Vasa Krishnan, Fin...Apidays New York 2024 - Accelerating FinTech Innovation by Vasa Krishnan, Fin...
Apidays New York 2024 - Accelerating FinTech Innovation by Vasa Krishnan, Fin...
 
Boost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfBoost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdf
 
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
 
Data Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonData Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt Robison
 
Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...
 
TrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
TrustArc Webinar - Unlock the Power of AI-Driven Data DiscoveryTrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
TrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
 
Manulife - Insurer Transformation Award 2024
Manulife - Insurer Transformation Award 2024Manulife - Insurer Transformation Award 2024
Manulife - Insurer Transformation Award 2024
 
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin WoodPolkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 

Datagrids with Symfony 2, Backbone and Backgrid

  • 1. Datagrids with Symfony 2, Backbone and Backgrid Eugenio Pombi & Giorgio Cefaro
  • 2. requirements - composer http://getcomposer.org Run this in your terminal to get the latest Composer version: curl -sS https://getcomposer.org/installer | php Or if you don't have curl: php -r "eval('?>'.file_get_contents('https://getcomposer.org/installer'));"
  • 3. requirements - symfony http://symfony.com/download Create a symfony 2.3.1 project in path/: php composer.phar create-project symfony/framework-standard-edition path/ 2.3.1
  • 4. requirements - dependencies composer.json: "require": { [...] "friendsofsymfony/rest-bundle": "0.12", "jms/serializer-bundle": "dev-master", "jms/di-extra-bundle": "dev-master", "friendsofsymfony/jsrouting-bundle": "~1.1" },
  • 6. requirements - javascript libs Download the required libs: http://backbonejs.org/ http://underscorejs.org/ http://jquery.com/ http://backgridjs.com/ http://twitter.github.io/bootstrap/
  • 7. requirements - javascript libs Place the libraries in src/Acme/MyBundle/Resources/public/js/ and include them with Assetic: base.html.yml: {% block javascripts %} {% javascripts 'bundles/mwtbrokertool/js/di-lite.js' 'bundles/mwtbrokertool/js/jquery.js' 'bundles/mwtbrokertool/js/underscore.js' 'bundles/mwtbrokertool/js/bootstrap.js' 'bundles/mwtbrokertool/js/backbone.js' %} <script src="{{ asset_url }}" type="text/javascript"></script> {% endjavascripts %} <script src="{{ asset('/js/fos_js_routes.js') }}"></script> {% endblock %}
  • 8. controllers - index /** * @ParamConverter("user", class="MyBundle:User", options={"id" = "userId"}) * @FosRestGet("/ticket.{_format}", * name="mwt_brokertool_ticket", * defaults={"_format": "json"}, * options={"expose"=true}) */ public function indexAction(User $user) { $em = $this->getDoctrine()->getManager(); $repo = $em->getRepository('MyBundle:Ticket'); $tickets = $repo->findBySellerJoinAll($user); return $tickets; }
  • 9. controllers - new /** * @ParamConverter("user", class="MyBundle:User", options={"id" = "userId"}) * @FosRestPost("/ticket.{_format}", * name="My_bundle_ticket_new", * defaults={"_format": "json"}, * options={"expose"=true} * ) * @FosRestView * @param User $user */ public function newAction(User $user) { [...] }
  • 10. controllers - new ticket $ticket = new Ticket(); $form = $this->createForm(new TicketType(), $ticket); $data = $this->getRequest()->request->all(); $children = $form->all(); $data = array_intersect_key($data, $children); $form->submit($data); if ($form->isValid()) { $em = $this->getDoctrine()->getManager(); $em->persist($ticket); $em->flush(); return View::create($ticket, 201); } return View::create($form, 400);
  • 11. test index public function testIndex() { $client = static::createClient(); $crawler = $client->request('GET','/'.$this->user1->getId().'/ticket'); $this->assertTrue($client->getResponse()->isSuccessful()); $json_response = json_decode($client->getResponse()->getContent(), true); $this->assertTrue(is_array($json_response)); $this->assertTrue(isset($json_response[0]['event_id'])); $this->assertTrue(isset($json_response[1]['event_id'])); $this->assertTrue(isset($json_response[2]['event_id'])); }
  • 12. test new ticket $client = static::createClient(); $client->request( 'POST', '/' . $this->user1->getId() . '/ticket', array(), array(), array('CONTENT_TYPE' => 'application/json'), '[aJsonString]' ); $this->assertEquals(201, $client->getResponse()->getStatusCode()); json_response = json_decode($client->getResponse()->getContent(), true); $this->assertTrue(is_array($json_response)); $ticket = $this->em->getRepository('ACMEMyBundle:Ticket')->findOneBy(array (...); $this->assertNotNull($ticket);
  • 15. backgrid backgridjs.com The goal of Backgrid.js is to produce a set of core Backbone UI elements that offer you all the basic displaying, sorting and editing functionalities you'd expect, and to create an elegant API that makes extending Backgrid.js with extra functionalities easy.
  • 16. backgrid Backgrid.js depends on 3 libraries to function: ● jquery >= 1.7.0 ● underscore.js ~ 1.4.0 ● backbone.js >= 0.9.10
  • 17. backgrid ● Solid foundation. Based on Backbone.js. ● Semantic and easily stylable. Just style with plain CSS like you would a normal HTML table. ● Low learning curve. Works with plain old Backbone models and collections. Easy things are easy, hards things possible. ● Highly modular and customizable. Componenets are just simple Backbone View classes, customization is easy if you already know Backbone. ● Lightweight. Extra features are separated into extensions, which keeps the bloat away.
  • 18. di-lite.js minimalistic dependency injection container ctx.register("name", instance); ctx.get("name"); My.Stuff = Backbone.Collection.extend({ dependencies: "name", [...] });
  • 19. di-lite.js - example var ctx = di.createContext(); var user = function () { this.id = $("#grid").attr('data-user); }; ctx.register("user", user); var App.Collections.Articles = Backbone.Collection.extend({ dependencies: "user", model: App.Models.Article, url: function() { return '/article?userId=' + this.user.id; } [...] }); ctx.register("articles", App.Collections.Articles);
  • 20. backbone model + collection var Ticket = Backbone.Model.extend({}); var Tickets = Backbone.Collection.extend({ model: Territory, url: Routing.generate('my_bundle_ticket', { userId: App.userId }) }); var tickets = new Tickets();
  • 21. backbone associations Associations allows Backbone applications to model 1:1 & 1: N associations between application models and Collections. https://github.com/dhruvaray/backbone-associations var TicketGroup = Backbone.AssociatedModel.extend({ relations: [ { type: Backbone.Many, key: 'tickets', relatedModel: 'Ticket' }] });
  • 22. backgrid columns var columns = [{ name: "event_name", label: "Event", cell: "string" , editable: false, }, { name: "event_datetime", label: "Event Date", cell: "datetime" }];
  • 23. backgrid initialize var grid = new Backgrid.Grid({ columns: columns, collection: tickets }); $("#my-list").append(grid.render().$el); // Fetch some tickets from the url tickets.fetch({reset: true});
  • 24. backgrid - computed fields https://github.com/alexanderbeletsky/backbone-computedfields var CartItem = Backbone.Model.extend({ initialize: function () { this.computedFields = new Backbone.ComputedFields(this); }, computed: { grossPrice: { depends: ['netPrice', 'vatRate'], get: function (fields) { return fields.netPrice * (1 + fields.vatRate / 100); } } } });
  • 25. backgrid - computed fields var columns = [{ name: "netPrice", label: "Net Price", cell: "number" }, { name: "vatRate", label: "VAT Rate", cell: "integer" }, { name: "grossPrice", label: "Gross price", cell: "number" }];
  • 26. backgrid - select editor { name: "country", label: "Country", cell: Backgrid.SelectCell.extend({ optionValues: ctx.get('countries').getAsOptions() }) }
  • 27. backgrid - select editor App.Collections.Countries = Backbone.Collection.extend({ getAsOptions: function () { var options = new Array(); this.models.forEach(function(item) { options.push([item.get('name'), item.get('id')]) }); return options; } });
  • 28. toggle cell - column definition { name: 'nonModelField', label: 'Details', editable: false, cell: Backgrid.ToggleCell, subtable: function(el, model) { var subtable = new Backgrid.Grid({ columns: columns, collection: model.get('tickets') }); el.append(subtable.render().$el); return subtable; }
  • 29. toggle cell - cell extension Backgrid.ToggleCell = Backgrid.Cell.extend({ [...] });
  • 30. toggle cell - cell extension - render Backgrid.ToggleCell = Backgrid.Cell.extend({ [...] render: function() { this.$el.empty(); var new_el = $('<span class="toggle"></span>'); this.$el.append(new_el); this.set_toggle().delegateEvents(); return this; } });
  • 31. toggle cell - cell extension - event set_toggle: function() { var self = this; var td_el = this.$el; td_el.find('.toggle').click( function() { var details_row = td_el.closest('tr').next('.child-table'); if (details_row.length > 0) { $(details_row).remove(); } else { details_row = $('<tr class="child-table"><td colspan="100"></td></tr>'); $(this).closest('tr').after(details_row); self.subtable = self.column.get('subtable')(details_row.find('td'), self.model); } }); return this; }
  • 32. retrieve data - model App.Models.TicketGroup = Backbone.AssociatedModel.extend({ relations: [ { type: Backbone.Many, key: tickets, relatedModel: 'App.Models.Ticket' } ], [...] });
  • 33. retrieve data - collection App.Collections.TicketGroups = Backbone.Collection.extend({ model: App.Models.TicketGroup, parse: function(tickets, options) { [...] return ticketGroups; }, });
  • 34. retrieve data - collection var ticketGroups = []; _.each(tickets, function (element, index, list) { var foundElement = _.findWhere( ticketGroups, {event_id: element.event_id} ) if (foundElement == null) { ticketGroups.push({ "event_id": element.event_id, "event_name": element.event_name, "tickets": [element] }); } else { foundElement.tickets.push(element); } }, this);
  • 35. testing! describe("TicketGroups Collection", function () { describe("parse", function () { beforeEach(function () { this.ticketGroupCollection = new App.Collections.TicketGroups(); }); it("parse should return a ticketGroup with nested tickets", function () { var jsonWith3Records = [...]; var result = this.ticketGroupCollection.parse(jsonWith3Records, {}); result.should.have.length(2); var firstResult = result[0]; firstResult.event_name.should.equal("Concerto Iron Maiden"); firstResult.tickets.should.have.length(2); var secondResult = result[1]; secondResult.event_name.should.equal("Battle Hymns Tour"); secondResult.tickets.should.have.length(1); //close brackets