form improvements

pull/26/head
Ondřej Hruška 6 years ago
parent 3242ae9cbe
commit a9ffd378a0
Signed by: MightyPork
GPG Key ID: 2C5FD5035250423D
  1. 3
      app/Http/Controllers/Auth/RegisterController.php
  2. 24
      app/Http/Controllers/TableController.php
  3. 3
      app/Models/User.php
  4. 14
      app/View/CheckboxWidget.php
  5. 36
      app/View/Widget.php
  6. 33
      app/View/WidgetFactory.php
  7. 3
      database/migrations/2014_10_12_000000_create_users_table.php
  8. 1
      database/migrations/2018_07_08_193700_create_tables_table.php
  9. 2
      resources/assets/sass/_bst-modules.scss
  10. 5
      resources/assets/sass/app.scss
  11. 148
      resources/views/auth/login.blade.php
  12. 55
      resources/views/auth/register.blade.php
  13. 2
      resources/views/form/_help.blade.php
  14. 22
      resources/views/form/checkbox.blade.php
  15. 4
      resources/views/form/input.blade.php
  16. 4
      resources/views/form/textarea.blade.php
  17. 31
      resources/views/table/create.blade.php

@ -49,7 +49,7 @@ class RegisterController extends Controller
protected function validator(array $data)
{
return Validator::make($data, [
'name' => 'required|string|max:255',
'name' => 'required|string|max:255|regex:/^[a-zA-Z0-9_.-]+$/',
'email' => 'required|string|email|max:255|unique:users',
'password' => 'required|string|min:6|confirmed',
]);
@ -65,6 +65,7 @@ class RegisterController extends Controller
{
return User::create([
'name' => $data['name'],
'title' => $data['name'], // display name - by default, init to name
'email' => $data['email'],
'password' => Hash::make($data['password']),
]);

@ -2,6 +2,7 @@
namespace App\Http\Controllers;
use App\Models\Table;
use Illuminate\Http\Request;
class TableController extends Controller
@ -13,7 +14,7 @@ class TableController extends Controller
*/
public function create()
{
$exampleColSpec =
$exampleColumns =
"latin, string, Latin Name\n".
"common, string, Common Name\n".
"lifespan, int, Lifespan (years)";
@ -24,21 +25,32 @@ class TableController extends Controller
"Patella vulgata, common limpet, 20";
return view('table.create',
compact('exampleColSpec', 'exampleData')
compact('exampleColumns', 'exampleData')
);
}
public function storeNew(Request $request)
{
$u = \Auth::user();
$this->validate($request, [
'table-name' => 'required',
'table-descr' => 'string|nullable',
'name' => 'required',
'title' => 'string|nullable',
'description' => 'string|nullable',
'license' => 'string|nullable',
'upstream' => 'string|nullable',
'col-spec' => 'required',
'row-data' => 'string|nullable',
'columns' => 'required',
'data' => 'string|nullable',
]);
// Check if table name is unique for user
$name = $request->get('name');
if ($u->tables()->where('name', $name)->exists()) {
return back()->withErrors([
'title' => "A table called \"$name\" already exists in your account.",
]);
}
return "Ok.";
}
}

@ -16,6 +16,7 @@ use Illuminate\Notifications\Notification;
* @property \Carbon\Carbon $created_at
* @property \Carbon\Carbon $updated_at
* @property string $name - unique, for vanity URL
* @property string $title - for display
* @property string $email - unique, for login and social auth chaining
* @property string $password - hashed pw
* @property Proposal[]|Collection $proposals
@ -40,7 +41,7 @@ class User extends Authenticatable
* @var array
*/
protected $fillable = [
'name', 'email', 'password',
'name', 'title', 'email', 'password',
];
/**

@ -0,0 +1,14 @@
<?php
namespace App\View;
class CheckboxWidget extends Widget
{
public function checked($condition=true)
{
$this->value = (bool)$condition;
return $this;
}
}

@ -12,14 +12,17 @@ namespace App\View;
* @property-read string|null $help
* @property-read string|null $value
* @property-read string $attributes
* @property-read int $labelCols
* @property-read int $fieldCols
*/
class Widget
{
private $attributes = [],
protected $attributesArray = [],
$id, $name, $label, $value,
$viewName,
$help,
$style = [];
$labelCols, $fieldCols,
$styleArray = [];
public function __construct($viewName, $name, $label)
{
@ -36,7 +39,7 @@ class Widget
'height', 'minHeight', 'maxHeight'
];
if (empty($args)) $args = [''];
if (empty($args)) $args = [null];
$arg = $args[0];
@ -50,7 +53,7 @@ class Widget
$this->$method = $arg;
}
else {
$this->attributes[$method] = $arg;
$this->attributesArray[$method] = $arg;
}
return $this;
@ -58,7 +61,15 @@ class Widget
public function css($prop, $val)
{
$this->style[$prop] = $val;
$this->styleArray[$prop] = $val;
return $this;
}
/** Apply a given layout (bootstrap grid, 12 cols total) */
public function layout($labelCols, $fieldCols)
{
$this->labelCols = $labelCols;
$this->fieldCols = $fieldCols;
return $this;
}
@ -82,14 +93,19 @@ class Widget
public function compileAttribs()
{
// compile attribs string
$attribs_s = array_reduce(array_keys($this->attributes), function ($carry, $key) {
return $carry . ' ' . $key . '="' . e($this->attributes[$key]) . '"';
$attribs_s = array_reduce(array_keys($this->attributesArray), function ($carry, $key) {
if ($this->attributesArray[$key] === null) {
// simple attribs like 'required'
return $carry . ' ' . $key;
} else {
return $carry . ' ' . $key . '="' . e($this->attributesArray[$key]) . '"';
}
}, '');
// add a compiled list of styles
if (!empty($this->style)) {
$attribs_s .= 'style="'.trim(e(array_reduce(array_keys($this->style), function ($carry, $key) {
return $carry . $key . ': ' . $this->style[$key] . '; ';
if (!empty($this->styleArray)) {
$attribs_s .= 'style="'.trim(e(array_reduce(array_keys($this->styleArray), function ($carry, $key) {
return $carry . $key . ': ' . $this->styleArray[$key] . '; ';
}, ''))).'"';
}

@ -5,23 +5,48 @@ namespace App\View;
class WidgetFactory
{
private $labelCols = 3, $fieldCols = 8;
/** Configure layout used for future elements */
public function setLayout($labelCols, $fieldCols)
{
$this->labelCols = $labelCols;
$this->fieldCols = $fieldCols;
}
private function baseWidget($view, $name, $label)
{
return (new Widget($view, $name, $label))->layout($this->labelCols, $this->fieldCols);
}
public function text($name, $label)
{
return new Widget('input', $name, $label);
return $this->baseWidget('input', $name, $label);
}
public function password($name, $label)
{
return $this->baseWidget('input', $name, $label)->type('password');
}
public function number($name, $label)
{
return new Widget('number', $name, $label);
return $this->baseWidget('input', $name, $label)->type('number');
}
public function email($name, $label)
{
return new Widget('email', $name, $label);
return $this->baseWidget('input', $name, $label)->type('email');
}
public function checkbox($name, $label)
{
return (new CheckboxWidget('checkbox', $name, $label))
->layout($this->labelCols, $this->fieldCols);
}
public function textarea($name, $label)
{
return (new Widget('textarea', $name, $label))->minHeight('4em');
return $this->baseWidget('textarea', $name, $label)->minHeight('4em');
}
}

@ -17,7 +17,8 @@ class CreateUsersTable extends Migration
$table->increments('id');
$table->timestamps();
$table->string('name')->unique();
$table->string('email')->unique()->nullable();
$table->string('title')->index();
$table->string('email')->unique(); // would have to be nullable if we supported login via providers that don't give us e-mail
$table->string('password')->nullable();
$table->rememberToken();
});

@ -19,6 +19,7 @@ class CreateTablesTable extends Migration
$table->unsignedInteger('owner_id');
$table->unsignedInteger('ancestor_id')->index()->nullable();
$table->unsignedInteger('revision_id')->index(); // active revision
$table->string('name')->index(); // indexable
$table->string('title')->index(); // indexable
$table->text('description');
$table->text('license');

@ -11,7 +11,7 @@
@import "~bootstrap/scss/dropdown";
@import "~bootstrap/scss/button-group";
//@import "~bootstrap/scss/input-group";
//@import "~bootstrap/scss/custom-forms";
@import "~bootstrap/scss/custom-forms";
//@import "~bootstrap/scss/nav";
@import "~bootstrap/scss/navbar";
@import "~bootstrap/scss/card";

@ -11,6 +11,11 @@ html {
overflow-y: scroll;
}
.form-help {
display: inline-block;
margin-top: $form-text-margin-top;
}
.tooltip-inner {
text-align: left;
}

@ -1,102 +1,62 @@
@extends('layouts.app')
@section('content')
<div class="container">
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ __('Login') }}</div>
<div class="card-body">
<form method="POST" action="{{ route('login') }}" aria-label="{{ __('Login') }}">
@csrf
<div class="form-group row">
<label for="email" class="col-sm-4 col-form-label text-md-right">{{ __('E-Mail Address') }}</label>
<div class="col-md-6">
<input id="email" type="email" class="form-control{{ $errors->has('email') ? ' is-invalid' : '' }}" name="email" value="{{ old('email') }}" required autofocus>
@if ($errors->has('email'))
<span class="invalid-feedback" role="alert">
<strong>{{ $errors->first('email') }}</strong>
</span>
@endif
</div>
</div>
<div class="form-group row">
<label for="password" class="col-md-4 col-form-label text-md-right">{{ __('Password') }}</label>
<div class="col-md-6">
<input id="password" type="password" class="form-control{{ $errors->has('password') ? ' is-invalid' : '' }}" name="password" required>
@if ($errors->has('password'))
<span class="invalid-feedback" role="alert">
<strong>{{ $errors->first('password') }}</strong>
</span>
@endif
</div>
</div>
<div class="form-group row">
<div class="col-md-6 offset-md-4">
<div class="checkbox">
<label>
<input type="checkbox" name="remember" {{ old('remember') ? 'checked' : '' }}> {{ __('Remember Me') }}
</label>
</div>
</div>
</div>
<div class="form-group row mb-0">
<div class="col-md-8 offset-md-4">
<button type="submit" class="btn btn-primary">
{{ __('Login') }}
</button>{{--
--}}<a class="btn btn-link" href="{{ route('password.request') }}">
{{ __('Forgot Your Password?') }}
</a>{{--
--}}<a class="btn btn-link pl-0" href="{{ route('register') }}">
{{ __('Register') }}
</a>
</div>
</div>
</form>
</div>
<div class="card-footer bg-white">
<div class="form-group row mb-0">
<span class="col-md-2 col-form-label text-md-right">{{ __('Login with') }}</span>
<div class="col-md-8">
@set('services.oauth_providers.github.client_id')
<a type="submit" href="{{route('oauth-github-authorize')}}" class="btn btn-dark">
<i class="fa-github pr-1"></i>
{{ __('GitHub') }}
</a>
@endset
@set('services.oauth_providers.google.client_id')
<a type="submit" href="{{route('oauth-google-authorize')}}" class="btn btn-dark">
<i class="fa-google pr-1"></i>
{{ __('Google') }}
</a>
@endset
@set('services.oauth_providers.facebook.client_id')
<a type="submit" href="{{route('oauth-facebook-authorize')}}" class="btn btn-dark">
<i class="fa-facebook-square pr-1"></i>
{{ __('Facebook') }}
</a>
@endset
</div>
</div>
</div>
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ __('Login') }}</div>
<div class="card-body">
<form method="POST" action="{{ route('login') }}" aria-label="{{ __('Login') }}">
@csrf
@php(Widget::setLayout(4, 6))
{!! Widget::email('email', 'E-Mail Address')->required()->autofocus() !!}
{!! Widget::password('password', 'Password')->required() !!}
{!! Widget::checkbox('remember', 'Remember Me')->checked(false) !!}
<div class="form-group row mb-0">
<div class="col-md-8 offset-md-4">
<button type="submit" class="btn btn-primary">{{ __('Login') }}</button>{{--
--}}<a class="btn btn-link" href="{{ route('password.request') }}">{{ __('Forgot Your Password?') }}</a>{{--
--}}<a class="btn btn-link pl-0" href="{{ route('register') }}">{{ __('Register') }}</a>
</div>
</div>
</form>
</div>
<div class="card-footer bg-white">
<div class="form-group row mb-0">
<span class="col-md-4 col-form-label text-md-right text-muted">…or authenticate with</span>
<div class="col-md-6">
@set('services.oauth_providers.github.client_id')
<a type="submit" href="{{route('oauth-github-authorize')}}" class="btn btn-dark">
<i class="fa-github pr-1"></i>
{{ __('GitHub') }}
</a>
@endset
@set('services.oauth_providers.google.client_id')
<a type="submit" href="{{route('oauth-google-authorize')}}" class="btn btn-dark">
<i class="fa-google pr-1"></i>
{{ __('Google') }}
</a>
@endset
@set('services.oauth_providers.facebook.client_id')
<a type="submit" href="{{route('oauth-facebook-authorize')}}" class="btn btn-dark">
<i class="fa-facebook-square pr-1"></i>
{{ __('Facebook') }}
</a>
@endset
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
@endsection

@ -11,55 +11,16 @@
<form method="POST" action="{{ route('register') }}" aria-label="{{ __('Register') }}">
@csrf
<div class="form-group row">
<label for="name" class="col-md-4 col-form-label text-md-right">{{ __('Name') }}</label>
@php(Widget::setLayout(4, 6))
<div class="col-md-6">
<input id="name" type="text" class="form-control{{ $errors->has('name') ? ' is-invalid' : '' }}" name="name" value="{{ old('name') }}" required autofocus>
{!! Widget::text('name', 'Name')->required()->autofocus()
->help('This will be part of your vanity URL; only letters, digits and
some symbols are allowed. You can always change this later, and even
set a different Display Name.') !!}
@if ($errors->has('name'))
<span class="invalid-feedback" role="alert">
<strong>{{ $errors->first('name') }}</strong>
</span>
@endif
</div>
</div>
<div class="form-group row">
<label for="email" class="col-md-4 col-form-label text-md-right">{{ __('E-Mail Address') }}</label>
<div class="col-md-6">
<input id="email" type="email" class="form-control{{ $errors->has('email') ? ' is-invalid' : '' }}" name="email" value="{{ old('email') }}" required>
@if ($errors->has('email'))
<span class="invalid-feedback" role="alert">
<strong>{{ $errors->first('email') }}</strong>
</span>
@endif
</div>
</div>
<div class="form-group row">
<label for="password" class="col-md-4 col-form-label text-md-right">{{ __('Password') }}</label>
<div class="col-md-6">
<input id="password" type="password" class="form-control{{ $errors->has('password') ? ' is-invalid' : '' }}" name="password" required>
@if ($errors->has('password'))
<span class="invalid-feedback" role="alert">
<strong>{{ $errors->first('password') }}</strong>
</span>
@endif
</div>
</div>
<div class="form-group row">
<label for="password-confirm" class="col-md-4 col-form-label text-md-right">{{ __('Confirm Password') }}</label>
<div class="col-md-6">
<input id="password-confirm" type="password" class="form-control" name="password_confirmation" required>
</div>
</div>
{!! Widget::email('email', 'E-Mail Address')->required()->autofocus() !!}
{!! Widget::password('password', 'Password')->required() !!}
{!! Widget::password('password_confirmation', 'Confirm Password')->required() !!}
<div class="form-group row mb-0">
<div class="col-md-6 offset-md-4">

@ -1,6 +1,6 @@
<div class="col-md-1 pl-0">
@if($w->help)
<i class="fa-question-circle"
<i class="fa-question-circle form-help"
data-toggle="tooltip"
data-placement="right"
@if(false!==strpos($w->help, '<'))

@ -0,0 +1,22 @@
@php
/** @var \App\View\Widget $w */
@endphp
<div class="row form-group">
<div class="col-md-{{$w->fieldCols}} offset-md-{{$w->labelCols}}">
<div class="custom-control custom-checkbox{{ $errors->has($w->name) ? ' is-invalid' : '' }}">
<input type="checkbox" class="custom-control-input"
id="field-{{ $w->name }}"
{{+$w->value?'checked':''}}
name="{{ $w->name }}">
<label class="custom-control-label" for="field-{{ $w->name }}">{{ $w->label }}</label>
</div>
@if ($errors->has($w->name))
<span class="invalid-feedback" role="alert">
<strong>{{ $errors->first($w->name) }}</strong>
</span>
@endif
</div>
@include('form._help')
</div>

@ -3,8 +3,8 @@
@endphp
<div class="row form-group">
<label for="field-{{ $w->name }}" class="col-md-3 col-form-label text-md-right">{{ $w->label }}</label>
<div class="col-md-8 pl-0">
<label for="field-{{ $w->name }}" class="col-md-{{$w->labelCols}} col-form-label text-md-right">{{ $w->label }}</label>
<div class="col-md-{{$w->fieldCols}}">
<input id="field-{{ $w->name }}"
name="{{ $w->name }}"
class="form-control{{ $errors->has($w->name) ? ' is-invalid' : '' }}"

@ -3,8 +3,8 @@
@endphp
<div class="row form-group">
<label for="field-{{ $w->name }}" class="col-md-3 col-form-label text-md-right">{{ $w->label }}</label>
<div class="col-md-8 pl-0">
<label for="field-{{ $w->name }}" class="col-md-{{$w->labelCols}} col-form-label text-md-right">{{ $w->label }}</label>
<div class="col-md-{{$w->fieldCols}}">
<textarea id="field-{{ $w->name }}"
name="{{ $w->name }}"
class="form-control{{ $errors->has($w->name) ? ' is-invalid' : '' }}"

@ -2,14 +2,21 @@
@section('content')
<div class="container">
<form method="POST" action="{{route('table.storeNew')}}" class="row justify-content-center">
<form method="POST" action="{{route('table.storeNew')}}" class="row justify-content-center"
aria-label="New Table">
<h2>New Table</h2>
@csrf
<div class="col-md-8">
{!! Widget::text('table-name', 'Title')->help('Unique among your tables') !!}
{!! Widget::text('name', 'Name')->value('molluscs-'.uniqid())
->help('Unique among your tables, and part of the URL; only letters, digits and
some symbols are allowed.') !!}
{!! Widget::text('title', 'Title')
->help('Unique among your tables') !!}
{!! Widget::textarea('table-descr', 'Description')->height('8em')
{!! Widget::textarea('description', 'Description')->height('8em')
->help('Description what data is in the table. Please use the dedicated
fields for License and data source. URLs in a full format will be clickable.') !!}
@ -21,24 +28,24 @@
->help('If you took the data from some external site, a book, etc., write it here.
URLs in a full format will be clickable.') !!}
{!! Widget::textarea('col-spec', 'Columns')->value($exampleColSpec)->height('8em')
{!! Widget::textarea('columns', 'Columns')->value($exampleColumns)->height('8em')
->help('
<div class="text-left">
Column parameters in CSV format:
<ul class="pl-3 mb-0">
<li><b>column identifier</b><br>letters, numbers, underscore
<li><b>column data type</b><br>int, string, float, bool
<li><b>column title</b><br>used for display (optional)
</ul>
Column parameters in CSV format:
<ul class="pl-3 mb-0">
<li><b>column identifier</b><br>letters, numbers, underscore
<li><b>column data type</b><br>int, string, float, bool
<li><b>column title</b><br>used for display (optional)
</ul>
</div>') !!}
{!! Widget::textarea('row-data', 'Initial data')->value($exampleData)->height('12em')
{!! Widget::textarea('data', 'Initial data')->value($exampleData)->height('12em')
->help('
Initial table data in CSV format, columns corresponding to the
specification you entered above.') !!}
<div class="row form-group">
<div class="col-md-8 offset-md-3 pl-0">
<div class="col-md-8 offset-md-3">
<button type="submit" class="btn btn-primary">
<i class="fa-save pr-2"></i>Create New Table
</button>

Loading…
Cancel
Save