Delete campaign feature

This commit is contained in:
Ingo Schommer 2016-03-30 16:38:48 +13:00
parent 0675f85d32
commit d27e3c2d34
5 changed files with 120 additions and 25 deletions

View File

@ -334,11 +334,26 @@ JSON;
* @return SS_HTTPResponse
*/
public function deleteCampaign(SS_HTTPRequest $request) {
$response = new SS_HTTPResponse();
$response->addHeader('Content-Type', 'application/json');
$response->setBody(Convert::raw2json(['campaign' => 'delete']));
$id = $request->param('ID');
if (!$id || !is_numeric($id)) {
return (new SS_HTTPResponse(json_encode(['status' => 'error']), 400))
->addHeader('Content-Type', 'application/json');
}
return $response;
$record = ChangeSet::get()->byID($id);
if(!$record) {
return (new SS_HTTPResponse(json_encode(['status' => 'error']), 404))
->addHeader('Content-Type', 'application/json');
}
if(!$record->canDelete()) {
return (new SS_HTTPResponse(json_encode(['status' => 'error']), 401))
->addHeader('Content-Type', 'application/json');
}
$record->delete();
return (new SS_HTTPResponse('', 204));
}
/**

View File

@ -42,15 +42,13 @@ class GridField extends SilverStripeComponent {
const columns = this.props.data.columns;
const actions = [
<GridFieldAction icon={'cog'} handleClick={this.editRecord} />,
<GridFieldAction icon={'cancel'} handleClick={this.deleteRecord} />
];
// TODO Replace with display: table based system
const cellWidth = 5;
const actionCellWidth = 2 * 36 + 12;
// Placeholder to align the headers correctly with the content
const actionPlaceholder = <span key={'actionPlaceholder'} style={{width: actions.length * 36 + 12}} />;
const headerCells = columns.map((column, i) => <GridFieldHeaderCell key={i} width={column.width}>{column.name}</GridFieldHeaderCell>);
const actionPlaceholder = <span key={'actionPlaceholder'} style={{width: actionCellWidth}} />;
const headerCells = columns.map((column, i) => <GridFieldHeaderCell key={i} width={cellWidth}>{column.name}</GridFieldHeaderCell>);
const header = <GridFieldHeader>{headerCells.concat(actionPlaceholder)}</GridFieldHeader>;
const rows = records.map((record, i) => {
@ -60,11 +58,18 @@ class GridField extends SilverStripeComponent {
return <GridFieldCell key={i} width={column.width}>{val}</GridFieldCell>
});
var rowActions = actions.map((action, j) => {
return Object.assign({}, action, {
key: `action-${i}-${j}`
});
})
var rowActions = [
<GridFieldAction
icon={'cog'}
handleClick={this.editRecord.bind(this, record.ID)}
key={"action-" + i + "-edit"}
/>,
<GridFieldAction
icon={'cancel'}
handleClick={this.deleteRecord.bind(this, record.ID)}
key={"action-" + i + "-delete"}
/>
];
return <GridFieldRow key={i}>{cells.concat(rowActions)}</GridFieldRow>;
});
@ -74,12 +79,23 @@ class GridField extends SilverStripeComponent {
);
}
deleteRecord(event) {
// delete record
/**
* @param number int
* @param event
*/
deleteRecord(id, event) {
event.preventDefault();
this.props.actions.deleteRecord(
this.props.data.recordType,
id,
this.props.data.itemDeleteEndpoint.method,
this.props.data.itemDeleteEndpoint.url
);
}
editRecord(event) {
// edit record
event.preventDefault();
// TODO
}
}

View File

@ -4,5 +4,8 @@ export default {
DELETE_RECORD: 'DELETE_RECORD',
FETCH_RECORDS_REQUEST: 'FETCH_RECORDS_REQUEST',
FETCH_RECORDS_FAILURE: 'FETCH_RECORDS_FAILURE',
FETCH_RECORDS_SUCCESS: 'FETCH_RECORDS_SUCCESS'
FETCH_RECORDS_SUCCESS: 'FETCH_RECORDS_SUCCESS',
DELETE_RECORD_REQUEST: 'DELETE_RECORD_REQUEST',
DELETE_RECORD_FAILURE: 'DELETE_RECORD_FAILURE',
DELETE_RECORD_SUCCESS: 'DELETE_RECORD_SUCCESS'
};

View File

@ -2,21 +2,64 @@ import ACTION_TYPES from './action-types';
import fetch from 'isomorphic-fetch';
import backend from 'silverstripe-backend.js';
/**
* Populate strings based on a whitelist.
* Not using ES6 string interpolation because its too powerful
* for user supplied data.
*
* Example: populate("foo/bar/:id", {id: 123}) => "foo/bar/123"
*
* @param string str A template string with ":<name>" notation.
* @param object Map of names to values
* @return string
*/
function populate(str, params) {
let names = ['id'];
return names.reduce((str, name) => str.replace(`:${name}`, params[name]), str);
}
/**
* Retrieves all records
*
* @param string recordType Type of record (the "class name")
* @param string method HTTP methods
* @param string method HTTP method
* @param string url API endpoint
*/
export function fetchRecords(recordType, method, url) {
let payload = {recordType: recordType};
url = populate(url, payload);
return (dispatch, getState) => {
dispatch ({type: ACTION_TYPES.FETCH_RECORDS_REQUEST, payload: {recordType: recordType}});
dispatch ({type: ACTION_TYPES.FETCH_RECORDS_REQUEST, payload: payload});
return backend[method.toLowerCase()](url)
.then(response => response.json())
.then(json => dispatch({type: ACTION_TYPES.FETCH_RECORDS_SUCCESS, payload: {recordType: recordType, data: json}}))
.then(json => {
dispatch({type: ACTION_TYPES.FETCH_RECORDS_SUCCESS, payload: {recordType: recordType, data: json}})
})
.catch((err) => {
dispatch({type: ACTION_TYPES.FETCH_RECORDS_FAILURE, payload: {error: err, recordType: recordType}})
});
}
}
/**
* Deletes a record
*
* @param string recordType Type of record (the "class name")
* @param number id Database identifier
* @param string method HTTP method
* @param string url API endpoint
*/
export function deleteRecord(recordType, id, method, url) {
let payload = {recordType: recordType, id: id};
url = populate(url, payload);
return (dispatch, getState) => {
dispatch ({type: ACTION_TYPES.DELETE_RECORD_REQUEST, payload: payload});
return backend[method.toLowerCase()](url)
.then(json => {
dispatch({type: ACTION_TYPES.DELETE_RECORD_SUCCESS, payload: {recordType: recordType, id: id}})
})
.catch((err) => {
dispatch({type: ACTION_TYPES.DELETE_RECORD_FAILURE, payload: {error: err, recordType: recordType, id: id}})
});
}
}

View File

@ -5,6 +5,9 @@ const initialState = {
};
function recordsReducer(state = initialState, action) {
let records;
let recordType;
switch (action.type) {
case ACTION_TYPES.CREATE_RECORD:
@ -29,9 +32,24 @@ function recordsReducer(state = initialState, action) {
return state;
case ACTION_TYPES.FETCH_RECORDS_SUCCESS:
let recordType = action.payload.recordType;
recordType = action.payload.recordType;
// TODO Automatic pluralisation from recordType
let records = action.payload.data._embedded[recordType + 's'];
records = action.payload.data._embedded[recordType + 's'];
return deepFreeze(Object.assign({}, state, {
[recordType]: records
}));
case ACTION_TYPES.DELETE_RECORD_REQUEST:
return state;
case ACTION_TYPES.DELETE_RECORD_FAILURE:
return state;
case ACTION_TYPES.DELETE_RECORD_SUCCESS:
recordType = action.payload.recordType;
records = state[recordType]
.filter(record => record.ID != action.payload.id)
return deepFreeze(Object.assign({}, state, {
[recordType]: records
}));