mirror of
https://github.com/prurigro/hypothetical.git
synced 2024-11-21 15:42:31 -05:00
Add support for linking dashboard tables, adding lists connected to other tables, formatted currency inputs, multi-line text fields without markdown, paginated edit lists (with working search), errors for columns not being unique or required (with modal popup), improve comments, and use links instead of javascript for the edit and new buttons
This commit is contained in:
parent
bf1b3ea9aa
commit
7f9f9bce1b
14 changed files with 893 additions and 96 deletions
|
@ -52,17 +52,23 @@ class DashboardController extends Controller {
|
||||||
$model_class = Dashboard::getModel($model, 'edit');
|
$model_class = Dashboard::getModel($model, 'edit');
|
||||||
|
|
||||||
if ($model_class != null) {
|
if ($model_class != null) {
|
||||||
|
$data = $model_class::getDashboardData(true);
|
||||||
|
|
||||||
return view('dashboard.pages.edit-list', [
|
return view('dashboard.pages.edit-list', [
|
||||||
'heading' => $model_class::getDashboardHeading($model),
|
'heading' => $model_class::getDashboardHeading($model),
|
||||||
'model' => $model,
|
'model' => $model,
|
||||||
'rows' => $model_class::getDashboardData(),
|
'rows' => $data['rows'],
|
||||||
'display' => $model_class::$dashboard_display,
|
'paramdisplay' => $data['paramdisplay'],
|
||||||
'button' => $model_class::$dashboard_button,
|
'query' => $model_class::getQueryString(),
|
||||||
'sortcol' => $model_class::$dashboard_reorder ? $model_class::$dashboard_sort_column : false,
|
'display' => $model_class::$dashboard_display,
|
||||||
'create' => $model_class::$create,
|
'button' => $model_class::$dashboard_button,
|
||||||
'delete' => $model_class::$delete,
|
'idlink' => $model_class::$dashboard_id_link,
|
||||||
'filter' => $model_class::$filter,
|
'sortcol' => $model_class::$dashboard_reorder ? $model_class::$dashboard_sort_column : false,
|
||||||
'export' => $model_class::$export
|
'paginate' => $model_class::$items_per_page !== 0,
|
||||||
|
'create' => $model_class::$create,
|
||||||
|
'delete' => $model_class::$delete,
|
||||||
|
'filter' => $model_class::$filter,
|
||||||
|
'export' => $model_class::$export
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
abort(404);
|
abort(404);
|
||||||
|
@ -81,6 +87,13 @@ class DashboardController extends Controller {
|
||||||
if ($model_class::where('id', $id)->exists()) {
|
if ($model_class::where('id', $id)->exists()) {
|
||||||
$item = $model_class::find($id);
|
$item = $model_class::find($id);
|
||||||
|
|
||||||
|
foreach ($model_class::$dashboard_columns as $column) {
|
||||||
|
if ($column['type'] === 'list') {
|
||||||
|
$list_model_class = 'App\\Models\\' . $column['model'];
|
||||||
|
$item->{$column['name']} = $list_model_class::where($column['foreign'], $item->id)->orderBy($column['sort'])->get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (is_null($item) || !$item->userCheck()) {
|
if (is_null($item) || !$item->userCheck()) {
|
||||||
return view('errors.no-such-record');
|
return view('errors.no-such-record');
|
||||||
}
|
}
|
||||||
|
@ -181,12 +194,83 @@ class DashboardController extends Controller {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// populate the eloquent object with the remaining items in $request
|
// check to ensure required columns have values
|
||||||
foreach ($request['columns'] as $column) {
|
$empty = [];
|
||||||
$item->$column = $request[$column];
|
|
||||||
|
foreach ($model_class::$dashboard_columns as $column) {
|
||||||
|
if ($request->has($column['name']) && array_key_exists('required', $column) && $column['required'] && ($request[$column['name']] == '' || $request[$column['name']] == null)) {
|
||||||
|
if (array_key_exists('title', $column)) {
|
||||||
|
array_push($empty, "'" . $column['title'] . "'");
|
||||||
|
} else {
|
||||||
|
array_push($empty, "'" . ucfirst($column['name']) . "'");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// save the new or updated item
|
if (count($empty) > 0) {
|
||||||
|
return 'required:' . implode(',', $empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check to ensure unique columns are unique
|
||||||
|
$not_unique = [];
|
||||||
|
|
||||||
|
foreach ($model_class::$dashboard_columns as $column) {
|
||||||
|
if ($request->has($column['name']) && array_key_exists('unique', $column) && $column['unique'] && $model_class::where($column['name'], $request[$column['name']])->where('id', '!=', $item->id)->exists()) {
|
||||||
|
if (array_key_exists('title', $column)) {
|
||||||
|
array_push($not_unique, "'" . $column['title'] . "'");
|
||||||
|
} else {
|
||||||
|
array_push($not_unique, "'" . ucfirst($column['name']) . "'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($not_unique) > 0) {
|
||||||
|
return 'not-unique:' . implode(',', $not_unique);
|
||||||
|
}
|
||||||
|
|
||||||
|
// populate the eloquent object with the non-list items in $request
|
||||||
|
foreach ($request['columns'] as $column) {
|
||||||
|
if ($column['type'] !== 'list') {
|
||||||
|
$column_name = $column['name'];
|
||||||
|
$item->$column_name = $request[$column_name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// save the item if it's new so we can access its id
|
||||||
|
if ($request['id'] == 'new') {
|
||||||
|
$item->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
// populate connected lists with list items in $request
|
||||||
|
foreach ($request['columns'] as $column) {
|
||||||
|
if ($column['type'] === 'list') {
|
||||||
|
$column_name = $column['name'];
|
||||||
|
|
||||||
|
foreach ($model_class::$dashboard_columns as $dashboard_column) {
|
||||||
|
if ($dashboard_column['name'] === $column_name) {
|
||||||
|
$foreign = $dashboard_column['foreign'];
|
||||||
|
$list_model_class = 'App\\Models\\' . $dashboard_column['model'];
|
||||||
|
$list_model_class::where($foreign, $item->id)->delete();
|
||||||
|
|
||||||
|
if ($request->has($column_name)) {
|
||||||
|
foreach ($request[$column_name] as $index => $row) {
|
||||||
|
$list_model_item = new $list_model_class;
|
||||||
|
$list_model_item->$foreign = $item->id;
|
||||||
|
$list_model_item->{$dashboard_column['sort']} = $index;
|
||||||
|
|
||||||
|
foreach ($row as $key => $value) {
|
||||||
|
$list_model_item->$key = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
$list_model_item->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// update the item
|
||||||
$item->save();
|
$item->save();
|
||||||
|
|
||||||
// return the id number in the format '^id:[0-9][0-9]*$' on success
|
// return the id number in the format '^id:[0-9][0-9]*$' on success
|
||||||
|
|
|
@ -9,6 +9,8 @@ class Blog extends DashboardModel
|
||||||
{
|
{
|
||||||
protected $table = 'blog';
|
protected $table = 'blog';
|
||||||
|
|
||||||
|
public static $items_per_page = 10;
|
||||||
|
|
||||||
public static $dashboard_type = 'edit';
|
public static $dashboard_type = 'edit';
|
||||||
|
|
||||||
public static $dashboard_help_text = '<strong>NOTE</strong>: Tags should be separated by semicolons';
|
public static $dashboard_help_text = '<strong>NOTE</strong>: Tags should be separated by semicolons';
|
||||||
|
@ -18,10 +20,10 @@ class Blog extends DashboardModel
|
||||||
public static $dashboard_columns = [
|
public static $dashboard_columns = [
|
||||||
[ 'name' => 'user_id', 'type' => 'user' ],
|
[ 'name' => 'user_id', 'type' => 'user' ],
|
||||||
[ 'name' => 'created_at', 'title' => 'Date', 'type' => 'display' ],
|
[ 'name' => 'created_at', 'title' => 'Date', 'type' => 'display' ],
|
||||||
[ 'name' => 'title', 'type' => 'text' ],
|
[ 'name' => 'title', 'required' => true, 'unique' => true, 'type' => 'string' ],
|
||||||
[ 'name' => 'body', 'type' => 'mkd' ],
|
[ 'name' => 'body', 'required' => true, 'type' => 'mkd' ],
|
||||||
[ 'name' => 'tags', 'type' => 'text' ],
|
[ 'name' => 'header-image', 'title' => 'Header Image', 'type' => 'image', 'delete' => true ],
|
||||||
[ 'name' => 'header-image', 'title' => 'Header Image', 'type' => 'image', 'delete' => true ]
|
[ 'name' => 'tags', 'type' => 'list', 'model' => 'BlogTags', 'columns' => [ 'name' ], 'foreign' => 'blog_id', 'sort' => 'order' ]
|
||||||
];
|
];
|
||||||
|
|
||||||
public static function getBlogEntries()
|
public static function getBlogEntries()
|
||||||
|
|
14
app/Models/BlogTags.php
Normal file
14
app/Models/BlogTags.php
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<?php namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class BlogTags extends Model {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The database table used by the model.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $table = 'blog_tags';
|
||||||
|
|
||||||
|
}
|
|
@ -52,6 +52,20 @@ class DashboardModel extends Model
|
||||||
*/
|
*/
|
||||||
public static $filter = true;
|
public static $filter = true;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Number of items per page (0 for unlimited)
|
||||||
|
*
|
||||||
|
* @var number
|
||||||
|
*/
|
||||||
|
public static $items_per_page = 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Query parameters to remember
|
||||||
|
*
|
||||||
|
* @var number
|
||||||
|
*/
|
||||||
|
public static $valid_query_params = [];
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Dashboard help text
|
* Dashboard help text
|
||||||
*
|
*
|
||||||
|
@ -88,12 +102,19 @@ class DashboardModel extends Model
|
||||||
public static $dashboard_sort_direction = 'desc';
|
public static $dashboard_sort_direction = 'desc';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The dashboard buttons
|
* The dashboard button
|
||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
public static $dashboard_button = [];
|
public static $dashboard_button = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The dashboard id link
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
public static $dashboard_id_link = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the dashboard heading
|
* Returns the dashboard heading
|
||||||
*
|
*
|
||||||
|
@ -130,15 +151,56 @@ class DashboardModel extends Model
|
||||||
return $column_data;
|
return $column_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a search against the columns in $dashboard_display
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function searchDisplay($term, $query = null)
|
||||||
|
{
|
||||||
|
if (static::$filter) {
|
||||||
|
$first = true;
|
||||||
|
|
||||||
|
if ($query === null) {
|
||||||
|
$query = self::orderBy(static::$dashboard_sort_column, static::$dashboard_sort_direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (static::$dashboard_display as $display) {
|
||||||
|
$type = '';
|
||||||
|
|
||||||
|
foreach (static::$dashboard_columns as $column) {
|
||||||
|
if ($column['name'] === $display) {
|
||||||
|
$type = $column['type'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($type !== '' && $type !== 'image') {
|
||||||
|
if ($first) {
|
||||||
|
$query->where($display, 'LIKE', '%' . $term . '%');
|
||||||
|
} else {
|
||||||
|
$query->orWhere($display, 'LIKE', '%' . $term . '%');
|
||||||
|
}
|
||||||
|
|
||||||
|
$first = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns data for the dashboard
|
* Returns data for the dashboard
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public static function getDashboardData()
|
public static function getDashboardData($include_param_display = false)
|
||||||
{
|
{
|
||||||
$sort_direction = static::$dashboard_reorder ? 'desc' : static::$dashboard_sort_direction;
|
$sort_direction = static::$dashboard_reorder ? 'desc' : static::$dashboard_sort_direction;
|
||||||
$query = self::orderBy(static::$dashboard_sort_column, $sort_direction);
|
$query = self::orderBy(static::$dashboard_sort_column, $sort_direction);
|
||||||
|
$query_param_display = [];
|
||||||
|
|
||||||
foreach (static::$dashboard_columns as $column) {
|
foreach (static::$dashboard_columns as $column) {
|
||||||
if (array_key_exists('type', $column) && $column['type'] == 'user') {
|
if (array_key_exists('type', $column) && $column['type'] == 'user') {
|
||||||
|
@ -147,7 +209,71 @@ class DashboardModel extends Model
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $query->get();
|
if (count(static::$valid_query_params) > 0) {
|
||||||
|
foreach (static::$valid_query_params as $param) {
|
||||||
|
if (request()->query($param['key'], null) != null) {
|
||||||
|
if ($include_param_display) {
|
||||||
|
$query_param_model = 'App\\Models\\' . $param['model'];
|
||||||
|
$query_param_column = $query_param_model::find(request()->query($param['key']));
|
||||||
|
|
||||||
|
if ($query_param_column !== null) {
|
||||||
|
array_push($query_param_display, [
|
||||||
|
'title' => $param['title'],
|
||||||
|
'value' => $query_param_column[$param['display']]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$query->where($param['column'], request()->query($param['key']));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (static::$items_per_page === 0) {
|
||||||
|
$results = $query->get();
|
||||||
|
} else {
|
||||||
|
if (static::$filter && request()->query('search', null) != null) {
|
||||||
|
$query = static::searchDisplay(request()->query('search'), $query);
|
||||||
|
}
|
||||||
|
|
||||||
|
$results = $query->paginate(static::$items_per_page);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($include_param_display) {
|
||||||
|
return [
|
||||||
|
'rows' => $results,
|
||||||
|
'paramdisplay' => $query_param_display
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
return $results;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the current query string containing valid query parameters
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function getQueryString()
|
||||||
|
{
|
||||||
|
$valid_query_params = static::$valid_query_params;
|
||||||
|
$string = '';
|
||||||
|
|
||||||
|
if (static::$items_per_page !== 0 && static::$filter) {
|
||||||
|
array_push($valid_query_params, [ 'key' => 'search' ]);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($valid_query_params as $param) {
|
||||||
|
if (request()->query($param['key'], null) != null) {
|
||||||
|
if ($string !== '') {
|
||||||
|
$string .= '&';
|
||||||
|
}
|
||||||
|
|
||||||
|
$string .= $param['key'] . '=' . request()->query($param['key']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -19,7 +19,6 @@ class AddBlogTable extends Migration
|
||||||
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
|
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
|
||||||
$table->string('title')->nullable();
|
$table->string('title')->nullable();
|
||||||
$table->text('body')->nullable();
|
$table->text('body')->nullable();
|
||||||
$table->text('tags')->nullable();
|
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
class AddBlogTagsTable extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::create('blog_tags', function(Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->bigInteger('blog_id')->unsigned();
|
||||||
|
$table->foreign('blog_id')->references('id')->on('blog')->onDelete('cascade');
|
||||||
|
$table->string('name')->nullable();
|
||||||
|
$table->integer('order')->default(0);
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::drop('blog_tags');
|
||||||
|
}
|
||||||
|
}
|
3
gulpfile.js
vendored
3
gulpfile.js
vendored
|
@ -63,7 +63,8 @@ const jsDashboardLibs = [
|
||||||
"node_modules/flatpickr/dist/flatpickr.js",
|
"node_modules/flatpickr/dist/flatpickr.js",
|
||||||
"node_modules/sortablejs/Sortable.js",
|
"node_modules/sortablejs/Sortable.js",
|
||||||
"node_modules/list.js/dist/list.js",
|
"node_modules/list.js/dist/list.js",
|
||||||
"node_modules/easymde/dist/easymde.min.js"
|
"node_modules/easymde/dist/easymde.min.js",
|
||||||
|
"node_modules/autonumeric/dist/autoNumeric.js"
|
||||||
];
|
];
|
||||||
|
|
||||||
// CSS libraries for the dashboard
|
// CSS libraries for the dashboard
|
||||||
|
|
5
package-lock.json
generated
5
package-lock.json
generated
|
@ -1232,6 +1232,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
|
||||||
"integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg=="
|
"integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg=="
|
||||||
},
|
},
|
||||||
|
"autonumeric": {
|
||||||
|
"version": "4.5.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/autonumeric/-/autonumeric-4.5.13.tgz",
|
||||||
|
"integrity": "sha512-GHpM0cGWTzFVDQyOgpXlNoqe8fDn6minpGEauLgDnGb4k24WmF8GInLnhIGWoJXisHZuWmTeKqFcIBnf5c2DaA=="
|
||||||
|
},
|
||||||
"autoprefixer": {
|
"autoprefixer": {
|
||||||
"version": "9.7.6",
|
"version": "9.7.6",
|
||||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.7.6.tgz",
|
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.7.6.tgz",
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
"@babel/core": "^7.9.0",
|
"@babel/core": "^7.9.0",
|
||||||
"@babel/preset-env": "^7.9.5",
|
"@babel/preset-env": "^7.9.5",
|
||||||
"@fortawesome/fontawesome-free": "^5.13.0",
|
"@fortawesome/fontawesome-free": "^5.13.0",
|
||||||
|
"autonumeric": "^4.5.13",
|
||||||
"autoprefixer": "^9.7.6",
|
"autoprefixer": "^9.7.6",
|
||||||
"babelify": "^10.0.0",
|
"babelify": "^10.0.0",
|
||||||
"bootstrap": "4.4.1",
|
"bootstrap": "4.4.1",
|
||||||
|
|
28
readme.md
28
readme.md
|
@ -179,12 +179,15 @@ These are variables that only function when the `$dashboard_type` variable is se
|
||||||
* `$dashboard_reorder`: A boolean determining whether to render drag handles to reorder the items in the list
|
* `$dashboard_reorder`: A boolean determining whether to render drag handles to reorder the items in the list
|
||||||
* `$dashboard_sort_column`: A string containing the column used to sort the list (this column should be an `integer` when `$dashboard_reorder` is true)
|
* `$dashboard_sort_column`: A string containing the column used to sort the list (this column should be an `integer` when `$dashboard_reorder` is true)
|
||||||
* `$dashboard_sort_direction`: When `$dashboard_reorder` is false this determines the sort direction (this can be `desc` for descending or `asc` ascending)
|
* `$dashboard_sort_direction`: When `$dashboard_reorder` is false this determines the sort direction (this can be `desc` for descending or `asc` ascending)
|
||||||
* `$dashboard_button`: An array containing the following items in this order:
|
* `$dashboard_button`: Add a dashboard button with custom functionality by populating an array containing the following items in this order:
|
||||||
* The title
|
* The title
|
||||||
* Confirmation text asking the user to confirm
|
* Confirmation text asking the user to confirm
|
||||||
* A "success" message to display when the response is `success`
|
* A "success" message to display when the response is `success`
|
||||||
* A "failure" message to display when the response is not `success`
|
* A "failure" message to display when the response is not `success`
|
||||||
* The URL to send the POST request to with the respective `id` in the request variable
|
* The URL to send the POST request to with the respective `id` in the request variable
|
||||||
|
* `$dashboard_id_link`: Add a dashboard button linking to another list filtered by the current item by populating an array containing the following items in this order:
|
||||||
|
* The title
|
||||||
|
* The URL to link to where the id will come after the rest
|
||||||
|
|
||||||
##### Configuring the columns
|
##### Configuring the columns
|
||||||
|
|
||||||
|
@ -198,15 +201,20 @@ All models use the following attributes:
|
||||||
Models with their `$dashboard_type` set to `edit` also use:
|
Models with their `$dashboard_type` set to `edit` also use:
|
||||||
|
|
||||||
* `type`: The column type which can be any of the following:
|
* `type`: The column type which can be any of the following:
|
||||||
* `text`: Text input field for text data
|
|
||||||
* `mkd`: Markdown editor for text data containing markdown
|
|
||||||
* `date`: Date and time selection tool for date/time data
|
|
||||||
* `select`: Text input via option select
|
|
||||||
* `hidden`: Fields that will contain values to pass to the update function but won't appear on the page (this must be used for the sort column)
|
* `hidden`: Fields that will contain values to pass to the update function but won't appear on the page (this must be used for the sort column)
|
||||||
|
* `user`: This should point to a foreign key that references the id on the users table; setting this will bind items to the user that created them
|
||||||
|
* `string`: Single-line text input field
|
||||||
|
* `text`: Multi-line text input field
|
||||||
|
* `currency`: Text input field for currency data
|
||||||
|
* `date`: Date and time selection tool for date/time data
|
||||||
|
* `mkd`: Multi-line text input field with a markdown editor
|
||||||
|
* `select`: Text input via option select
|
||||||
|
* `list`: One or more items saved to a connected table
|
||||||
* `image`: Fields that contain image uploads
|
* `image`: Fields that contain image uploads
|
||||||
* `file`: Fields that contains file uploads
|
* `file`: Fields that contains file uploads
|
||||||
* `display`: Displayed information that can't be edited
|
* `display`: Displayed information that can't be edited
|
||||||
* `user`: This should point to a foreign key that references the id on the users table; setting this will bind items to the user that created them
|
* `required`: If set an error will be displayed if the field has no value
|
||||||
|
* `unique`: If set an error will be displayed if another row in the table has the same value for a given column
|
||||||
* `type-new`: This takes the same options as `type` and overrides it when creating new items (eg: to allow input on a field during creation but not after)
|
* `type-new`: This takes the same options as `type` and overrides it when creating new items (eg: to allow input on a field during creation but not after)
|
||||||
* `options` (required by `select`) Takes an array of options that are either strings or arrays containing the keys `title` (for what will display with the option) and `value` (for what will be recorded)
|
* `options` (required by `select`) Takes an array of options that are either strings or arrays containing the keys `title` (for what will display with the option) and `value` (for what will be recorded)
|
||||||
* `name`: (required by `file` and `image`) Used along with the record id to determine the filename
|
* `name`: (required by `file` and `image`) Used along with the record id to determine the filename
|
||||||
|
@ -229,9 +237,9 @@ An example of the `$dashboard_columns` array in a model with its `$dashboard_typ
|
||||||
public static $dashboard_columns = [
|
public static $dashboard_columns = [
|
||||||
[ 'name' => 'user_id', 'type' => 'user' ],
|
[ 'name' => 'user_id', 'type' => 'user' ],
|
||||||
[ 'name' => 'created_at', 'title' => 'Date', 'type' => 'display' ],
|
[ 'name' => 'created_at', 'title' => 'Date', 'type' => 'display' ],
|
||||||
[ 'name' => 'title', 'type' => 'text' ],
|
[ 'name' => 'title', 'required' => true, 'unique' => true, 'type' => 'string' ],
|
||||||
[ 'name' => 'body', 'type' => 'mkd' ],
|
[ 'name' => 'body', 'required' => true, 'type' => 'mkd' ],
|
||||||
[ 'name' => 'tags', 'type' => 'text' ],
|
[ 'name' => 'header-image', 'title' => 'Header Image', 'type' => 'image', 'delete' => true ],
|
||||||
[ 'name' => 'header-image', 'title' => 'Header Image', 'type' => 'image', 'delete' => true ]
|
[ 'name' => 'tags', 'type' => 'list', 'model' => 'BlogTags', 'columns' => [ 'name' ], 'foreign' => 'blog_id', 'sort' => 'order' ]
|
||||||
];
|
];
|
||||||
```
|
```
|
||||||
|
|
180
resources/js/dashboard.js
vendored
180
resources/js/dashboard.js
vendored
|
@ -135,7 +135,7 @@ function showAlert(message, command) {
|
||||||
$acceptButton.on("click", closeAlertModal);
|
$acceptButton.on("click", closeAlertModal);
|
||||||
|
|
||||||
// set the message with the supplied message
|
// set the message with the supplied message
|
||||||
$message.text(message);
|
$message.html(message);
|
||||||
|
|
||||||
// show the alert modal
|
// show the alert modal
|
||||||
$alertModal.css({
|
$alertModal.css({
|
||||||
|
@ -151,15 +151,6 @@ function editListInit() {
|
||||||
$token = $("#token"),
|
$token = $("#token"),
|
||||||
model = $editList.data("model");
|
model = $editList.data("model");
|
||||||
|
|
||||||
// initialize new button functionality
|
|
||||||
const newButtonInit = function() {
|
|
||||||
const $newButton = $(".btn.new-button");
|
|
||||||
|
|
||||||
$newButton.on("click", function() {
|
|
||||||
window.location.href = "/dashboard/edit/" + model + "/new";
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// initialize delete button functionality
|
// initialize delete button functionality
|
||||||
const deleteButtonInit = function() {
|
const deleteButtonInit = function() {
|
||||||
const $deleteButtons = $(".btn.delete-button");
|
const $deleteButtons = $(".btn.delete-button");
|
||||||
|
@ -282,11 +273,32 @@ function editListInit() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
newButtonInit();
|
// initialize search functionality if the search-form element exists
|
||||||
|
const searchFormInit = function() {
|
||||||
|
const $form = $("#search-form");
|
||||||
|
|
||||||
|
if ($form.length) {
|
||||||
|
$form.on("submit", function(e) {
|
||||||
|
const term = $form.find(".search").val();
|
||||||
|
|
||||||
|
let url = $form.data("url");
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (term !== "") {
|
||||||
|
url += `?search=${term}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.location.href = url;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
deleteButtonInit();
|
deleteButtonInit();
|
||||||
actionButtonInit();
|
actionButtonInit();
|
||||||
sortRowInit();
|
sortRowInit();
|
||||||
filterInputInit();
|
filterInputInit();
|
||||||
|
searchFormInit();
|
||||||
}
|
}
|
||||||
|
|
||||||
// initialize edit item functionality
|
// initialize edit item functionality
|
||||||
|
@ -295,10 +307,12 @@ function editItemInit() {
|
||||||
$submit = $("#submit"),
|
$submit = $("#submit"),
|
||||||
$backButton = $("#back"),
|
$backButton = $("#back"),
|
||||||
$textInputs = $(".text-input"),
|
$textInputs = $(".text-input"),
|
||||||
|
$currencyInputs = $(".currency-input"),
|
||||||
$datePickers = $(".date-picker"),
|
$datePickers = $(".date-picker"),
|
||||||
$mkdEditors = $(".mkd-editor"),
|
$mkdEditors = $(".mkd-editor"),
|
||||||
$fileUploads = $(".file-upload"),
|
$fileUploads = $(".file-upload"),
|
||||||
$imgUploads = $(".image-upload"),
|
$imgUploads = $(".image-upload"),
|
||||||
|
$lists = $(".list"),
|
||||||
$token = $("#token"),
|
$token = $("#token"),
|
||||||
model = $editItem.data("model"),
|
model = $editItem.data("model"),
|
||||||
id = $editItem.data("id"),
|
id = $editItem.data("id"),
|
||||||
|
@ -315,12 +329,12 @@ function editItemInit() {
|
||||||
// fill the formData object with data from all the form fields
|
// fill the formData object with data from all the form fields
|
||||||
const getFormData = function() {
|
const getFormData = function() {
|
||||||
// function to add a column and value to the formData object
|
// function to add a column and value to the formData object
|
||||||
const addFormData = function(column, value) {
|
const addFormData = function(type, column, value) {
|
||||||
// add the value to a key with the column name
|
// add the value to a key with the column name
|
||||||
formData[column] = value;
|
formData[column] = value;
|
||||||
|
|
||||||
// add the column to the array of columns
|
// add the column to the array of columns
|
||||||
formData.columns.push(column);
|
formData.columns.push({ type: type, name: column });
|
||||||
};
|
};
|
||||||
|
|
||||||
// reset the formData object
|
// reset the formData object
|
||||||
|
@ -334,31 +348,63 @@ function editItemInit() {
|
||||||
// create an empty array to contain the list of columns
|
// create an empty array to contain the list of columns
|
||||||
formData.columns = [];
|
formData.columns = [];
|
||||||
|
|
||||||
// add values from the contents of text-input class elements
|
// add values from the contents of text-input elements
|
||||||
$textInputs.each(function() {
|
$textInputs.each(function() {
|
||||||
const $this = $(this),
|
const $this = $(this),
|
||||||
column = $this.attr("id"),
|
column = $this.attr("id"),
|
||||||
value = $this.val();
|
value = $this.val();
|
||||||
|
|
||||||
addFormData(column, value);
|
addFormData("text", column, value);
|
||||||
});
|
});
|
||||||
|
|
||||||
// add values from the contents of date-picker class elements
|
// add values from the contents of date-picker elements
|
||||||
$datePickers.each(function() {
|
$datePickers.each(function() {
|
||||||
const $this = $(this),
|
const $this = $(this),
|
||||||
column = $this.attr("id"),
|
column = $this.attr("id"),
|
||||||
value = $this.val();
|
value = $this.val();
|
||||||
|
|
||||||
addFormData(column, value);
|
addFormData("date", column, value);
|
||||||
});
|
});
|
||||||
|
|
||||||
// add values from the contents of the markdown editor for mkd-editor class elements
|
// add values from the contents of currency-input elements
|
||||||
|
$currencyInputs.each(function() {
|
||||||
|
const $this = $(this),
|
||||||
|
column = $this.attr("id"),
|
||||||
|
value = AutoNumeric.getNumericString(this);
|
||||||
|
|
||||||
|
addFormData("text", column, value);
|
||||||
|
});
|
||||||
|
|
||||||
|
// add values from the contents of the markdown editor for mkd-editor elements
|
||||||
$mkdEditors.each(function() {
|
$mkdEditors.each(function() {
|
||||||
const $this = $(this),
|
const $this = $(this),
|
||||||
column = $this.attr("id"),
|
column = $this.attr("id"),
|
||||||
value = easymde[column].value();
|
value = easymde[column].value();
|
||||||
|
|
||||||
addFormData(column, value);
|
addFormData("text", column, value);
|
||||||
|
});
|
||||||
|
|
||||||
|
// add values from list-items inputs
|
||||||
|
$lists.each(function() {
|
||||||
|
const $this = $(this),
|
||||||
|
column = $this.attr("id"),
|
||||||
|
value = [];
|
||||||
|
|
||||||
|
$this.find(".list-items .list-items-row").each(function(index, row) {
|
||||||
|
const rowData = {};
|
||||||
|
|
||||||
|
$(row).find(".list-items-row-input-inner").each(function(index, input) {
|
||||||
|
const $input = $(input),
|
||||||
|
column = $input.data("column"),
|
||||||
|
value = $input.val();
|
||||||
|
|
||||||
|
rowData[column] = value;
|
||||||
|
});
|
||||||
|
|
||||||
|
value.push(rowData);
|
||||||
|
});
|
||||||
|
|
||||||
|
addFormData("list", column, value);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -463,6 +509,7 @@ function editItemInit() {
|
||||||
$submit.removeClass("no-input");
|
$submit.removeClass("no-input");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// initialize image deletion
|
||||||
$(".edit-button.delete.image").on("click", function(e) {
|
$(".edit-button.delete.image").on("click", function(e) {
|
||||||
const $this = $(this),
|
const $this = $(this),
|
||||||
name = $this.data("name");
|
name = $this.data("name");
|
||||||
|
@ -497,6 +544,7 @@ function editItemInit() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// initialize file deletion
|
||||||
$(".edit-button.delete.file").on("click", function(e) {
|
$(".edit-button.delete.file").on("click", function(e) {
|
||||||
const $this = $(this),
|
const $this = $(this),
|
||||||
name = $this.data("name"),
|
name = $this.data("name"),
|
||||||
|
@ -533,21 +581,89 @@ function editItemInit() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// allow start time selection to start on the hour and every 15 minutes after
|
// initialize list item functionality
|
||||||
|
$lists.each(function(index, list) {
|
||||||
|
const $list = $(list),
|
||||||
|
$template = $list.find(".list-template"),
|
||||||
|
$items = $list.find(".list-items");
|
||||||
|
|
||||||
|
let sortable = undefined;
|
||||||
|
|
||||||
|
const initSort = function() {
|
||||||
|
if (typeof sortable !== "undefined") {
|
||||||
|
sortable.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
sortable = Sortable.create($items[0], {
|
||||||
|
handle: ".sort-icon",
|
||||||
|
onUpdate: contentChanged
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const initDelete = function() {
|
||||||
|
$items.find(".list-items-row").each(function(index, row) {
|
||||||
|
const $row = $(row);
|
||||||
|
|
||||||
|
// initialize delete button functionality
|
||||||
|
$row.find(".list-items-row-button").off("click").on("click", function() {
|
||||||
|
$row.remove();
|
||||||
|
initSort();
|
||||||
|
contentChanged();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const initList = function() {
|
||||||
|
$list.find(".list-data-row").each(function(rowIndex, row) {
|
||||||
|
// Add the values from the current data row to the template
|
||||||
|
$(row).find(".list-data-row-item").each(function(itemIndex, item) {
|
||||||
|
const $item = $(item),
|
||||||
|
column = $item.data("column"),
|
||||||
|
value = $item.data("value");
|
||||||
|
|
||||||
|
$template.find(".list-items-row-input-inner").each(function(inputIndex, input) {
|
||||||
|
const $input = $(input);
|
||||||
|
|
||||||
|
if ($input.data("column") === column) {
|
||||||
|
$input.val(value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add the populated template to the list of items then clear the template values
|
||||||
|
$template.find(".list-items-row").clone().appendTo($items);
|
||||||
|
$template.find(".list-items-row-input-inner").val("");
|
||||||
|
});
|
||||||
|
|
||||||
|
initSort();
|
||||||
|
initDelete();
|
||||||
|
};
|
||||||
|
|
||||||
|
$list.find(".list-add-button").on("click", function() {
|
||||||
|
$template.find(".list-items-row").clone().appendTo($items);
|
||||||
|
initDelete();
|
||||||
|
initSort();
|
||||||
|
contentChanged();
|
||||||
|
});
|
||||||
|
|
||||||
|
initList();
|
||||||
|
});
|
||||||
|
|
||||||
|
// allow the date picker start time selection to start on the hour and every 15 minutes after
|
||||||
for (hours = 0; hours <= 23; hours++) {
|
for (hours = 0; hours <= 23; hours++) {
|
||||||
for (minutes = 0; minutes <= 3; minutes++) {
|
for (minutes = 0; minutes <= 3; minutes++) {
|
||||||
allowTimes.push(hours + ":" + (minutes === 0 ? "00" : minutes * 15));
|
allowTimes.push(hours + ":" + (minutes === 0 ? "00" : minutes * 15));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// enable the datepicker for each element with the date-picker class
|
// enable the datepicker for date-picker elements
|
||||||
$datePickers.each(function() {
|
$datePickers.each(function() {
|
||||||
$(this).flatpickr({
|
$(this).flatpickr({
|
||||||
enableTime: true
|
enableTime: true
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// enable the markdown editor for each element with the mkd-editor class
|
// enable the markdown editor for mkd-editor elements
|
||||||
$mkdEditors.each(function() {
|
$mkdEditors.each(function() {
|
||||||
const $this = $(this),
|
const $this = $(this),
|
||||||
column = $this.attr("id");
|
column = $this.attr("id");
|
||||||
|
@ -583,6 +699,14 @@ function editItemInit() {
|
||||||
}, 500);
|
}, 500);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// enable currency formatting for currency-input elements
|
||||||
|
new AutoNumeric.multiple($currencyInputs.toArray(), {
|
||||||
|
currencySymbol: "$",
|
||||||
|
rawValueDivisor: 0.01,
|
||||||
|
allowDecimalPadding: false,
|
||||||
|
modifyValueOnWheel: false
|
||||||
|
});
|
||||||
|
|
||||||
// watch for changes to input and select element contents
|
// watch for changes to input and select element contents
|
||||||
$editItem.find("input, select").on("input change", contentChanged);
|
$editItem.find("input, select").on("input change", contentChanged);
|
||||||
|
|
||||||
|
@ -617,12 +741,22 @@ function editItemInit() {
|
||||||
url: "/dashboard/update",
|
url: "/dashboard/update",
|
||||||
data: formData
|
data: formData
|
||||||
}).always(function(response) {
|
}).always(function(response) {
|
||||||
|
let message = "";
|
||||||
|
|
||||||
if ((/^id:[0-9][0-9]*$/).test(response)) {
|
if ((/^id:[0-9][0-9]*$/).test(response)) {
|
||||||
uploadImage(response.replace(/^id:/, ""), 0);
|
uploadImage(response.replace(/^id:/, ""), 0);
|
||||||
} else {
|
} else {
|
||||||
loadingModal("hide");
|
loadingModal("hide");
|
||||||
|
|
||||||
showAlert("Failed to " + operation + " record", function() {
|
if ((/^not-unique:/).test(response)) {
|
||||||
|
message = `<strong>${response.replace(/'/g, "").replace(/^[^:]*:/, "").replace(/,([^,]*)$/, "</strong> and <strong>$1").replace(/,/g, "</strong>, <strong>")}</strong> must be unique`;
|
||||||
|
} else if ((/^required:/).test(response)) {
|
||||||
|
message = `<strong>${response.replace(/'/g, "").replace(/^[^:]*:/, "").replace(/,([^,]*)$/, "</strong> and <strong>$1").replace(/,/g, "</strong>, <strong>")}</strong> must not be empty`;
|
||||||
|
} else {
|
||||||
|
message = `Failed to <strong>${operation}</strong> record`;
|
||||||
|
}
|
||||||
|
|
||||||
|
showAlert(message, function() {
|
||||||
submitting = false;
|
submitting = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
251
resources/sass/dashboard.scss
vendored
251
resources/sass/dashboard.scss
vendored
|
@ -271,6 +271,23 @@ body {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.search-form {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&-input {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-submit {
|
||||||
|
margin-left: 10px;
|
||||||
|
height: 32px;
|
||||||
|
border: 1px solid fade-out($c-text, 0.75);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.search {
|
.search {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -478,6 +495,20 @@ body {
|
||||||
&.btn-link {
|
&.btn-link {
|
||||||
color: $c-dashboard-light;
|
color: $c-dashboard-light;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.btn-outline {
|
||||||
|
background-color: transparent;
|
||||||
|
color: $c-text;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.btn-inactive {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.btn-disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.view-table-container {
|
.view-table-container {
|
||||||
|
@ -536,6 +567,123 @@ body {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pagination-navigation-bar {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
margin-left: 4px;
|
||||||
|
display: flex;
|
||||||
|
width: calc(100% - 8px);
|
||||||
|
|
||||||
|
@include media-breakpoint-down(xs) {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include media-breakpoint-up(sm) {
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-arrow {
|
||||||
|
position: relative;
|
||||||
|
font-size: 0px;
|
||||||
|
|
||||||
|
@include media-breakpoint-down(xs) {
|
||||||
|
height: 35px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.prev {
|
||||||
|
@include media-breakpoint-down(xs) {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include media-breakpoint-up(sm) {
|
||||||
|
margin-right: ($grid-gutter-width / 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.next {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
|
||||||
|
@include media-breakpoint-down(xs) {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include media-breakpoint-up(sm) {
|
||||||
|
margin-left: ($grid-gutter-width / 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:before, &:after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: calc(50% - 1px);
|
||||||
|
left: 25%;
|
||||||
|
width: 16px;
|
||||||
|
height: 2px;
|
||||||
|
background-color: $c-text-light;
|
||||||
|
|
||||||
|
@include media-breakpoint-down(xs) {
|
||||||
|
left: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
transform: rotate(-45deg);
|
||||||
|
transform-origin: bottom left;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
transform: rotate(45deg);
|
||||||
|
transform-origin: top left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-page-count {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
@include media-breakpoint-down(xs) {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
$spacing: 3px;
|
||||||
|
|
||||||
|
&:not(:first-child) {
|
||||||
|
margin-left: $spacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(:last-child) {
|
||||||
|
margin-right: $spacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.space {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
content: "...";
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
line-height: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
margin-right: 20px;
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
left: calc(100% + 7px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-left: 20px;
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
right: calc(100% + 7px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.list-group {
|
.list-group {
|
||||||
margin-bottom: 0px;
|
margin-bottom: 0px;
|
||||||
|
|
||||||
|
@ -715,6 +863,108 @@ form {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.list {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
border-bottom: 1px solid fade-out($c-text, 0.75);
|
||||||
|
|
||||||
|
&-template, &-data {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-items-row {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.sort-icon {
|
||||||
|
margin-right: 10px;
|
||||||
|
display: inline-block;
|
||||||
|
opacity: 1;
|
||||||
|
transition: opacity 100ms;
|
||||||
|
cursor: grab;
|
||||||
|
|
||||||
|
&-inner {
|
||||||
|
position: relative;
|
||||||
|
top: 2px;
|
||||||
|
display: inline-block;
|
||||||
|
width: 12px;
|
||||||
|
height: 14px;
|
||||||
|
|
||||||
|
&-bar {
|
||||||
|
position: absolute;
|
||||||
|
left: 0px;
|
||||||
|
width: 100%;
|
||||||
|
height: 2px;
|
||||||
|
background-color: $c-text;
|
||||||
|
|
||||||
|
&:nth-child(1) {
|
||||||
|
top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-child(2) {
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-child(3) {
|
||||||
|
bottom: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-input, &-button {
|
||||||
|
&:not(:first-child) {
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(:last-child) {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-input {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&.wide {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-inner {
|
||||||
|
margin-bottom: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-overlay {
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
left: 0px;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
padding: 5px 8px;
|
||||||
|
background-color: $c-input-bg;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-button {
|
||||||
|
min-width: 70px;
|
||||||
|
height: $label-height;
|
||||||
|
border: 1px solid fade-out($c-text, 0.75);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-add-button {
|
||||||
|
height: $label-height;
|
||||||
|
border: 1px solid fade-out($c-text, 0.75);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.picker__holder {
|
.picker__holder {
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
|
|
||||||
|
@ -1023,6 +1273,7 @@ form {
|
||||||
.card {
|
.card {
|
||||||
position: relative;
|
position: relative;
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
|
width: 100%;
|
||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
|
|
|
@ -27,13 +27,17 @@
|
||||||
@elseif($type == 'user')
|
@elseif($type == 'user')
|
||||||
<input class="text-input" type="hidden" name="{{ $column['name'] }}" id="{{ $column['name'] }}" value="{{ Auth::id() }}" />
|
<input class="text-input" type="hidden" name="{{ $column['name'] }}" id="{{ $column['name'] }}" value="{{ Auth::id() }}" />
|
||||||
@elseif($type != 'display' || $id != 'new')
|
@elseif($type != 'display' || $id != 'new')
|
||||||
<div class="col-12 col-md-2">
|
<div class="col-12 col-md-4 col-lg-3">
|
||||||
<label for="{{ $column['name'] }}">{{ array_key_exists('title', $column) ? $column['title'] : ucfirst($column['name']) }}:</label>
|
<label for="{{ $column['name'] }}">{{ array_key_exists('title', $column) ? $column['title'] : ucfirst($column['name']) }}:</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12 col-md-10">
|
<div class="col-12 col-md-8 col-lg-9">
|
||||||
@if($type == 'text')
|
@if($type == 'string')
|
||||||
<input class="text-input" type="text" name="{{ $column['name'] }}" id="{{ $column['name'] }}" value="{{ $value }}" />
|
<input class="text-input" type="text" name="{{ $column['name'] }}" id="{{ $column['name'] }}" value="{{ $value }}" />
|
||||||
|
@elseif($type == 'text')
|
||||||
|
<textarea class="text-input" name="{{ $column['name'] }}" id="{{ $column['name'] }}">{{ $value }}</textarea>
|
||||||
|
@elseif($type == 'currency')
|
||||||
|
<input class="currency-input" type="text" name="{{ $column['name'] }}" id="{{ $column['name'] }}" value="{{ $value }}" autocomplete="off" />
|
||||||
@elseif($type == 'date')
|
@elseif($type == 'date')
|
||||||
<input class="date-picker" type="text" name="{{ $column['name'] }}" id="{{ $column['name'] }}" value="{{ $value == '' ? date('Y-m-d', time()) : preg_replace('/:[0-9][0-9]$/', '', $value) }}" />
|
<input class="date-picker" type="text" name="{{ $column['name'] }}" id="{{ $column['name'] }}" value="{{ $value == '' ? date('Y-m-d', time()) : preg_replace('/:[0-9][0-9]$/', '', $value) }}" />
|
||||||
@elseif($type == 'mkd')
|
@elseif($type == 'mkd')
|
||||||
|
@ -58,6 +62,45 @@
|
||||||
@endif
|
@endif
|
||||||
@endforeach
|
@endforeach
|
||||||
</select>
|
</select>
|
||||||
|
@elseif($type == 'list')
|
||||||
|
<div class="list" id="{{ $column['name'] }}">
|
||||||
|
<div class="list-template">
|
||||||
|
<div class="list-items-row">
|
||||||
|
<div class="sort-icon" title="Click and drag to reorder">
|
||||||
|
<div class="sort-icon-inner">
|
||||||
|
<div class="sort-icon-inner-bar"></div>
|
||||||
|
<div class="sort-icon-inner-bar"></div>
|
||||||
|
<div class="sort-icon-inner-bar"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@foreach($column['columns'] as $list_column)
|
||||||
|
@set('placeholder', $column['name'] == 'included' || $column['name'] == 'recommended' ? '' : $list_column)
|
||||||
|
|
||||||
|
<div class="list-items-row-input {{ count($column['columns']) == 1 ? 'wide' : '' }}">
|
||||||
|
<input class="list-items-row-input-inner" data-column="{{ $list_column }}" placeholder="{{ $placeholder }}" />
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
|
||||||
|
<button class="list-items-row-button" type="button">Delete</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="list-data">
|
||||||
|
@if($id != 'new')
|
||||||
|
@foreach($value as $row)
|
||||||
|
<div class="list-data-row">
|
||||||
|
@foreach($column['columns'] as $list_column)
|
||||||
|
<div class="list-data-row-item" data-column="{{ $list_column }}" data-value="{{ $row[$list_column] }}"></div>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="list-items"></div>
|
||||||
|
<button class="list-add-button" type="button">Add</button>
|
||||||
|
</div>
|
||||||
@elseif($type == 'image')
|
@elseif($type == 'image')
|
||||||
@set('current_image', "/uploads/$model/img/$id-" . $column['name'] . '.jpg')
|
@set('current_image', "/uploads/$model/img/$id-" . $column['name'] . '.jpg')
|
||||||
<input class="image-upload" type="file" name="{{ $column['name'] }}" id="{{ $column['name'] }}" />
|
<input class="image-upload" type="file" name="{{ $column['name'] }}" id="{{ $column['name'] }}" />
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
@if($create)
|
@if($create)
|
||||||
<button type="button" class="new-button btn btn-secondary">New</button>
|
<a href="/dashboard/edit/{{ $model }}/new" class="new-button btn btn-secondary">New</a>
|
||||||
@endif
|
@endif
|
||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
|
@ -14,48 +14,142 @@
|
||||||
<div id="edit-list-wrapper">
|
<div id="edit-list-wrapper">
|
||||||
<input type="hidden" id="token" value="{{ csrf_token() }}" />
|
<input type="hidden" id="token" value="{{ csrf_token() }}" />
|
||||||
|
|
||||||
@if($filter)
|
@if(count($paramdisplay))
|
||||||
<input id="filter-input" class="search" placeholder="Filter" />
|
@foreach($paramdisplay as $param)
|
||||||
|
<div>Showing {{ $heading }} with a {{ $param['title'] }} of "{{ $param['value'] }}"</div>
|
||||||
|
@endforeach
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
<ul id="edit-list" class="list-group edit-list list" data-model="{{ $model }}" {{ $sortcol != false ? "data-sort=$sortcol" : '' }}>
|
@if($filter)
|
||||||
@foreach($rows as $row)
|
@if(!$paginate)
|
||||||
<li class="list-group-item {{ $sortcol != false ? 'sortable' : '' }}" data-id="{{ $row['id'] }}">
|
<input id="filter-input" class="search" placeholder="Filter" />
|
||||||
<div class="title-column">
|
@else
|
||||||
@if($sortcol != false)
|
<form
|
||||||
<div class="sort-icon" title="Click and drag to reorder">
|
id="search-form"
|
||||||
<div class="sort-icon-inner">
|
class="search-form"
|
||||||
<div class="sort-icon-inner-bar"></div>
|
data-url="{{ url()->current() }}">
|
||||||
<div class="sort-icon-inner-bar"></div>
|
|
||||||
<div class="sort-icon-inner-bar"></div>
|
<input
|
||||||
|
class="search-form-input search"
|
||||||
|
placeholder="Search"
|
||||||
|
value="{{ request()->query('search') }}"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="submit"
|
||||||
|
class="search-form-submit"
|
||||||
|
value="Search"
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
@endif
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if($paginate && $rows->lastPage() !== 1)
|
||||||
|
<div class="pagination-navigation-bar">
|
||||||
|
<a
|
||||||
|
class="pagination-navigation-bar-arrow prev btn btn-primary {{ $rows->onFirstPage() ? 'btn-disabled' : '' }}"
|
||||||
|
href="/dashboard/edit/{{ $model }}?page={{ $rows->onFirstPage() ? 1 : $rows->currentPage() - 1 }}{{ $query !== '' ? ('&' . $query) : '' }}">
|
||||||
|
|
||||||
|
Previous Page
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="pagination-navigation-bar-page-count">
|
||||||
|
@set('pages_around', 2)
|
||||||
|
@set('start_page', $rows->currentPage() - $pages_around)
|
||||||
|
|
||||||
|
@if($start_page < 1)
|
||||||
|
@set('start_page', 1)
|
||||||
|
@elseif($start_page + $pages_around > $rows->lastPage())
|
||||||
|
@set('start_page', $rows->lastPage() - $pages_around)
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if($start_page > 1)
|
||||||
|
<a
|
||||||
|
class="pagination-navigation-bar-pages-number btn btn-outline space"
|
||||||
|
href="/dashboard/edit/{{ $model }}?page=1{{ $query !== '' ? ('&' . $query) : '' }}">
|
||||||
|
|
||||||
|
1
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@for($page = $start_page; $page < $start_page + 1 + $pages_around * 2; $page++)
|
||||||
|
@if($page === $rows->currentPage())
|
||||||
|
<div class="pagination-navigation-bar-pages-number btn btn-inactive">{{ $page }}</div>
|
||||||
|
@elseif($page <= $rows->lastPage())
|
||||||
|
<a
|
||||||
|
class="pagination-navigation-bar-pages-number btn btn-outline"
|
||||||
|
href="/dashboard/edit/{{ $model }}?page={{ $page }}{{ $query !== '' ? ('&' . $query) : '' }}">
|
||||||
|
|
||||||
|
{{ $page }}
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
@endfor
|
||||||
|
|
||||||
|
@if($start_page + $pages_around * 2 < $rows->lastPage())
|
||||||
|
<a
|
||||||
|
class="pagination-navigation-bar-pages-number btn btn-outline space"
|
||||||
|
href="/dashboard/edit/{{ $model }}?page={{ $rows->lastPage() }}{{ $query !== '' ? ('&' . $query) : '' }}">
|
||||||
|
|
||||||
|
{{ $rows->lastPage() }}
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a
|
||||||
|
class="pagination-navigation-bar-arrow next btn btn-primary {{ $rows->hasMorePages() ? '' : 'btn-disabled' }}"
|
||||||
|
href="/dashboard/edit/{{ $model }}?page={{ $rows->hasMorePages() ? $rows->currentPage() + 1 : $rows->currentPage() }}{{ $query !== '' ? ('&' . $query) : '' }}">
|
||||||
|
|
||||||
|
Next Page
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if(request()->query('search', null) != null && count($rows) == 0)
|
||||||
|
<div class="help-text text-center">No Matching {{ $heading }} Found</div>
|
||||||
|
@else
|
||||||
|
<ul id="edit-list" class="list-group edit-list list" data-model="{{ $model }}" {{ $sortcol != false ? "data-sort=$sortcol" : '' }}>
|
||||||
|
@foreach($rows as $row)
|
||||||
|
<li class="list-group-item {{ $sortcol != false ? 'sortable' : '' }}" data-id="{{ $row['id'] }}">
|
||||||
|
<div class="title-column">
|
||||||
|
@if($sortcol != false)
|
||||||
|
<div class="sort-icon" title="Click and drag to reorder">
|
||||||
|
<div class="sort-icon-inner">
|
||||||
|
<div class="sort-icon-inner-bar"></div>
|
||||||
|
<div class="sort-icon-inner-bar"></div>
|
||||||
|
<div class="sort-icon-inner-bar"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
@foreach($display as $display_column)
|
|
||||||
@if($row[$display_column] != '')
|
|
||||||
<div class="column">{{ $row[$display_column] }}</div>
|
|
||||||
|
|
||||||
@if(!$loop->last)
|
|
||||||
<div class="spacer">|</div>
|
|
||||||
@endif
|
|
||||||
@endif
|
@endif
|
||||||
@endforeach
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="button-column">
|
@foreach($display as $display_column)
|
||||||
@if(!empty($button))
|
@if($row[$display_column] != '')
|
||||||
<button type="button" class="action-button btn btn-secondary" data-confirmation="{{ $button[1] }}" data-success="{{ $button[2] }}" data-error="{{ $button[3] }}" data-url="{{ $button[4] }}">{{ $button[0] }}</button>
|
<div class="column">{{ $row[$display_column] }}</div>
|
||||||
@endif
|
|
||||||
|
|
||||||
<a class="edit-button btn btn-warning" href="/dashboard/edit/{{ $model }}/{{ $row['id'] }}">Edit</a>
|
@if(!$loop->last)
|
||||||
|
<div class="spacer">|</div>
|
||||||
|
@endif
|
||||||
|
@endif
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
|
||||||
@if($delete)
|
<div class="button-column">
|
||||||
<button type="button" class="delete-button btn btn-danger">Delete</button>
|
@if(!empty($button))
|
||||||
@endif
|
<button type="button" class="action-button btn btn-secondary" data-confirmation="{{ $button[1] }}" data-success="{{ $button[2] }}" data-error="{{ $button[3] }}" data-url="{{ $button[4] }}">{{ $button[0] }}</button>
|
||||||
</div>
|
@endif
|
||||||
</li>
|
|
||||||
@endforeach
|
@if(!empty($idlink))
|
||||||
</ul>
|
<a class="btn btn-secondary" href="{{ $idlink[1] }}{{ $row['id'] }}">{{ $idlink[0] }}</a>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<a class="edit-button btn btn-warning" href="/dashboard/edit/{{ $model }}/{{ $row['id'] }}">Edit</a>
|
||||||
|
|
||||||
|
@if($delete)
|
||||||
|
<button type="button" class="delete-button btn btn-danger">Delete</button>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
@endforeach
|
||||||
|
</ul>
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
@endsection
|
@endsection
|
||||||
|
|
Loading…
Reference in a new issue