Skip to content

Instantly share code, notes, and snippets.

@mRoca
Last active November 19, 2015 15:39
Show Gist options
  • Save mRoca/c23bd7e65b73fbe4ae4d to your computer and use it in GitHub Desktop.
Save mRoca/c23bd7e65b73fbe4ae4d to your computer and use it in GitHub Desktop.
NgAdmin & Hydra
{
"name": "rest-test",
"version": "0.0.1",
"license": "MIT",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
],
"dependencies": {
"ng-admin": "~0.7"
},
"resolutions": {
"angular": "1.3.16"
}
}
/*global angular*/
(function () {
"use strict";
var baseUrl = 'http://localhost:12080/api/';
var imageUploadInformations = {'url': baseUrl + 'cdn_resources', 'apifilename': 'url'};
var hackIdRewriteRules = {};
var app = angular.module('myApp', ['ng-admin']);
app.config(['RestangularProvider', function (RestangularProvider) {
// The URL of the API endpoint
RestangularProvider.setBaseUrl(baseUrl);
// JSON-LD @id support
RestangularProvider.setRestangularFields({id: '@id'});
RestangularProvider.setUseCannonicalId(true);
RestangularProvider.setSelfLinkAbsoluteUrl(false);
RestangularProvider.addFullRequestInterceptor(function (element, operation, what, url, headers, params, httpConfig) {
if (operation == 'put' || operation == 'post') {
// TODO clean that
// Vilain hack to use a true hydra id in many to one/many relations
if (typeof hackIdRewriteRules[what] !== 'undefined') {
angular.forEach(hackIdRewriteRules[what], function (column) {
if (typeof element[column] === 'undefined') {
return;
}
angular.forEach(element[column], function (value, key) {
element[column][key] = '/api/' + column + '/' + value;
});
});
}
}
if (operation == 'getList') {
if (params._page > 1) {
params.page = params._page;
params.itemsPerPage = params._perPage;
}
params.order = params._sortDir;
delete params._sortDir;
delete params._sortField; // Not implemented
delete params._page;
delete params._perPage;
}
return {params: params};
});
// Hydra collections support
RestangularProvider.addResponseInterceptor(function (data, operation) {
// Remove trailing slash to make Restangular working
function populateHref(data) {
if (data['@id']) {
data.href = data['@id'].substring(1);
// Set an angular compatible id
data.id = data['@id'].split('/').pop();
}
}
// Populate href property for the collection
populateHref(data);
if ('getList' === operation) {
var collectionResponse = data['hydra:member'];
collectionResponse.metadata = {};
// Put metadata in a property of the collection
angular.forEach(data, function (value, key) {
if ('hydra:member' !== key) {
collectionResponse.metadata[key] = value;
}
});
// Populate href property for all elements of the collection
angular.forEach(collectionResponse, function (value) {
populateHref(value);
});
return collectionResponse;
} else if ('get' === operation) {
// TODO clean that
var what = data['@type'].toLowerCase() + 's';
// Vilain hack to use a true hydra id in many to one/many relations
if (typeof hackIdRewriteRules[what] !== 'undefined') {
angular.forEach(hackIdRewriteRules[what], function (column) {
if (typeof data[column] === 'undefined') {
return;
}
angular.forEach(data[column], function (value, key) {
data[column][key] = value.split('/').pop();
});
});
}
}
return data;
});
}]);
app.config(['NgAdminConfigurationProvider', 'RestangularProvider', function (NgAdminConfigurationProvider, RestangularProvider) {
var nga = NgAdminConfigurationProvider;
function truncate(value) {
if (!value) {
return '';
}
return value.length > 50 ? value.substr(0, 50) + '...' : value;
}
function addHackIdRewriteRules(resource, column) {
if (typeof hackIdRewriteRules[resource] === 'undefined') {
hackIdRewriteRules[resource] = [];
}
hackIdRewriteRules[resource].push(column);
}
var admin = nga.application('ng-admin backend demo'); // main API endpoint
var article = nga.entity('articles');
var category = nga.entity('categories');
// set the application entities
admin
.addEntity(article)
.addEntity(category)
;
// customize entities and views
category.dashboardView() // customize the dashboard panel for this entity
.name('categories')
.title('Recent categories')
.order(2) // display the post panel first in the dashboard
.perPage(5) // limit the panel to the 5 latest posts
.fields([nga.field('name').isDetailLink(true).map(truncate)]); // fields() called with arguments add fields to the view
category.listView()
.title('All categories') // default title is "[Entity_name] list"
.description('List of posts with infinite pagination') // description appears under the title
.infinitePagination(true) // load pages as the user scrolls
.fields([
nga.field('@id').label('id'), // The default displayed name is the camelCase field name. label() overrides id
nga.field('name') // the default list field type is "string", and displays as a string
])
.listActions(['show', 'edit', 'delete']);
category.creationView()
.fields([
nga.field('name') // the default edit field type is "string", and displays as a text input
.attributes({placeholder: 'the category name'}) // you can add custom attributes, too
.validation({required: true, minlength: 3, maxlength: 100}), // add validation rules for fields
nga.field('slug') // the default edit field type is "string", and displays as a text input
.attributes({placeholder: 'the category slug'}) // you can add custom attributes, too
.validation({required: true, minlength: 3, maxlength: 255}) // add validation rules for fields
]);
category.editionView()
.title('Edit post "{{ entry.values.name }}"') // title() accepts a template string, which has access to the entry
.actions(['list', 'show', 'delete']) // choose which buttons appear in the top action bar. Show is disabled by default
.fields([
category.creationView().fields() // fields() without arguments returns the list of fields. That way you can reuse fields from another view to avoid repetition
]);
category.showView() // a showView displays one entry in full page - allows to display more data than in a a list
.fields([
nga.field('id'),
category.editionView().fields() // reuse fields from another view in another order
]);
article.dashboardView() // customize the dashboard panel for this entity
.name('articles')
.title('Recent articles')
.order(1) // display the post panel first in the dashboard
.perPage(5) // limit the panel to the 5 latest posts
.fields([nga.field('name').isDetailLink(true).map(truncate)]); // fields() called with arguments add fields to the view
article.listView()
.title('All posts') // default title is "[Entity_name] list"
.description('List of posts with infinite pagination') // description appears under the title
.infinitePagination(true) // load pages as the user scrolls
.fields([
nga.field('@id').label('id'), // The default displayed name is the camelCase field name. label() overrides id
nga.field('name'), // the default list field type is "string", and displays as a string
nga.field('datePublished', 'date')
])
.listActions(['show', 'edit', 'delete']);
article.creationView()
.fields([
nga.field('name') // the default edit field type is "string", and displays as a text input
.attributes({placeholder: 'the article name'}) // you can add custom attributes, too
.validation({required: true, minlength: 3, maxlength: 100}), // add validation rules for fields
nga.field('slug') // the default edit field type is "string", and displays as a text input
.attributes({placeholder: 'the article slug'}) // you can add custom attributes, too
.validation({required: true, minlength: 3, maxlength: 255}), // add validation rules for fields
nga.field('description', 'text'), // text field type translates to a textarea
nga.field('body', 'wysiwyg'), // overriding the type allows rich text editing for the body
nga.field('datePublished', 'date'), // Date field type translates to a datepicker
nga.field('image', 'file').uploadInformation(imageUploadInformations),
nga.field('categories', 'reference_many')
.targetEntity(nga.entity('categories'))
.targetField(nga.field('name'))
]);
addHackIdRewriteRules('articles', 'categories');
article.editionView()
.title('Edit post "{{ entry.values.name }}"') // title() accepts a template string, which has access to the entry
.actions(['list', 'show', 'delete']) // choose which buttons appear in the top action bar. Show is disabled by default
.fields([
article.creationView().fields() // fields() without arguments returns the list of fields. That way you can reuse fields from another view to avoid repetition
]);
article.showView() // a showView displays one entry in full page - allows to display more data than in a a list
.fields([
nga.field('id'),
article.editionView().fields() // reuse fields from another view in another order
]);
// customize header
var customHeaderTemplate =
'<div class="navbar-header">' +
'<a class="navbar-brand" href="#" ng-click="appController.displayHome()">ng-admin backend demo</a>' +
'</div>';
admin.header(customHeaderTemplate);
// customize menu
admin.menu(nga.menu()
.addChild(nga.menu(article).icon('<span class="glyphicon glyphicon-file"></span>')) // customize the entity menu icon
.addChild(nga.menu(category).icon('<span class="glyphicon glyphicon-file"></span>')) // customize the entity menu icon
.addChild(nga.menu().title('Other')
.addChild(nga.menu().title('Stats').icon('').link('/stats'))
)
);
nga.configure(admin);
}]);
}());
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Angular admin</title>
<meta name="viewport" content="width=device-width">
<link rel="stylesheet" href="bower_components/ng-admin/build/ng-admin.min.css">
</head>
<body ng-app="myApp">
<div ui-view></div>
<script src="bower_components/angular/angular.min.js"></script>
<script src="bower_components/ng-admin/build/ng-admin.min.js" type="text/javascript"></script>
<script src="config.js" type="text/javascript"></script>
</body>
</html>
<?php
namespace AppBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Gedmo\Mapping\Annotation as Gedmo;
use Symfony\Component\Serializer\Annotation\Groups;
/**
* An article, such as a news article or piece of investigative report.
*
* @ORM\Entity
* @ORM\Table(name="article")
*/
class Article
{
/**
* @var int
*
* @ORM\Column(type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
* @Groups({"article_to"})
*/
private $id;
/**
* @var string The name of the item.
*
* @ORM\Column(nullable=true)
* @Assert\Type(type="string")
* @Assert\NotBlank()
* @Groups({"article_to", "article_from"})
*/
private $name;
/**
* @var string The slug of the item.
*
* @Gedmo\Slug(fields={"name"}, updatable=true, separator="-")
* @ORM\Column(nullable=true, length=255, unique=true)
* @Assert\Type(type="string")
*/
private $slug;
/**
* @var boolean The status of the item
*
* @ORM\Column(type="boolean")
* @Assert\Type(type="boolean")
* @Groups({"article_to", "article_from"})
*/
protected $published;
/**
* @var string A short description of the item.
*
* @ORM\Column(type="text", nullable=true)
* @Assert\Type(type="string")
* @Assert\NotBlank()
* @Groups({"article_to", "article_from"})
*/
private $description;
/**
* @var Category[]|ArrayCollection
* @ORM\ManyToMany(targetEntity="Category", inversedBy="articles")
* @Groups({"article_to", "article_from"})
*/
private $categories;
/**
* @var string The actual body of the article.
*
* @ORM\Column(nullable=true)
* @Assert\Type(type="string")
* @Assert\NotBlank()
* @Groups({"article_to", "article_from"})
*/
private $body;
/**
* @var string Articles may belong to one or more 'sections' in a magazine or newspaper, such as Sports, Lifestyle, etc.
*
* @ORM\Column(nullable=true)
* @Assert\Type(type="string")
* @Groups({"article_to", "article_from"})
*/
private $section;
/**
* @var string The author name of this content.
*
* @ORM\Column(name="author_name", nullable=true)
* @Groups({"article_to", "article_from"})
*/
private $authorName;
/**
* @var \DateTime The date on which the item was created.
*
* @ORM\Column(name="date_created", type="datetime", nullable=true)
* @Assert\DateTime
*/
private $dateCreated;
/**
* @var \DateTime The date on which the item was most recently modified.
*
* @ORM\Column(name="date_modified", type="datetime", nullable=true)
* @Assert\DateTime
*/
private $dateModified;
/**
* @var \DateTime Date of first broadcast/publication.
*
* @ORM\Column(name="date_published", type="datetime", nullable=true)
* @Assert\DateTime
*/
private $datePublished;
/**
* @var string A resource that was used in the creation of this resource.
*
* @ORM\Column(name="original_url", nullable=true)
* @Assert\Url
* @Groups({"article_to", "article_from"})
*/
private $originalUrl;
/**
* @var string The main image URL of the item
*
* @ORM\Column(name="image", nullable=true)
* @Assert\Url
* @Groups({"article_to", "article_from"})
*/
private $image;
public function __construct()
{
$this->categories = new ArrayCollection();
$this->published = false;
}
// Here getters & setters
}
<?php
namespace AppBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Gedmo\Mapping\Annotation as Gedmo;
use Symfony\Component\Serializer\Annotation\Groups;
/**
* @ORM\Entity
* @ORM\Table(name="category")
*/
class Category
{
/**
* @ORM\Column(type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
* @Groups({"category_to"})
*/
private $id;
/**
* @ORM\Column
* @Assert\NotBlank
* @Groups({"category_to", "category_from"})
*/
private $name;
/**
* @var string The slug of the item.
*
* @Gedmo\Slug(fields={"name"}, updatable=true, separator="-")
* @ORM\Column(nullable=true, length=255, unique=true)
* @Assert\Type(type="string")
*/
private $slug;
/**
* @ORM\Column(name="import_url", nullable=true)
* @Assert\Url
* @Groups({"category_to", "category_from"})
*/
private $importUrl;
/**
* @var Article[]|ArrayCollection
* @ORM\ManyToMany(targetEntity="Article", mappedBy="categories")
* @Groups({"category_to", "category_from"})
*/
private $articles;
public function __construct()
{
$this->articles = new ArrayCollection();
}
// Here getters & setters
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment