datagrids with symfony 2, backbone and backgrid

36
Datagrids with Symfony 2, Backbone and Backgrid Eugenio Pombi & Giorgio Cefaro

Upload: giorgio-cefaro

Post on 27-Jan-2015

121 views

Category:

Technology


2 download

DESCRIPTION

These are the slides of the code-centered presentation I did with Eugenio Pombi at the Javascript User Group Roma and the PHP User Group Roma. In this presentation we try to show many powerful features of symfony2 and its bundles to work as a backend system for single page applications. On the client side we describe how we made a javascript editable grid using Backbone.js and its plugin for grids Backgrid.js.

TRANSCRIPT

Page 1: Datagrids with Symfony 2, Backbone and Backgrid

Datagrids with Symfony 2, Backbone and Backgrid

Eugenio Pombi & Giorgio Cefaro

Page 2: Datagrids with Symfony 2, Backbone and Backgrid

requirements - composerhttp://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'));"

Page 3: Datagrids with Symfony 2, Backbone and Backgrid

requirements - symfonyhttp://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

Page 4: Datagrids with Symfony 2, Backbone and Backgrid

requirements - dependenciescomposer.json:

"require": { [...] "friendsofsymfony/rest-bundle": "0.12", "jms/serializer-bundle": "dev-master", "jms/di-extra-bundle": "dev-master", "friendsofsymfony/jsrouting-bundle": "~1.1"},

Page 5: Datagrids with Symfony 2, Backbone and Backgrid

requirements - FOSRestBundlehttps://github.com/FriendsOfSymfony/FOSRestBundle

app/config/config.yml:

fos_rest:param_fetcher_listener: truebody_listener: trueformat_listener: trueview:

view_response_listener: 'force'

Page 6: Datagrids with Symfony 2, Backbone and Backgrid

requirements - javascript libsDownload the required libs:

http://backbonejs.org/http://underscorejs.org/http://jquery.com/http://backgridjs.com/http://twitter.github.io/bootstrap/

Page 7: Datagrids with Symfony 2, Backbone and Backgrid

requirements - javascript libsPlace 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 %}

Page 8: Datagrids with Symfony 2, Backbone and Backgrid

controllers - index/*** @ParamConverter("user", class="MyBundle:User", options={"id" = "userId"})* @FosRest\Get("/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;}

Page 9: Datagrids with Symfony 2, Backbone and Backgrid

controllers - new/**

* @ParamConverter("user", class="MyBundle:User", options={"id" = "userId"}) * @FosRest\Post("/ticket.{_format}", * name="My_bundle_ticket_new", * defaults={"_format": "json"}, * options={"expose"=true} * ) * @FosRest\View * @param User $user */

public function newAction(User $user){

[...]}

Page 10: Datagrids with Symfony 2, Backbone and Backgrid

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);

Page 11: Datagrids with Symfony 2, Backbone and Backgrid

test indexpublic 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']));}

Page 12: Datagrids with Symfony 2, Backbone and Backgrid

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);

Page 13: Datagrids with Symfony 2, Backbone and Backgrid

backgrid

Page 14: Datagrids with Symfony 2, Backbone and Backgrid

backgrid

Page 15: Datagrids with Symfony 2, Backbone and 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.

Page 16: Datagrids with Symfony 2, Backbone and Backgrid

backgrid

Backgrid.js depends on 3 libraries to function:● jquery >= 1.7.0● underscore.js ~ 1.4.0● backbone.js >= 0.9.10

Page 17: Datagrids with Symfony 2, Backbone and Backgrid

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.

Page 18: Datagrids with Symfony 2, Backbone and Backgrid

di-lite.js

minimalistic dependency injection container

ctx.register("name", instance);

ctx.get("name");

My.Stuff = Backbone.Collection.extend({ dependencies: "name", [...]});

Page 19: Datagrids with Symfony 2, Backbone and Backgrid

di-lite.js - examplevar 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);

Page 20: Datagrids with Symfony 2, Backbone and Backgrid

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();

Page 21: Datagrids with Symfony 2, Backbone and Backgrid

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' }]});

Page 22: Datagrids with Symfony 2, Backbone and Backgrid

backgrid columns

var columns = [{ name: "event_name", label: "Event", cell: "string" , editable: false,}, { name: "event_datetime", label: "Event Date", cell: "datetime"}];

Page 23: Datagrids with Symfony 2, Backbone and Backgrid

backgrid initialize

var grid = new Backgrid.Grid({ columns: columns, collection: tickets});

$("#my-list").append(grid.render().$el);

// Fetch some tickets from the urltickets.fetch({reset: true});

Page 24: Datagrids with Symfony 2, Backbone and Backgrid

backgrid - computed fieldshttps://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); } } }});

Page 25: Datagrids with Symfony 2, Backbone and Backgrid

backgrid - computed fieldsvar columns = [{ name: "netPrice", label: "Net Price", cell: "number" }, { name: "vatRate", label: "VAT Rate", cell: "integer"}, {

name: "grossPrice",

label: "Gross price", cell: "number"}];

Page 26: Datagrids with Symfony 2, Backbone and Backgrid

backgrid - select editor

{ name: "country", label: "Country", cell: Backgrid.SelectCell.extend({ optionValues: ctx.get('countries').getAsOptions() })}

Page 27: Datagrids with Symfony 2, Backbone and Backgrid

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;

}});

Page 28: Datagrids with Symfony 2, Backbone and Backgrid

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; }

Page 29: Datagrids with Symfony 2, Backbone and Backgrid

toggle cell - cell extensionBackgrid.ToggleCell = Backgrid.Cell.extend({ [...]});

Page 30: Datagrids with Symfony 2, Backbone and Backgrid

toggle cell - cell extension - renderBackgrid.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; }});

Page 31: Datagrids with Symfony 2, Backbone and Backgrid

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;

}

Page 32: Datagrids with Symfony 2, Backbone and Backgrid

retrieve data - modelApp.Models.TicketGroup = Backbone.AssociatedModel.extend({ relations: [ { type: Backbone.Many, key: tickets, relatedModel: 'App.Models.Ticket' } ],

[...]});

Page 33: Datagrids with Symfony 2, Backbone and Backgrid

retrieve data - collectionApp.Collections.TicketGroups = Backbone.Collection.extend({ model: App.Models.TicketGroup, parse: function(tickets, options) {

[...]

return ticketGroups; },});

Page 34: Datagrids with Symfony 2, Backbone and Backgrid

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);

Page 35: Datagrids with Symfony 2, Backbone and Backgrid

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

Page 36: Datagrids with Symfony 2, Backbone and Backgrid

thanks

@giorrrgiogiorgiocefaro.com

@euxpomnerd2business.net