Last active
November 19, 2015 15:39
-
-
Save mRoca/c23bd7e65b73fbe4ae4d to your computer and use it in GitHub Desktop.
NgAdmin & Hydra
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"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" | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*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); | |
}]); | |
}()); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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 | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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