Skip to content

Commit

Permalink
Ember Data with Posts
Browse files Browse the repository at this point in the history
Ref #2699

- Introduce ember data dependency
- Add loadInitializers and refactor most initializers into one combined
- Add Post ember data model
- Refactor generateSlug to use title of post and ghostPaths
- Refactor post controller to not reference model.property everywhere
- Use RESTAdapter for posts, users and tags
- Setup author and tag relations in Post model
- Fix broken API calls by adding CSRF header
- Add initiaizer for csrf value
- Use actual User model for current user initializer
- Add action for setting featured post, test with actual api call
- Fix the sending of UUID's up to the server
- Refactor current-user to use ember-data store
- If a user is preloaded in the application, use pushPayload to put it
in the store
- Do a lookup on the store to get an actual User model for injection
- Fix posts/post controllerName in route/new.js
- Alter signup process to push user into ember data store
  • Loading branch information
jgable committed May 29, 2014
1 parent 97011e5 commit 5abeadf
Show file tree
Hide file tree
Showing 25 changed files with 258 additions and 207 deletions.
2 changes: 2 additions & 0 deletions Gruntfile.js
Expand Up @@ -548,8 +548,10 @@ var path = require('path'),
'bower_components/jquery/dist/jquery.js',
'bower_components/handlebars/handlebars.js',
'bower_components/ember/ember.js',
'bower_components/ember-data/ember-data.js',
'bower_components/ember-resolver/dist/ember-resolver.js',
'bower_components/ic-ajax/dist/globals/main.js',
'bower_components/ember-load-initializers/ember-load-initializers.js',
'bower_components/validator-js/validator.js',
'bower_components/codemirror/lib/codemirror.js',
'bower_components/codemirror/addon/mode/overlay.js',
Expand Down
2 changes: 2 additions & 0 deletions bower.json
Expand Up @@ -5,6 +5,8 @@
"codemirror": "4.0.1",
"Countable": "2.0.2",
"ember": "1.5.0",
"ember-data": "~1.0.0-beta.7",
"ember-load-initializers": "git://github.com/stefanpenner/ember-load-initializers.git#0.0.1",
"ember-resolver": "git://github.com/stefanpenner/ember-jj-abrams-resolver.git#181251821cf513bb58d3e192faa13245a816f75e",
"fastclick": "1.0.0",
"ghost-ui": "0.1.3",
Expand Down
22 changes: 22 additions & 0 deletions core/client/adapters/application.js
@@ -0,0 +1,22 @@
import ghostPaths from 'ghost/utils/ghost-paths';

// export default DS.FixtureAdapter.extend({});

export default DS.RESTAdapter.extend({
host: window.location.origin,
namespace: ghostPaths().apiRoot.slice(1),
headers: {
'X-CSRF-Token': $('meta[name="csrf-param"]').attr('content')
},

buildURL: function (type, id) {
// Ensure trailing slashes
var url = this._super(type, id);

if (url.slice(-1) !== '/') {
url += '/';
}

return url;
}
});
15 changes: 4 additions & 11 deletions core/client/app.js
@@ -1,13 +1,11 @@
import Resolver from 'ember/resolver';
import initFixtures from 'ghost/fixtures/init';
import injectCurrentUser from 'ghost/initializers/current-user';
import injectCsrf from 'ghost/initializers/csrf';
import {registerNotifications, injectNotifications} from 'ghost/initializers/notifications';
import registerTrailingLocationHistory from 'ghost/initializers/trailing-history';
import injectGhostPaths from 'ghost/initializers/ghost-paths';
import loadInitializers from 'ember/load-initializers';
import 'ghost/utils/link-view';
import 'ghost/utils/text-field';

Ember.MODEL_FACTORY_INJECTIONS = true;

var App = Ember.Application.extend({
/**
* These are debugging flags, they are useful during development
Expand All @@ -23,11 +21,6 @@ var App = Ember.Application.extend({

initFixtures();

App.initializer(injectCurrentUser);
App.initializer(injectCsrf);
App.initializer(injectGhostPaths);
App.initializer(registerNotifications);
App.initializer(injectNotifications);
App.initializer(registerTrailingLocationHistory);
loadInitializers(App, 'ghost');

export default App;
81 changes: 32 additions & 49 deletions core/client/controllers/posts/post.js
Expand Up @@ -13,49 +13,26 @@ var PostController = Ember.ObjectController.extend({
isDraft: equal('status', 'draft'),
willPublish: Ember.computed.oneWay('isPublished'),
isStaticPage: function (key, val) {
var self = this;

if (arguments.length > 1) {
this.set('model.page', val ? 1 : 0);
this.get('model').save('page').then(function () {
this.notifications.showSuccess('Succesfully converted ' + (val ? 'to static page' : 'to post'));
this.set('page', val ? 1 : 0);

return this.get('model').save().then(function () {
self.notifications.showSuccess('Succesfully converted to ' + (val ? 'static page' : 'post'));

return !!self.get('page');
}, this.notifications.showErrors);
}
return !!this.get('model.page');
}.property('model.page'),

isOnServer: function () {
return this.get('model.id') !== undefined;
}.property('model.id'),

newSlugBinding: Ember.Binding.oneWay('model.slug'),
slugPlaceholder: null,
// Requests a new slug when the title was changed
updateSlugPlaceholder: function () {
var model,
self = this,
title = this.get('title');

// If there's a title present we want to
// validate it against existing slugs in the db
// and then update the placeholder value.
if (title) {
model = self.get('model');
model.generateSlug().then(function (slug) {
self.set('slugPlaceholder', slug);
}, function () {
self.notifications.showWarn('Unable to generate a slug for "' + title + '"');
});
} else {
// If there's no title set placeholder to blank
// and don't make an ajax request to server
// for a proper slug (as there won't be any).
self.set('slugPlaceholder', '');
}
}.observes('model.title'),

publishedAt: null,
publishedAtChanged: function () {
this.set('publishedAt', formatDate(this.get('model.published_at')));
}.observes('model.published_at'),
return !!this.get('page');
}.property('page'),

newSlugBinding: Ember.computed.oneWay('slug'),

slugPlaceholder: function () {
return this.get('model').generateSlug();
}.property('title'),

actions: {
save: function () {
Expand All @@ -78,6 +55,11 @@ var PostController = Ember.ObjectController.extend({
console.warn('Received invalid save type; ignoring.');
}
},
toggleFeatured: function () {
this.set('featured', !this.get('featured'));

this.get('model').save();
},
editSettings: function () {
var isEditing = this.toggleProperty('isEditingSettings');
if (isEditing) {
Expand All @@ -91,9 +73,10 @@ var PostController = Ember.ObjectController.extend({
});
}
},

updateSlug: function () {
var newSlug = this.get('newSlug'),
slug = this.get('model.slug'),
slug = this.get('slug'),
placeholder = this.get('slugPlaceholder'),
self = this;

Expand All @@ -110,17 +93,17 @@ var PostController = Ember.ObjectController.extend({
}

//Validation complete
this.set('model.slug', newSlug);
this.set('slug', newSlug);

// If the model doesn't currently
// exist on the server
// then just update the model's value
if (!this.get('isOnServer')) {
if (!this.get('isNew')) {
return;
}

this.get('model').save('slug').then(function () {
self.notifications.showSuccess('Permalink successfully changed to <strong>' + this.get('model.slug') + '</strong>.');
this.get('model').save().then(function () {
self.notifications.showSuccess('Permalink successfully changed to <strong>' + this.get('slug') + '</strong>.');
}, this.notifications.showErrors);
},

Expand Down Expand Up @@ -182,20 +165,20 @@ var PostController = Ember.ObjectController.extend({
}

//Validation complete
this.set('model.published_at', newPubDateMoment.toDate());
this.set('published_at', newPubDateMoment.toDate());

// If the model doesn't currently
// exist on the server
// then just update the model's value
if (!this.get('isOnServer')) {
if (!this.get('isNew')) {
return;
}

this.get('model').save('published_at').then(function () {
this.get('model').save().then(function () {
this.notifications.showSuccess('Publish date successfully changed to <strong>' + this.get('publishedAt') + '</strong>.');
}, this.notifications.showErrors);
}
}
});

export default PostController;
export default PostController;
11 changes: 11 additions & 0 deletions core/client/initializers/csrf-token.js
@@ -0,0 +1,11 @@
export default {
name: 'csrf-token',

initialize: function (container) {
container.register('csrf:token', $('meta[name="csrf-param"]').attr('content'), { instantiate: false });

container.injection('route', 'csrf', 'csrf:token');
container.injection('model', 'csrf', 'csrf:token');
container.injection('controller', 'csrf', 'csrf:token');
}
};
32 changes: 26 additions & 6 deletions core/client/initializers/current-user.js
@@ -1,14 +1,34 @@
import User from 'ghost/models/user';

export default {
name: 'currentUser',
after: 'store',

initialize: function (container, application) {
var user = User.create(application.get('user') || {});
var store = container.lookup('store:main'),
preloadedUser = application.get('user');

// If we don't have a user, don't do the injection
if (!preloadedUser) {
return;
}

// Push the preloaded user into the data store
store.pushPayload({
users: [preloadedUser]
});

// Signal to wait until the user is loaded before continuing.
application.deferReadiness();

// Find the user (which should be fast since we just preloaded it in the store)
store.find('user', preloadedUser.id).then(function (user) {
// Register the value for injection
container.register('user:current', user, { instantiate: false });

container.register('user:current', user, { instantiate: false });
// Inject into the routes and controllers as the user property.
container.injection('route', 'user', 'user:current');
container.injection('controller', 'user', 'user:current');

container.injection('route', 'user', 'user:current');
container.injection('controller', 'user', 'user:current');
application.advanceReadiness();
});
}
};
1 change: 1 addition & 0 deletions core/client/initializers/ghost-paths.js
Expand Up @@ -2,6 +2,7 @@ import ghostPaths from 'ghost/utils/ghost-paths';

export default {
name: 'ghost-paths',
after: 'store',

initialize: function (container) {
container.register('ghost:paths', ghostPaths(), {instantiate: false});
Expand Down
14 changes: 3 additions & 11 deletions core/client/initializers/notifications.js
@@ -1,21 +1,13 @@
import Notifications from 'ghost/utils/notifications';

var registerNotifications = {
name: 'registerNotifications',
export default {
name: 'injectNotifications',

initialize: function (container, application) {
application.register('notifications:main', Notifications);
}
};

var injectNotifications = {
name: 'injectNotifications',

initialize: function (container, application) {
application.inject('controller', 'notifications', 'notifications:main');
application.inject('component', 'notifications', 'notifications:main');
application.inject('route', 'notifications', 'notifications:main');
}
};

export {registerNotifications, injectNotifications};
};
71 changes: 30 additions & 41 deletions core/client/models/post.js
@@ -1,56 +1,45 @@
import BaseModel from 'ghost/models/base';

var PostModel = BaseModel.extend({
url: BaseModel.apiRoot + '/posts/',
var Post = DS.Model.extend({
uuid: DS.attr('string'),
title: DS.attr('string'),
slug: DS.attr('string'),
markdown: DS.attr('string'),
html: DS.attr('string'),
image: DS.attr('string'),
featured: DS.attr('boolean'),
page: DS.attr('boolean'),
status: DS.attr('string'),
language: DS.attr('string'),
meta_title: DS.attr('string'),
meta_description: DS.attr('string'),
author: DS.belongsTo('user', { async: true }),
created_at: DS.attr('date'),
created_by: DS.belongsTo('user', { async: true }),
updated_at: DS.attr('date'),
updated_by: DS.belongsTo('user', { async: true }),
published_at: DS.attr('date'),
published_by: DS.belongsTo('user', { async: true }),
tags: DS.hasMany('tag', { async: true }),

generateSlug: function () {
// @TODO Make this request use this.get('title') once we're an actual user
var url = this.get('url') + 'slug/' + encodeURIComponent('test title') + '/';
return ic.ajax.request(url, {
type: 'GET'
});
},

save: function (properties) {
var url = this.url,
self = this,
type,
validationErrors = this.validate();

if (validationErrors.length) {
return Ember.RSVP.Promise(function (resolve, reject) {
return reject(validationErrors);
});
}

//If specific properties are being saved,
//this is an edit. Otherwise, it's an add.
if (properties && properties.length > 0) {
type = 'PUT';
url += this.get('id');
} else {
type = 'POST';
properties = Ember.keys(this);
}
var title = this.get('title'),
url = this.get('ghostPaths').apiUrl('posts', 'slug', encodeURIComponent(title));

return ic.ajax.request(url, {
type: type,
data: this.getProperties(properties)
}).then(function (model) {
return self.setProperties(model);
});
type: 'GET'
});
},
validate: function () {

validationErrors: function () {
var validationErrors = [];

if (!(this.get('title') && this.get('title').length)) {
if (!this.get('title.length')) {
validationErrors.push({
message: "You must specify a title for the post."
});
}

return validationErrors;
}
}.property('title')
});

export default PostModel;
export default Post;

0 comments on commit 5abeadf

Please sign in to comment.