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'),
classNameBindings: [':user-field'],
cantMoveUp: Discourse.computed.propertyEqual('userField', 'firstField'),
cantMoveDown: Discourse.computed.propertyEqual('userField', 'lastField'),
userFieldsDescription: function() {
return I18n.t('admin.user_fields.description');
}.property(),
@ -55,13 +58,20 @@ export default Ember.Component.extend(bufferedProperty('userField'), {
'show_on_profile',
'options');
this.get('userField').save(attrs).then(function(res) {
self.set('userField.id', res.user_field.id);
this.get('userField').save(attrs).then(function() {
self.set('editing', false);
self.commitBuffer();
}).catch(popupAjaxError);
},
moveUp() {
this.sendAction('moveUpAction', this.get('userField'));
},
moveDown() {
this.sendAction('moveDownAction', this.get('userField'));
},
edit() {
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,
createDisabled: Em.computed.gte('model.length', 20),
createDisabled: Em.computed.gte('model.length', MAX_FIELDS),
_performDestroy(f, model) {
return f.destroy().then(function() {
model.removeObject(f);
arrangedContent: function() {
return Ember.ArrayProxy.extend(Ember.SortableMixin).create({
sortProperties: ['position'],
content: this.get('model')
});
},
}.property('model'),
actions: {
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) {
const model = this.get('model'),
self = this;
const model = this.get('model');
// Only confirm if we already been saved
if (f.get('id')) {
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 {
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 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 UserField = RestModel.extend();
const UserFieldType = Ember.Object.extend({
name: Discourse.computed.i18n('id', 'admin.user_fields.field_types.%@')
});
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() {
if (!this._fieldTypes) {
this._fieldTypes = [

View file

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

View file

@ -35,13 +35,17 @@
{{/admin-form-row}}
{{else}}
<div class="row">
<div class='form-display'><strong>{{userField.name}}</strong></div>
<div class='form-display'>{{{userField.description}}}</div>
<div class='form-display'>
<strong>{{userField.name}}</strong>
<br/>
{{{userField.description}}}
</div>
<div class='form-display'>{{fieldName}}</div>
<div class='form-display'></div>
<div class='form-element controls'>
{{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="moveUp" icon="arrow-up" disabled=cantMoveUp}}
{{d-button action="moveDown" icon="arrow-down" disabled=cantMoveDown}}
</div>
</div>
<div class="row">{{flags}}</div>

View file

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

View file

@ -17,10 +17,14 @@ function rethrow(error) {
}
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 (typeof findArgs === "object") {
@ -51,9 +55,10 @@ export default Ember.Object.extend({
update(store, type, id, attrs) {
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 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');
if (userFields) {
userFields = userFields.map(function(f) {
return Ember.Object.create({
value: null,
field: f
});
userFields = _.sortBy(userFields, 'position').map(function(f) {
return Ember.Object.create({ value: null, field: f });
});
}
this.set('userFields', userFields);

View file

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

View file

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

View file

@ -1,11 +1,13 @@
class Admin::UserFieldsController < Admin::AdminController
def self.columns
[:name, :field_type, :editable, :description, :required, :show_on_profile]
[:name, :field_type, :editable, :description, :required, :show_on_profile, :position]
end
def create
field = UserField.new(params.require(:user_field).permit(*Admin::UserFieldsController.columns))
field.position = (UserField.maximum(:position) || 0) + 1
field.required = params[:required] == "true"
fetch_options(field)
@ -15,33 +17,35 @@ class Admin::UserFieldsController < Admin::AdminController
end
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')
end
def update
field_params = params.require(:user_field)
field_params = params[:user_field]
field = UserField.where(id: params.require(:id)).first
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
UserFieldOption.where(user_field_id: field.id).delete_all
fetch_options(field)
json_result(field, serializer: UserFieldSerializer) do
field.save
if field.save
render_serialized(field, UserFieldSerializer, root: 'user_field')
else
render_json_error(field)
end
end
def destroy
field = UserField.where(id: params.require(:id)).first
field.destroy if field.present?
render nothing: true
render json: success_json
end
protected
def fetch_options(field)

View file

@ -6,6 +6,7 @@ class UserFieldSerializer < ApplicationSerializer
:editable,
:required,
:show_on_profile,
:position,
: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 });
});
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) {
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() {
const store = createStore();
return store.findAll('widget').then(function(result) {
@ -84,7 +92,7 @@ test('destroyRecord', function(assert) {
const store = createStore();
return store.find('widget', 123).then(function(w) {
store.destroyRecord('widget', w).then(function(result) {
ok(result);
assert.ok(result);
});
});
});