<!DOCTYPE html>
<html ng-app="app">
<head>
<script data-require="angular.js@1.4.0-rc.1" data-semver="1.4.0-rc.1" src="https://code.angularjs.org/1.4.0-rc.1/angular.js"></script>
<script data-require="angular-route@1.4.0-rc.1" data-semver="1.4.0-rc.1" src="https://code.angularjs.org/1.4.0-rc.1/angular-route.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body>
<ng-include src="'nav.html'"></ng-include>
<div ng-view></div>
</body>
</html>
/*
* Define app and configure routes
*/
angular.module('app', ['ngRoute']).config(routeConfig);
function routeConfig($routeProvider){
$routeProvider.
when('/articles/new', {
templateUrl: 'edit-article.html',
controller: 'EditArticleCtrl',
controllerAs: 'editor'
}).
when('/articles', {
templateUrl: 'articles-list.html',
controller: 'ArticlesListCtrl',
controllerAs: 'list'
}).
when('/articles/:articleId', {
templateUrl: 'edit-article.html',
controller: 'EditArticleCtrl',
controllerAs: 'editor'
}).
otherwise({
redirectTo: '/articles'
});
}
/*
* Articles list controller
*/
angular.module('app').controller('ArticlesListCtrl', ArticlesListCtrl);
ArticlesListCtrl.$inject = ['$scope', 'articlesApiSvc'];
function ArticlesListCtrl($scope, articlesApiSvc) {
var list = this;
list.articles = articlesApiSvc.articles;
// list.articles is now bound to articlesApiSvc.articles array
// when the api methods modify that variable, list.articles will be updated
articlesApiSvc.getArticles();
list.removeArticle = articlesApiSvc.deleteArticle;
list.saveArticle = articlesApiSvc.saveArticle;
}
/*
* Edit article controller
*/
angular.module('app').controller('EditArticleCtrl', EditArticleCtrl);
EditArticleCtrl.$inject = ['$scope', '$routeParams', '$location', 'articlesApiSvc'];
function EditArticleCtrl($scope, $routeParams, $location, articlesApiSvc) {
var editor = this;
var id = $routeParams.articleId;
if (id) {
articlesApiSvc.selectArticle(id);
} else {
articlesApiSvc.newArticle();
}
editor.article = articlesApiSvc.selected;
editor.save = save;
function save() {
editor.article.lastSaved = new Date();
articlesApiSvc.saveArticle();
}
}
/*
* Articles API service
*/
angular.module('app').factory('articlesApiSvc', articlesApiSvc);
articlesApiSvc.$inject = ['$http'];
function articlesApiSvc($http) {
var articles = [];
var selected = {};
return {
articles: articles,
selected: selected,
getArticles: getArticles,
selectArticle: selectArticle,
saveArticle: saveArticle,
newArticle: newArticle,
deleteArticle: deleteArticle
};
function getArticles() {
if(articles.length) { return true; } // don't fetch them twice (for demo purposes)
return $http.get('articles.json').then(function(response){
// angular.copy is used so as not to break the binding
// i.e. articles is mutated not pointed to a new array
angular.copy(response.data, articles);
//articles = response.data; // <-- won't work
});
}
function selectArticle(id){
angular.copy(articles[id], selected);
}
function saveArticle(){
// an actual http POST request would be made first
angular.copy(selected, articles[selected.id]);
}
function newArticle() {
var newId = articles.length;
articles.push({id: newId, title: 'New Article', body: 'Text', image: ''});
selectArticle(newId);
}
function deleteArticle(id) {
articles[id].status = 'deleted';
}
}
/* Styles go here */
html, body {
font-family: Arial;
padding: 0;
margin: 0;
}
div {
box-sizing: border-box;
}
navigation ul {
background: black;
margin: 0;
padding: 1em 0;
}
navigation li {
list-style-type: none;
display: inline;
padding: 0 1em;
color: white;
}
navigation a {
color: white;
text-decoration: none;
}
navigation a:hover {
text-decoration: underline;
}
header h1 {
color: white;
background: red;
margin: 0;
padding: 14px;
font-style: italic;
font-size: 48px;
}
.list-of-articles {
-webkit-columns: auto 2;
-moz-columns: auto 2;
columns: auto 2;
padding: 1em;
background: #eee;
margin: 0;
}
.articles-filter {
padding: 0.5em;
background: silver;
border-bottom: solid 1px gray;
font-size: 12px;
}
.article--in-list {
margin: 0;
margin-bottom: 1em;
list-style: none;
-webkit-column-break-inside: avoid;
page-break-inside: avoid;
break-inside: avoid;
}
.list-tile, .article-preview {
border: solid 1px gray;
padding: 1em;
overflow: auto;
background: white;
}
.list-tile img, .article-preview img {
float: left;
width: 33%;
}
.list-tile h2, .article-preview h2 {
margin: 0;
padding: 0;
}
.list-tile__text, .article-preview__text {
float: left;
overflow: auto;
max-width: 66%;
padding-left: 1em;
}
.delete-link, .delete-link:visited, .delete-link:active {
color: red;
}
.article-preview-wrapper {
width: 66%;
float: left;
margin-top: 1em;
}
.editor-form {
float: left;
max-width: 33%;
padding: 1em;
margin-top: 1em;
}
p:focus, h2:focus {
background: yellow;
outline: solid 0px transparent;
}
[
{
"id": 0,
"title": "Protection Jacket",
"body": "It's a jungle out there - but you can survive if you're dressed not to be killed.",
"image": "http://unsplash.it/200/200?image=99",
"status": "published"
},
{
"id": 1,
"title": "Plot to kill corrupt CID",
"body": "Four allegedly corrupt CID officers were the target of a rocket attack plot by two best friends, the Digger can reveal.",
"image": "http://unsplash.it/200/200?image=100",
"status": "published"
},
{
"id": 2,
"title": "Police state tactics exposed",
"body": "Sean Clerkin says his anti-poverty group were harassed and intimidated by cops.",
"image": "http://unsplash.it/200/200?image=101",
"status": "published"
},
{
"id": 3,
"title": "Smurphy knew too much",
"body": "Pals claim he was on the run from the cops but close friends and family say he was bundled into a van and dumped in the canal.",
"image": "http://unsplash.it/200/200?image=102",
"status": "published"
},
{
"id" : 4,
"title" : "The Proof",
"body" : "The Digger exposes the lie police peddled, claiming they busted dealers in the Fox's house in the East end.",
"image" : "http://unsplash.it/200/200?image=103",
"status" : "published"
},
{
"id" : 5,
"title" : "Violent Attacks Linked To Suspect Killer",
"body" : "Two assaults have been linked to cops probing a man being questioned about a murder.",
"image" : "http://unsplash.it/200/200?image=104",
"status" : "published"
}
]
<div class="articles-filter">
<label for="name">Title: </label><input name="search" type="text" ng-model="list.search.title">
<label for="name">Body: </label><input name="search" type="text" ng-model="list.search.body">
<select ng-init="list.search.status = 'published'" id="status" name="status" ng-model="list.search.status">
<option value="published" selected>Published</option>
<option value="deleted">Deleted</option>
<option value="draft">Draft</option>
</select>
</div>
<ul class="list-of-articles">
<li class="article--in-list" ng-repeat="article in list.articles | filter:list.search track by article.id">
<div class="list-tile">
<img ng-src="{{article.image}}" />
<div class="list-tile__text">
<h2 ng-model="article.title" contenteditable>{{article.title}}</h2>
<p ng-model="article.body" contenteditable>{{article.body}}</p>
<p>
<a href="" ng-click="list.saveArticle(article.id)" >Save</a>
<a href="#/articles/{{article.id}}" >Edit</a>
<a class="delete-link" href="" ng-click="list.removeArticle(article.id)">Delete</a>
</p>
</div>
</div>
</li>
</ul>
<div class="article-preview-wrapper">
<div class="article-preview">
<img ng-src="{{editor.article.image}}" />
<div class="article-preview__text">
<h2>{{editor.article.title}}</h2>
<p>{{editor.article.body}}</p>
</div>
</div>
</div>
<div class="editor-form">
<form ng-submit="editor.save()">
<table>
<tbody>
<tr><td><label for="title">Title</label></td>
<td><input name="title" type="text" ng-model="editor.article.title"></td></tr>
<tr><td><label for="body">Body</label></td>
<td><textarea name="body" ng-model="editor.article.body"></textarea></td></tr>
<tr><td><label for="image">Image</label></td>
<td><input name="image" type="text" ng-model="editor.article.image" ng-model-options="{updateOn: 'blur'}"></td></tr>
<tr>
<td>Status: </td>
<td>
<select ng-init="editor.article.status = 'published'" ng-model="editor.article.status">
<option value="published">Published</option>
<option value="draft">Draft</option>
<option value="deleted">Deleted</option>
</select>
</td>
</tr>
</tbody>
</table>
<button action="submit">Save</button>
<h6>Last saved: {{editor.article.lastSaved | date:'yyyy-MM-dd HH:mm:ss'}}</h6>
<p><a href="#/articles">Back to list</a></p>
</form>
</div>
<header>
<h1>The Digger CMS!</h1>
</header>
<navigation>
<ul>
<li><a href="#/articles/new">New article</a></li>
<li><a href="#/articles">Articles list</a></li>
</ul>
</navigation>