add some validation to row edit, better handle setting null cols with empty
This commit is contained in:
@@ -6,6 +6,7 @@ use Exception;
|
|||||||
use Illuminate\Auth\AuthenticationException;
|
use Illuminate\Auth\AuthenticationException;
|
||||||
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||||
use Illuminate\Validation\ValidationException;
|
use Illuminate\Validation\ValidationException;
|
||||||
|
use MightyPork\Exceptions\Exceptions\SimpleValidationException;
|
||||||
|
|
||||||
class Handler extends ExceptionHandler
|
class Handler extends ExceptionHandler
|
||||||
{
|
{
|
||||||
@@ -15,7 +16,7 @@ class Handler extends ExceptionHandler
|
|||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $dontReport = [
|
protected $dontReport = [
|
||||||
//
|
SimpleValidationException::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace FlowBox\Exceptions;
|
|
||||||
|
|
||||||
class ViewException extends FBRuntimeException
|
|
||||||
{
|
|
||||||
public $captured;
|
|
||||||
|
|
||||||
public function __construct(\Exception $cause, $captured)
|
|
||||||
{
|
|
||||||
$this->captured = $captured;
|
|
||||||
parent::__construct($cause);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,14 +3,12 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Models\Row;
|
|
||||||
use App\Models\Table;
|
use App\Models\Table;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Tables\Changeset;
|
use App\Tables\Changeset;
|
||||||
use App\Tables\Column;
|
|
||||||
use Illuminate\Http\JsonResponse;
|
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Input;
|
use Illuminate\Support\Facades\Input;
|
||||||
|
use MightyPork\Exceptions\SimpleValidationException;
|
||||||
|
|
||||||
class TableEditController extends Controller
|
class TableEditController extends Controller
|
||||||
{
|
{
|
||||||
@@ -22,14 +20,12 @@ class TableEditController extends Controller
|
|||||||
*/
|
*/
|
||||||
private function getChangeset(Table $table)
|
private function getChangeset(Table $table)
|
||||||
{
|
{
|
||||||
$session_key = "proposal_{$table->id}";
|
|
||||||
|
|
||||||
if (Input::has('reset')) {
|
if (Input::has('reset')) {
|
||||||
session()->forget($session_key);
|
session()->forget($table->draftSessionKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @var Changeset $changeset */
|
/** @var Changeset $changeset */
|
||||||
return session()->remember($session_key, function () use ($table) {
|
return session()->remember($table->draftSessionKey, function () use ($table) {
|
||||||
$changeset = new Changeset();
|
$changeset = new Changeset();
|
||||||
$changeset->table = $table;
|
$changeset->table = $table;
|
||||||
$changeset->revision = $table->revision;
|
$changeset->revision = $table->revision;
|
||||||
@@ -39,10 +35,21 @@ class TableEditController extends Controller
|
|||||||
|
|
||||||
private function storeChangeset(Changeset $chs)
|
private function storeChangeset(Changeset $chs)
|
||||||
{
|
{
|
||||||
$session_key = "proposal_{$chs->table->id}";
|
session()->put($chs->table->draftSessionKey, $chs);
|
||||||
|
}
|
||||||
|
|
||||||
session()->put($session_key, $chs);
|
/**
|
||||||
session()->save();
|
* Discard draft and redirect to table view
|
||||||
|
*/
|
||||||
|
public function discard(User $user, string $table)
|
||||||
|
{
|
||||||
|
/** @var Table $tableModel */
|
||||||
|
$tableModel = $user->tables()->where('name', $table)->first();
|
||||||
|
if ($tableModel === null) abort(404, "No such table.");
|
||||||
|
|
||||||
|
session()->forget($tableModel->draftSessionKey);
|
||||||
|
|
||||||
|
return redirect($tableModel->viewRoute);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function draft(User $user, string $table, $tab = null)
|
public function draft(User $user, string $table, $tab = null)
|
||||||
@@ -103,29 +110,33 @@ class TableEditController extends Controller
|
|||||||
|
|
||||||
$input = objBag($request->all(), false);
|
$input = objBag($request->all(), false);
|
||||||
|
|
||||||
$code = 200;
|
try {
|
||||||
switch ($input->action) {
|
$code = 200;
|
||||||
case 'row.update':
|
switch ($input->action) {
|
||||||
$data = (object)$input->data;
|
case 'row.update':
|
||||||
$changeset->rowUpdate($data);
|
$data = (object)$input->data;
|
||||||
|
$changeset->rowUpdate($data);
|
||||||
|
|
||||||
$resp = $changeset->fetchAndTransformRow($data->_id);
|
$resp = $changeset->fetchAndTransformRow($data->_id);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'row.remove':
|
case 'row.remove':
|
||||||
$changeset->rowRemove($input->id);
|
$changeset->rowRemove($input->id);
|
||||||
$resp = $changeset->fetchAndTransformRow($input->id);
|
$resp = $changeset->fetchAndTransformRow($input->id);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'row.restore':
|
case 'row.restore':
|
||||||
$changeset->rowRestore($input->id);
|
$changeset->rowRestore($input->id);
|
||||||
$resp = $changeset->fetchAndTransformRow($input->id);
|
$resp = $changeset->fetchAndTransformRow($input->id);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
$resp = "Bad Action";
|
$resp = "Bad Action";
|
||||||
$code = 400;
|
$code = 400;
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
} catch (SimpleValidationException $e) {
|
||||||
|
return $this->jsonResponse(['errors' => $e->getMessageBag()->getMessages()], 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->storeChangeset($changeset);
|
$this->storeChangeset($changeset);
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ use Illuminate\Database\Eloquent\Collection;
|
|||||||
* @property-read string $draftRoute
|
* @property-read string $draftRoute
|
||||||
* @property-read string $settingsRoute
|
* @property-read string $settingsRoute
|
||||||
* @property-read string $deleteRoute
|
* @property-read string $deleteRoute
|
||||||
|
* @property-read string $draftSessionKey
|
||||||
|
* @property-read string $draftDiscardRoute
|
||||||
|
* @property-read string $draftUpdateRoute
|
||||||
* @property-read User $owner
|
* @property-read User $owner
|
||||||
* @property-read Table $parentTable
|
* @property-read Table $parentTable
|
||||||
* @property-read Table[]|Collection $forks
|
* @property-read Table[]|Collection $forks
|
||||||
@@ -136,9 +139,13 @@ class Table extends BaseModel
|
|||||||
case 'settingsRoute': return route('table.conf', $arg);
|
case 'settingsRoute': return route('table.conf', $arg);
|
||||||
case 'draftRoute': return route('table.draft', $arg);
|
case 'draftRoute': return route('table.draft', $arg);
|
||||||
case 'deleteRoute': return route('table.delete', $arg);
|
case 'deleteRoute': return route('table.delete', $arg);
|
||||||
|
case 'draftDiscardRoute': return route('table.draft-discard', $arg);
|
||||||
|
case 'draftUpdateRoute': return route('table.draft-update', $arg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($name == 'draftSessionKey') return "proposal.{$this->id}";
|
||||||
|
|
||||||
return parent::__get($name);
|
return parent::__get($name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -325,8 +325,12 @@ class Changeset
|
|||||||
foreach ($newVals as $colId => $value) {
|
foreach ($newVals as $colId => $value) {
|
||||||
if (starts_with($colId, '_')) continue; // internals
|
if (starts_with($colId, '_')) continue; // internals
|
||||||
|
|
||||||
if (!isset($origRow->$colId) || $value != $origRow->$colId) {
|
$col = $cols[$colId];
|
||||||
$updateObj[$colId] = $cols[$colId]->cast($value);
|
$value = $col->cast($value);
|
||||||
|
$origValue = $col->cast(isset($origRow->$colId) ? $origRow->$colId : null);
|
||||||
|
|
||||||
|
if ($value !== $origValue) {
|
||||||
|
$updateObj[$colId] = $value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ namespace App\Tables;
|
|||||||
|
|
||||||
use Illuminate\Contracts\Support\Arrayable;
|
use Illuminate\Contracts\Support\Arrayable;
|
||||||
use JsonSerializable;
|
use JsonSerializable;
|
||||||
|
use MightyPork\Exceptions\SimpleValidationException;
|
||||||
use MightyPork\Exceptions\NotApplicableException;
|
use MightyPork\Exceptions\NotApplicableException;
|
||||||
use MightyPork\Utils\Utils;
|
use MightyPork\Utils\Utils;
|
||||||
|
|
||||||
@@ -172,13 +173,13 @@ class Column implements JsonSerializable, Arrayable
|
|||||||
if (is_int($value)) return $value;
|
if (is_int($value)) return $value;
|
||||||
if (is_float($value)) return round($value);
|
if (is_float($value)) return round($value);
|
||||||
if (is_numeric($value)) return intval($value);
|
if (is_numeric($value)) return intval($value);
|
||||||
throw new NotApplicableException("Could not convert value \"$value\" to int!");
|
throw new SimpleValidationException($this->id, "Could not convert value \"$value\" to int!");
|
||||||
|
|
||||||
case 'float':
|
case 'float':
|
||||||
if (is_null($value)) return 0;
|
if (is_null($value)) return 0;
|
||||||
if (is_int($value) || is_float($value)) return (float)$value;
|
if (is_int($value) || is_float($value)) return (float)$value;
|
||||||
if (is_numeric($value)) return floatval($value);
|
if (is_numeric($value)) return floatval($value);
|
||||||
throw new NotApplicableException("Could not convert value \"$value\" to float!");
|
throw new SimpleValidationException($this->id, "Could not convert value \"$value\" to float!");
|
||||||
|
|
||||||
case 'bool':
|
case 'bool':
|
||||||
if (is_null($value)) return false;
|
if (is_null($value)) return false;
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace MightyPork\Exceptions;
|
||||||
|
|
||||||
|
use Illuminate\Contracts\Support\MessageProvider;
|
||||||
|
use Illuminate\Support\MessageBag;
|
||||||
|
|
||||||
|
class SimpleValidationException extends RuntimeException implements MessageProvider
|
||||||
|
{
|
||||||
|
/** @var MessageBag */
|
||||||
|
private $mb;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FBValidationException constructor.
|
||||||
|
*
|
||||||
|
* @param string|MessageProvider $key
|
||||||
|
* @param string $message
|
||||||
|
*/
|
||||||
|
public function __construct($key, $message = null)
|
||||||
|
{
|
||||||
|
if (is_null($message)) {
|
||||||
|
$this->mb = $key->getMessageBag();
|
||||||
|
} else {
|
||||||
|
$mb = new MessageBag();
|
||||||
|
$mb->add($key, $message);
|
||||||
|
$this->mb = $mb;
|
||||||
|
}
|
||||||
|
|
||||||
|
$str = '';
|
||||||
|
foreach ($this->mb->getMessages() as $key => $errors) {
|
||||||
|
$str .= $key . ': ' . implode(', ', $errors) . "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
parent::__construct($str);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the messages for the instance.
|
||||||
|
*
|
||||||
|
* @return MessageBag
|
||||||
|
*/
|
||||||
|
public function getMessageBag()
|
||||||
|
{
|
||||||
|
return $this->mb;
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
//window._ = require('lodash');
|
window._ = require('lodash');
|
||||||
window.Popper = require('popper.js').default
|
window.Popper = require('popper.js').default
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -26,14 +26,16 @@
|
|||||||
|
|
||||||
<template v-if="row._editing">
|
<template v-if="row._editing">
|
||||||
<td v-for="col in columns" class="pr-0">
|
<td v-for="col in columns" class="pr-0">
|
||||||
<input v-model="row[col.id]" class="form-control" type="text">
|
<input v-model="row[col.id]" :class="['form-control', { 'is-invalid': row._errors && row._errors[col.id] }]"
|
||||||
|
:title="(row._errors && row._errors[col.id]) ? row._errors[col.id][0] : null"
|
||||||
|
type="text" @keyup.enter="toggleRowEditing(row._id)">
|
||||||
</td>
|
</td>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<td v-for="col in columns">
|
<td v-for="col in columns">
|
||||||
<span class="text-danger strike" title="Original value" v-if="isChanged(row, col.id)">{{row._orig[col.id]}}</span>
|
<span class="text-danger strike" title="Original value" v-if="isChanged(row, col.id)">{{row._orig[col.id]}}</span>
|
||||||
<span>{{ row[col.id] || '' }}</span>
|
<span>{{ row[col.id] }}</span>
|
||||||
<v-icon v-if="isChanged(row, col.id)"
|
<v-icon v-if="isChanged(row, col.id)"
|
||||||
@click="revertCell(row, col.id)"
|
@click="revertCell(row, col.id)"
|
||||||
class="fa-undo text-danger pointer"
|
class="fa-undo text-danger pointer"
|
||||||
@@ -52,8 +54,6 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { merge } from 'lodash';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
route: String,
|
route: String,
|
||||||
@@ -69,11 +69,13 @@ export default {
|
|||||||
busy (yes) {
|
busy (yes) {
|
||||||
$('#draft-busy').css('opacity', yes ? 1 : 0)
|
$('#draft-busy').css('opacity', yes ? 1 : 0)
|
||||||
},
|
},
|
||||||
query (data, sucfn) {
|
query (data, sucfn, erfn) {
|
||||||
this.busy(true)
|
this.busy(true)
|
||||||
if (!sucfn) sucfn = ()=>{}
|
if (!sucfn) sucfn = ()=>{}
|
||||||
window.axios.post(this.route, data).then(sucfn).catch((e) => {
|
if (!sucfn) erfn = ()=>{}
|
||||||
console.error(e)
|
window.axios.post(this.route, data).then(sucfn).catch((error) => {
|
||||||
|
console.error(error.message)
|
||||||
|
erfn(error.response.data)
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
this.busy(false)
|
this.busy(false)
|
||||||
})
|
})
|
||||||
@@ -94,6 +96,10 @@ export default {
|
|||||||
data: row
|
data: row
|
||||||
}, (resp) => {
|
}, (resp) => {
|
||||||
this.$set(this.rows, resp.data._id, resp.data);
|
this.$set(this.rows, resp.data._id, resp.data);
|
||||||
|
}, (er) => {
|
||||||
|
if (!_.isUndefined(er.errors)) {
|
||||||
|
this.$set(this.rows[row._id], '_errors', er.errors);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
toggleRowEditing(_id) {
|
toggleRowEditing(_id) {
|
||||||
@@ -127,7 +133,7 @@ export default {
|
|||||||
return row._changed.indexOf(colId) > -1
|
return row._changed.indexOf(colId) > -1
|
||||||
},
|
},
|
||||||
revertCell(row, colId) {
|
revertCell(row, colId) {
|
||||||
this.submitRowChange(merge({}, row, { [colId]: row._orig[colId] }))
|
this.submitRowChange(_.merge({}, row, { [colId]: row._orig[colId] }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,40 +4,24 @@
|
|||||||
/** @var \App\Tables\Changeset $changeset */
|
/** @var \App\Tables\Changeset $changeset */
|
||||||
/** @var \App\Models\Row[]|Illuminate\Pagination\Paginator $rows */
|
/** @var \App\Models\Row[]|Illuminate\Pagination\Paginator $rows */
|
||||||
/** @var \App\Models\Table $table */
|
/** @var \App\Models\Table $table */
|
||||||
|
/** @var \App\Tables\Column[] $columns */
|
||||||
|
/** @var \App\Tables\Changeset $changeset */
|
||||||
|
/** @var \App\Models\Table $table */
|
||||||
@endphp
|
@endphp
|
||||||
|
|
||||||
@extends('table.propose.layout')
|
@extends('table.propose.layout-row-pagination')
|
||||||
|
|
||||||
@section('tab-content')
|
@section('rows')
|
||||||
|
@php
|
||||||
|
$transformed = $rows->keyBy('_id')->map(function($r) use ($changeset) {
|
||||||
|
/** @var \App\Tables\Changeset $changeset */
|
||||||
|
return $changeset->transformRow($r, true);
|
||||||
|
});
|
||||||
|
@endphp
|
||||||
|
|
||||||
@if($rows->hasPages())
|
<table is="row-editor"
|
||||||
<div class="col-md-12 d-flex">
|
route="{{$table->draftUpdateRoute}}"
|
||||||
<nav class="text-center" aria-label="Pages of the table">
|
:columns="{{toJSON($columns)}}"
|
||||||
{{ $rows->links(null, ['ulClass' => 'mb-0']) }}
|
:x-rows="{{toJSON($transformed)}}">
|
||||||
</nav>
|
</table>
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
<div class="col-12">
|
|
||||||
@php
|
|
||||||
$transformed = $rows->keyBy('_id')->map(function($r) use ($changeset) {
|
|
||||||
/** @var \App\Tables\Changeset $changeset */
|
|
||||||
return $changeset->transformRow($r, true);
|
|
||||||
});
|
|
||||||
@endphp
|
|
||||||
|
|
||||||
<table is="row-editor"
|
|
||||||
route="{{$table->getDraftRoute('update')}}"
|
|
||||||
:columns="{{toJSON($columns)}}"
|
|
||||||
:x-rows="{{toJSON($transformed)}}">
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if($rows->hasPages())
|
|
||||||
<div class="col-md-12 d-flex">
|
|
||||||
<nav class="text-center" aria-label="Pages of the table">
|
|
||||||
{{ $rows->links(null, ['ulClass' => 'mb-0']) }}
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
@stop
|
@stop
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
@php
|
||||||
|
/** @var \App\Models\Row[]|Illuminate\Pagination\Paginator $rows */
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
@extends('table.propose.layout')
|
||||||
|
|
||||||
|
@section('tab-content')
|
||||||
|
@if($rows->hasPages())
|
||||||
|
<div class="col-md-12 d-flex">
|
||||||
|
<nav class="text-center" aria-label="Pages of the table">
|
||||||
|
{{ $rows->links(null, ['ulClass' => 'mb-0']) }}
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<div class="col-12">
|
||||||
|
@yield('rows')
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if($rows->hasPages())
|
||||||
|
<div class="col-md-12 d-flex">
|
||||||
|
<nav class="text-center" aria-label="Pages of the table">
|
||||||
|
{{ $rows->links(null, ['ulClass' => 'mb-0']) }}
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
@stop
|
||||||
@@ -22,7 +22,7 @@ if (!isset($tab) || $tab == '') $tab = 'edit-rows';
|
|||||||
<h1 class="mx-3">
|
<h1 class="mx-3">
|
||||||
<a href="{{ $table->viewRoute }}" class="link-no-color">{{ $table->title }}</a>
|
<a href="{{ $table->viewRoute }}" class="link-no-color">{{ $table->title }}</a>
|
||||||
</h1>
|
</h1>
|
||||||
<a href="" class="btn btn-outline-danger mr-2" @tooltip(Discard changes)>
|
<a href="{{$table->draftDiscardRoute}}" class="btn btn-outline-danger mr-2" @tooltip(Discard changes)>
|
||||||
@icon(fa-trash-o, sr:Discard)
|
@icon(fa-trash-o, sr:Discard)
|
||||||
</a>
|
</a>
|
||||||
@if(user()->ownsTable($table))
|
@if(user()->ownsTable($table))
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ Route::group(['middleware' => ['auth', 'activated']], function () {
|
|||||||
Route::post('@{user}/{table}/delete', 'TableController@delete')->name('table.delete');
|
Route::post('@{user}/{table}/delete', 'TableController@delete')->name('table.delete');
|
||||||
|
|
||||||
Route::post('@{user}/{table}/draft/update', 'TableEditController@draftUpdate')->name('table.draft-update');
|
Route::post('@{user}/{table}/draft/update', 'TableEditController@draftUpdate')->name('table.draft-update');
|
||||||
|
Route::get('@{user}/{table}/draft/discard', 'TableEditController@discard')->name('table.draft-discard');
|
||||||
Route::get('@{user}/{table}/draft/{tab?}', 'TableEditController@draft')->name('table.draft');
|
Route::get('@{user}/{table}/draft/{tab?}', 'TableEditController@draft')->name('table.draft');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user