FEATURE: Custom orders for user fields

This commit is contained in:
Robin Ward 2015-07-30 14:52:53 -04:00
parent 8e603503e6
commit aa6f792ce1
16 changed files with 126 additions and 83 deletions

View file

@ -6,6 +6,9 @@ export default Ember.Component.extend(bufferedProperty('userField'), {
editing: Ember.computed.empty('userField.id'), editing: Ember.computed.empty('userField.id'),
classNameBindings: [':user-field'], classNameBindings: [':user-field'],
cantMoveUp: Discourse.computed.propertyEqual('userField', 'firstField'),
cantMoveDown: Discourse.computed.propertyEqual('userField', 'lastField'),
userFieldsDescription: function() { userFieldsDescription: function() {
return I18n.t('admin.user_fields.description'); return I18n.t('admin.user_fields.description');
}.property(), }.property(),
@ -55,13 +58,20 @@ export default Ember.Component.extend(bufferedProperty('userField'), {
'show_on_profile', 'show_on_profile',
'options'); 'options');
this.get('userField').save(attrs).then(function(res) { this.get('userField').save(attrs).then(function() {
self.set('userField.id', res.user_field.id);
self.set('editing', false); self.set('editing', false);
self.commitBuffer(); self.commitBuffer();
}).catch(popupAjaxError); }).catch(popupAjaxError);
}, },
moveUp() {
this.sendAction('moveUpAction', this.get('userField'));
},
moveDown() {
this.sendAction('moveDownAction', this.get('userField'));
},
edit() { edit() {
this.set('editing', true); this.set('editing', true);
}, },

View file

@ -1,31 +1,60 @@
import UserField from 'admin/models/user-field'; import { popupAjaxError } from 'discourse/lib/ajax-error';
export default Ember.ArrayController.extend({ const MAX_FIELDS = 20;
export default Ember.Controller.extend({
fieldTypes: null, fieldTypes: null,
createDisabled: Em.computed.gte('model.length', 20), createDisabled: Em.computed.gte('model.length', MAX_FIELDS),
_performDestroy(f, model) { arrangedContent: function() {
return f.destroy().then(function() { return Ember.ArrayProxy.extend(Ember.SortableMixin).create({
model.removeObject(f); sortProperties: ['position'],
content: this.get('model')
}); });
}, }.property('model'),
actions: { actions: {
createField() { createField() {
this.pushObject(UserField.create({ field_type: 'text' })); const f = this.store.createRecord('user-field', { field_type: 'text', position: MAX_FIELDS });
this.get('model').pushObject(f);
},
moveUp(f) {
const idx = this.get('arrangedContent').indexOf(f);
if (idx) {
const prev = this.get('arrangedContent').objectAt(idx-1);
const prevPos = prev.get('position');
prev.update({ position: f.get('position') });
f.update({ position: prevPos });
}
},
moveDown(f) {
const idx = this.get('arrangedContent').indexOf(f);
if (idx > -1) {
const next = this.get('arrangedContent').objectAt(idx+1);
const nextPos = next.get('position');
next.update({ position: f.get('position') });
f.update({ position: nextPos });
}
}, },
destroy(f) { destroy(f) {
const model = this.get('model'), const model = this.get('model');
self = this;
// Only confirm if we already been saved // Only confirm if we already been saved
if (f.get('id')) { if (f.get('id')) {
bootbox.confirm(I18n.t("admin.user_fields.delete_confirm"), function(result) { bootbox.confirm(I18n.t("admin.user_fields.delete_confirm"), function(result) {
if (result) { self._performDestroy(f, model); } if (result) {
f.destroyRecord().then(function() {
model.removeObject(f);
}).catch(popupAjaxError);
}
}); });
} else { } else {
self._performDestroy(f, model); model.removeObject(f);
} }
} }
} }

View file

@ -1,47 +1,12 @@
const UserField = Ember.Object.extend({ import RestModel from 'discourse/models/rest';
destroy() { const UserField = RestModel.extend();
const self = this;
return new Ember.RSVP.Promise(function(resolve) {
const id = self.get('id');
if (id) {
return Discourse.ajax("/admin/customize/user_fields/" + id, { type: 'DELETE' }).then(function() {
resolve();
});
}
resolve();
});
},
save(attrs) {
const id = this.get('id');
if (!id) {
return Discourse.ajax("/admin/customize/user_fields", {
type: "POST",
data: { user_field: attrs }
});
} else {
return Discourse.ajax("/admin/customize/user_fields/" + id, {
type: "PUT",
data: { user_field: attrs }
});
}
}
});
const UserFieldType = Ember.Object.extend({ const UserFieldType = Ember.Object.extend({
name: Discourse.computed.i18n('id', 'admin.user_fields.field_types.%@') name: Discourse.computed.i18n('id', 'admin.user_fields.field_types.%@')
}); });
UserField.reopenClass({ UserField.reopenClass({
findAll() {
return Discourse.ajax("/admin/customize/user_fields").then(function(result) {
return result.user_fields.map(function(uf) {
return UserField.create(uf);
});
});
},
fieldTypes() { fieldTypes() {
if (!this._fieldTypes) { if (!this._fieldTypes) {
this._fieldTypes = [ this._fieldTypes = [

View file

@ -2,13 +2,10 @@ import UserField from 'admin/models/user-field';
export default Discourse.Route.extend({ export default Discourse.Route.extend({
model: function() { model: function() {
return UserField.findAll(); return this.store.findAll('user-field');
}, },
setupController: function(controller, model) { setupController: function(controller, model) {
controller.setProperties({ controller.setProperties({ model, fieldTypes: UserField.fieldTypes() });
model: model,
fieldTypes: UserField.fieldTypes()
});
} }
}); });

View file

@ -35,13 +35,17 @@
{{/admin-form-row}} {{/admin-form-row}}
{{else}} {{else}}
<div class="row"> <div class="row">
<div class='form-display'><strong>{{userField.name}}</strong></div> <div class='form-display'>
<div class='form-display'>{{{userField.description}}}</div> <strong>{{userField.name}}</strong>
<br/>
{{{userField.description}}}
</div>
<div class='form-display'>{{fieldName}}</div> <div class='form-display'>{{fieldName}}</div>
<div class='form-display'></div>
<div class='form-element controls'> <div class='form-element controls'>
{{d-button action="edit" class="btn-default" icon="pencil" label="admin.user_fields.edit"}} {{d-button action="edit" class="btn-default" icon="pencil" label="admin.user_fields.edit"}}
{{d-button action="destroy" class="btn-danger" icon="trash-o" label="admin.user_fields.delete"}} {{d-button action="destroy" class="btn-danger" icon="trash-o" label="admin.user_fields.delete"}}
{{d-button action="moveUp" icon="arrow-up" disabled=cantMoveUp}}
{{d-button action="moveDown" icon="arrow-down" disabled=cantMoveDown}}
</div> </div>
</div> </div>
<div class="row">{{flags}}</div> <div class="row">{{flags}}</div>

View file

@ -4,8 +4,14 @@
<p class="desc">{{i18n 'admin.user_fields.help'}}</p> <p class="desc">{{i18n 'admin.user_fields.help'}}</p>
{{#if model}} {{#if model}}
{{#each model as |uf|}} {{#each arrangedContent as |uf|}}
{{admin-user-field-item userField=uf fieldTypes=fieldTypes destroyAction="destroy"}} {{admin-user-field-item userField=uf
fieldTypes=fieldTypes
firstField=arrangedContent.firstObject
lastField=arrangedContent.lastObject
destroyAction="destroy"
moveUpAction="moveUp"
moveDownAction="moveDown"}}
{{/each}} {{/each}}
{{/if}} {{/if}}

View file

@ -17,10 +17,14 @@ function rethrow(error) {
} }
export default Ember.Object.extend({ export default Ember.Object.extend({
pathFor(store, type, findArgs) {
let path = "/" + Ember.String.underscore(store.pluralize(type));
if (ADMIN_MODELS.indexOf(type) !== -1) { path = "/admin" + path; } basePath(store, type) {
if (ADMIN_MODELS.indexOf(type) !== -1) { return "/admin/"; }
return "/";
},
pathFor(store, type, findArgs) {
let path = this.basePath(store, type, findArgs) + Ember.String.underscore(store.pluralize(type));
if (findArgs) { if (findArgs) {
if (typeof findArgs === "object") { if (typeof findArgs === "object") {
@ -51,9 +55,10 @@ export default Ember.Object.extend({
update(store, type, id, attrs) { update(store, type, id, attrs) {
const data = {}; const data = {};
data[Ember.String.underscore(type)] = attrs; const typeField = Ember.String.underscore(type);
data[typeField] = attrs;
return ajax(this.pathFor(store, type, id), { method: 'PUT', data }).then(function(json) { return ajax(this.pathFor(store, type, id), { method: 'PUT', data }).then(function(json) {
return new Result(json[type], json); return new Result(json[typeField], json);
}); });
}, },

View file

@ -0,0 +1,7 @@
import RestAdapter from 'discourse/adapters/rest';
export default RestAdapter.extend({
basePath() {
return "/admin/customize/";
}
});

View file

@ -394,11 +394,8 @@ export default DiscourseController.extend(ModalFunctionality, {
let userFields = this.site.get('user_fields'); let userFields = this.site.get('user_fields');
if (userFields) { if (userFields) {
userFields = userFields.map(function(f) { userFields = _.sortBy(userFields, 'position').map(function(f) {
return Ember.Object.create({ return Ember.Object.create({ value: null, field: f });
value: null,
field: f
});
}); });
} }
this.set('userFields', userFields); this.set('userFields', userFields);

View file

@ -18,7 +18,6 @@ const RestModel = Ember.Object.extend(Presence, {
const self = this; const self = this;
self.set('isSaving', true); self.set('isSaving', true);
return store.update(type, this.get('id'), props).then(function(res) { return store.update(type, this.get('id'), props).then(function(res) {
const payload = self.__munge(res.payload || res.responseJson); const payload = self.__munge(res.payload || res.responseJson);
if (payload.success === "OK") { if (payload.success === "OK") {

View file

@ -1460,7 +1460,6 @@ tr.not-activated {
.controls { .controls {
float: right; float: right;
text-align: right; text-align: right;
width: 20%;
} }
.clearfix { .clearfix {

View file

@ -1,11 +1,13 @@
class Admin::UserFieldsController < Admin::AdminController class Admin::UserFieldsController < Admin::AdminController
def self.columns def self.columns
[:name, :field_type, :editable, :description, :required, :show_on_profile] [:name, :field_type, :editable, :description, :required, :show_on_profile, :position]
end end
def create def create
field = UserField.new(params.require(:user_field).permit(*Admin::UserFieldsController.columns)) field = UserField.new(params.require(:user_field).permit(*Admin::UserFieldsController.columns))
field.position = (UserField.maximum(:position) || 0) + 1
field.required = params[:required] == "true" field.required = params[:required] == "true"
fetch_options(field) fetch_options(field)
@ -15,33 +17,35 @@ class Admin::UserFieldsController < Admin::AdminController
end end
def index def index
user_fields = UserField.all.includes(:user_field_options) user_fields = UserField.all.includes(:user_field_options).order(:position)
render_serialized(user_fields, UserFieldSerializer, root: 'user_fields') render_serialized(user_fields, UserFieldSerializer, root: 'user_fields')
end end
def update def update
field_params = params.require(:user_field) field_params = params[:user_field]
field = UserField.where(id: params.require(:id)).first field = UserField.where(id: params.require(:id)).first
Admin::UserFieldsController.columns.each do |col| Admin::UserFieldsController.columns.each do |col|
field.send("#{col}=", field_params[col] || false) unless field_params[col].nil?
field.send("#{col}=", field_params[col])
end
end end
UserFieldOption.where(user_field_id: field.id).delete_all UserFieldOption.where(user_field_id: field.id).delete_all
fetch_options(field) fetch_options(field)
json_result(field, serializer: UserFieldSerializer) do if field.save
field.save render_serialized(field, UserFieldSerializer, root: 'user_field')
else
render_json_error(field)
end end
end end
def destroy def destroy
field = UserField.where(id: params.require(:id)).first field = UserField.where(id: params.require(:id)).first
field.destroy if field.present? field.destroy if field.present?
render nothing: true render json: success_json
end end
protected protected
def fetch_options(field) def fetch_options(field)

View file

@ -6,6 +6,7 @@ class UserFieldSerializer < ApplicationSerializer
:editable, :editable,
:required, :required,
:show_on_profile, :show_on_profile,
:position,
:options :options
def options def options

View file

@ -0,0 +1,6 @@
class AddPositionToUserFields < ActiveRecord::Migration
def change
add_column :user_fields, :position, :integer, default: 0
execute "UPDATE user_fields SET position = (SELECT COUNT(*) from user_fields as uf2 where uf2.id < user_fields.id)"
end
end

View file

@ -249,6 +249,12 @@ export default function() {
return response({ widget }); return response({ widget });
}); });
this.put('/cool_things/:cool_thing_id', function(request) {
const cool_thing = parsePostData(request.requestBody).cool_thing;
return response({ cool_thing });
});
this.get('/widgets', function(request) { this.get('/widgets', function(request) {
let result = _widgets; let result = _widgets;

View file

@ -70,6 +70,14 @@ test('update', function() {
}); });
}); });
test('update with a multi world name', function(assert) {
const store = createStore();
return store.update('cool-thing', 123, {name: 'hello'}).then(function(result) {
assert.ok(result);
assert.equal(result.payload.name, 'hello');
});
});
test('findAll', function() { test('findAll', function() {
const store = createStore(); const store = createStore();
return store.findAll('widget').then(function(result) { return store.findAll('widget').then(function(result) {
@ -84,7 +92,7 @@ test('destroyRecord', function(assert) {
const store = createStore(); const store = createStore();
return store.find('widget', 123).then(function(w) { return store.find('widget', 123).then(function(w) {
store.destroyRecord('widget', w).then(function(result) { store.destroyRecord('widget', w).then(function(result) {
ok(result); assert.ok(result);
}); });
}); });
}); });