SDK... Ambitious...

By Emmanuelle Delescolle

What about a RAD tool for the web?

Who am I?

  • An average developper
  • Who loves working with Ember
  • A woman who codes
  • Someone prone to burnouts
  • Works at LevIT

Who am I?

Disclaimer

So I wondered...

What would it take to write such a tool with an Ember Frontend?

  • defining your data structure in the DB and/or ORM
  • creating a REST API on the backend
  • replicating the backend API structure as Ember models
  • creating forms corresponding to those models with custom widgets
  • setting up the layout for your form

Replicating the backend API

<?php
/** @Entity */
class Product {

  /** @Column(type="integer") */
  private $id;
  /** @Column(length=255) */
  private $name;
  /** @Column(length=8) */
  private $sale_type
  /** @Column(type="decimal") */
  private $price;
  /** @Column(type="text") */
  private $description;
  /** @ManyToOne(targetEntity="Category", inversedBy="products")
   *  @JoinColumn(name="category_id", referencedColumnName="id)
   */
  private $category;

  public function setSaleType($sale_type) {
    if (!in_array($sale_type, array('product', 'service'))) {
      throw new \InvalidArgumentException('Invalid sale type');
    }
    $this->sale_type = $sale_type;
  }
}

Do we really have to?

import Model from 'ember-data/model';
import attr from 'ember-data/attr';
import { belongsTo } from 'ember-data/relationships';

export default Model.extend({
  name: attr('string'),
  sale_type: attr('string'),
  price: attr('number'),
  description: attr('string'),
  category: belongsTo('category', {
    async: true,
    inverse: 'products'
  })
});

Replicating the backend API

Do we really have to?

define('djember-sample/models/product',
  ['exports', 'ember-data/model', 'ember-data/attr', 'ember-data/relationships'],
  function (exports, _emberDataModel, _emberDataAttr, _emberDataRelationships) {

    exports['default'] = _emberDataModel['default'].extend({
    
      name: (0, _emberDataAttr['default'])('string'),
      sale_type: (0, _emberDataAttr['default'])('string'),
      price: (0, _emberDataAttr['default'])('number'),
      description: (0, _emberDataAttr['default'])('string'),
      category: (0, _emberDataRelationships['belongsTo'])('category', {
        async: true, inverse: 'products'
      }),

    });
});
Ember.$.getScript(`/models/${model_name}.js`).done(() => {
  const model_module = window.require(`${modulePrefix}/models/${model_name}`)['default'];
  App.register(`model:${model_name}`, model_module, { singleton: false });
});

Replicating the backend API

We are still loosing information...

class Product(models.Model):

    name = models.CharField(max_length=255)
    category = models.ForeignKey(Category, related_name='products')
    sale_type = models.CharField(max_length=1, choices=(
        ('p', 'Product'),
        ('s', 'Service'),
    ))
    price = models.DecimalField(max_digits=5, decimal_places=2)
    picture = models.ImageField(null=True)
    description = models.TextField(blank=True, null=True)

    def __str__(self):
        return '{} ({})'.format(self.name, self.price)
@register
class ProductEndpoint(Endpoint):
    model = Product

There's an HTTP verb for that:

OPTIONS

{
  "fields": [
    { "label": "Id", "readonly": true, "widget": "number", "required": false, "name": "id" },
    { "label": "Name", "readonly": false, "widget": "text", "required": true, "name": "name" },
    { "label": "Category", "readonly": false, "widget": "foreignkey", "required": true, "extra": {
        "related_model": "products/category"
      }, "name": "category" },
    { "label": "Sale Type", "readonly": false, "widget": "select", "required": true, "extra": {
        "choices": [{"label": "Product", "value": "p" }, { "label": "Service", "value": "s" }]
      }, "name": "sale_type" },
    { "label": "Price", "readonly": false, "widget": "number", "required": true, "name": "price" },
    { "label": "Picture", "readonly": false, "widget": "image", "required": false, "name": "picture" },
    { "label": "Description", "readonly": false, "widget": "textarea", "required": false, "name": "description" },
    { "label": "Product", "readonly": true, "widget": "text", "required": false, "name": "__str__" },
  ],
  "needs": [
    { "singular": "category", "plural": "categories", "app": "products" }
  ],
}

And there is a library for that too:

import Ember from 'ember';

export default Ember.Route.extend({
  modelLoader: Ember.inject.service(),

  model(params /* { app_name, model_name, id }*/) {
    const modelLoader = this.get('modelLoader');
    return new Ember.RSVP.Promise((resolve, reject) => {
      modelLoader.ensure_model(params.app_name, params.model_name).then((app_info) => {
        this.get('store').findRecord(`${params.app_name}/${params.model_name}`, params.id).then((record) => {
          resolve({
            model: record,
            meta: app_info.meta
          });
        }, reject);
      }).catch(reject);
    });
  },
});

Demo

Other backends?

Probably not

Likely

Already started

Ok, but you said like...

10's even 100's of models! That was 7!

Other demo

Image source: BuzzFeed

I regret nothing!

Questions

Links

Made with Slides.com