mirror of
https://github.com/prurigro/hypothetical.git
synced 2024-11-21 15:42:31 -05:00
Reuse dashboard logic much more, delete images and files when the associated item is deleted, rework dashboard lists so their items can be configured in the $dashboard_columns of their own class, allow dashboard lists to upload images, delete images when dashboard list items are deleted, add a default image extension variable to dashboard model items rather than hard-coding it so it can be reconfigured, improve the dashboard styles some more, and improve the readme (including documenting the new dashboard list update)
This commit is contained in:
parent
ace3c607ca
commit
db7deb0fdc
9 changed files with 397 additions and 166 deletions
|
@ -70,38 +70,19 @@ class Dashboard
|
|||
*/
|
||||
public static function getModel($model, $type = null)
|
||||
{
|
||||
$model_name = ucfirst($model);
|
||||
$model_name = implode('', array_map('ucfirst', explode('_', $model)));
|
||||
|
||||
// Ensure the model has been declared in the menu
|
||||
$model_in_menu = false;
|
||||
|
||||
foreach (self::$menu as $menu_item) {
|
||||
if (array_key_exists('submenu', $menu_item)) {
|
||||
// Check each item if this is a submenu
|
||||
foreach ($menu_item['submenu'] as $submenu_item) {
|
||||
if ($submenu_item['model'] == $model) {
|
||||
$model_in_menu = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Check the menu item
|
||||
if ($menu_item['model'] == $model) {
|
||||
$model_in_menu = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Don't bother continuing if we've already confirmed it's in the menu
|
||||
if ($model_in_menu) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($model_in_menu && file_exists(app_path() . '/Models/' . $model_name . '.php')) {
|
||||
if (file_exists(app_path() . '/Models/' . $model_name . '.php')) {
|
||||
$model_class = 'App\\Models\\' . $model_name;
|
||||
|
||||
if ($type != null && $type != $model_class::$dashboard_type) {
|
||||
return null;
|
||||
if ($type != null) {
|
||||
if (is_array($type)) {
|
||||
if (!in_array($model_class::$dashboard_type, $type)) {
|
||||
return null;
|
||||
}
|
||||
} else if ($type != $model_class::$dashboard_type) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return new $model_class;
|
||||
|
|
|
@ -82,18 +82,11 @@ class DashboardController extends Controller {
|
|||
|
||||
if ($model_class != null) {
|
||||
if ($id == 'new') {
|
||||
$item = null;
|
||||
$item = new $model_class;
|
||||
} else {
|
||||
if ($model_class::where('id', $id)->exists()) {
|
||||
$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()) {
|
||||
return view('errors.no-such-record');
|
||||
}
|
||||
|
@ -102,13 +95,26 @@ class DashboardController extends Controller {
|
|||
}
|
||||
}
|
||||
|
||||
foreach ($model_class::$dashboard_columns as $column) {
|
||||
if ($column['type'] === 'list') {
|
||||
$list_model_class = 'App\\Models\\' . $column['model'];
|
||||
$list_model_instance = new $list_model_class;
|
||||
|
||||
$item->{$column['name']} = [
|
||||
'model' => $list_model_instance->getTable(),
|
||||
'list' => $id == 'new' ? [] : $list_model_instance::where($column['foreign'], $item->id)->orderBy($column['sort'])->get()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return view('dashboard.pages.edit-item', [
|
||||
'heading' => $model_class->getDashboardHeading(),
|
||||
'model' => $model,
|
||||
'id' => $id,
|
||||
'item' => $item,
|
||||
'help_text' => $model_class::$dashboard_help_text,
|
||||
'columns' => $model_class::$dashboard_columns
|
||||
'heading' => $model_class->getDashboardHeading(),
|
||||
'default_img_ext' => $model_class::$default_image_ext,
|
||||
'model' => $model,
|
||||
'id' => $id,
|
||||
'item' => $item,
|
||||
'help_text' => $model_class::$dashboard_help_text,
|
||||
'columns' => $model_class::$dashboard_columns
|
||||
]);
|
||||
} else {
|
||||
abort(404);
|
||||
|
@ -242,6 +248,8 @@ class DashboardController extends Controller {
|
|||
}
|
||||
|
||||
// populate connected lists with list items in $request
|
||||
$lists = [];
|
||||
|
||||
foreach ($request['columns'] as $column) {
|
||||
if ($column['type'] === 'list') {
|
||||
$column_name = $column['name'];
|
||||
|
@ -250,20 +258,44 @@ class DashboardController extends Controller {
|
|||
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;
|
||||
if ($list_model_class::$dashboard_type == 'list') {
|
||||
$ids = [];
|
||||
|
||||
foreach ($row as $key => $value) {
|
||||
$list_model_item->$key = $value;
|
||||
if ($request->has($column_name)) {
|
||||
foreach ($request[$column_name] as $index => $row) {
|
||||
if ($row['id'] == 'new') {
|
||||
$list_model_item = new $list_model_class;
|
||||
} else {
|
||||
$list_model_item = $list_model_class::find($row['id']);
|
||||
}
|
||||
|
||||
$list_model_item->$foreign = $item->id;
|
||||
$list_model_item->{$dashboard_column['sort']} = $index;
|
||||
|
||||
foreach ($row['data'] as $key => $data) {
|
||||
if ($data['type'] == 'string') {
|
||||
$list_model_item->$key = $data['value'];
|
||||
}
|
||||
}
|
||||
|
||||
$list_model_item->save();
|
||||
array_push($ids, $list_model_item->id);
|
||||
}
|
||||
|
||||
$list_model_item->save();
|
||||
}
|
||||
|
||||
// delete any associated row that wasn't just created or edited
|
||||
foreach ($list_model_class::where($foreign, $item->id)->whereNotIn('id', $ids)->get() as $list_item) {
|
||||
$list_item->delete();
|
||||
}
|
||||
|
||||
// store the sets of ids for each list
|
||||
$lists[$column_name] = $ids;
|
||||
|
||||
// stop looping through dashboard columns
|
||||
break;
|
||||
} else {
|
||||
return 'invalid-list-model:' . $dashboard_column['model'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -274,7 +306,10 @@ class DashboardController extends Controller {
|
|||
$item->save();
|
||||
|
||||
// return the id number in the format '^id:[0-9][0-9]*$' on success
|
||||
return 'id:' . $item->id;
|
||||
return [
|
||||
'id' => $item->id,
|
||||
'lists' => $lists
|
||||
];
|
||||
} else {
|
||||
return 'model-access-fail';
|
||||
}
|
||||
|
@ -289,7 +324,7 @@ class DashboardController extends Controller {
|
|||
'name' => 'required'
|
||||
]);
|
||||
|
||||
$model_class = Dashboard::getModel($request['model'], 'edit');
|
||||
$model_class = Dashboard::getModel($request['model'], [ 'edit', 'list' ]);
|
||||
|
||||
if ($model_class != null) {
|
||||
$item = $model_class::find($request['id']);
|
||||
|
@ -321,7 +356,7 @@ class DashboardController extends Controller {
|
|||
'name' => 'required'
|
||||
]);
|
||||
|
||||
$model_class = Dashboard::getModel($request['model'], 'edit');
|
||||
$model_class = Dashboard::getModel($request['model'], [ 'edit', 'list' ]);
|
||||
|
||||
if ($model_class != null) {
|
||||
$item = $model_class::find($request['id']);
|
||||
|
@ -363,12 +398,16 @@ class DashboardController extends Controller {
|
|||
return 'permission-fail';
|
||||
}
|
||||
|
||||
// delete associated files if they exist
|
||||
foreach ($model_class::$dashboard_columns as $column) {
|
||||
if ($column['type'] == 'image') {
|
||||
$item->deleteImage($column['name'], false);
|
||||
} else if ($column['type'] == 'file') {
|
||||
$item->deleteFile($column['name'], false);
|
||||
// delete associated list items
|
||||
foreach ($item::$dashboard_columns as $column) {
|
||||
if ($column['type'] == 'list') {
|
||||
$list_model_class = Dashboard::getModel($column['model'], 'list');
|
||||
|
||||
if ($list_model_class != null) {
|
||||
foreach ($list_model_class::where($column['foreign'], $item->id)->get() as $list_item) {
|
||||
$list_item->delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -399,7 +438,7 @@ class DashboardController extends Controller {
|
|||
'name' => 'required'
|
||||
]);
|
||||
|
||||
$model_class = Dashboard::getModel($request['model'], 'edit');
|
||||
$model_class = Dashboard::getModel($request['model'], [ 'edit', 'list' ]);
|
||||
|
||||
if ($model_class != null) {
|
||||
$item = $model_class::find($request['id']);
|
||||
|
@ -423,7 +462,7 @@ class DashboardController extends Controller {
|
|||
'name' => 'required'
|
||||
]);
|
||||
|
||||
$model_class = Dashboard::getModel($request['model'], 'edit');
|
||||
$model_class = Dashboard::getModel($request['model'], [ 'edit', 'list' ]);
|
||||
|
||||
if ($model_class != null) {
|
||||
$item = $model_class::find($request['id']);
|
||||
|
|
|
@ -21,8 +21,8 @@ class Blog extends DashboardModel
|
|||
[ 'name' => 'created_at', 'title' => 'Date', 'type' => 'display' ],
|
||||
[ 'name' => 'title', 'required' => true, 'unique' => true, 'type' => 'string' ],
|
||||
[ 'name' => 'body', 'required' => true, 'type' => 'mkd' ],
|
||||
[ 'name' => 'header-image', 'title' => 'Header Image', 'type' => 'image', 'delete' => true ],
|
||||
[ 'name' => 'tags', 'type' => 'list', 'model' => 'BlogTags', 'columns' => [ 'name' ], 'foreign' => 'blog_id', 'sort' => 'order' ]
|
||||
[ 'name' => 'header-image', 'title' => 'Header Image', 'type' => 'image', 'delete' => true, 'ext' => 'jpg' ],
|
||||
[ 'name' => 'tags', 'type' => 'list', 'model' => 'BlogTags', 'foreign' => 'blog_id', 'sort' => 'order' ]
|
||||
];
|
||||
|
||||
public static function getBlogEntries()
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class BlogTags extends Model {
|
||||
|
||||
class BlogTags extends DashboardModel
|
||||
{
|
||||
/**
|
||||
* The database table used by the model.
|
||||
*
|
||||
|
@ -11,4 +11,9 @@ class BlogTags extends Model {
|
|||
*/
|
||||
protected $table = 'blog_tags';
|
||||
|
||||
public static $dashboard_type = 'list';
|
||||
|
||||
public static $dashboard_columns = [
|
||||
[ 'type' => 'string', 'name' => 'name' ]
|
||||
];
|
||||
}
|
||||
|
|
|
@ -117,6 +117,33 @@ class DashboardModel extends Model
|
|||
*/
|
||||
public static $dashboard_id_link = [];
|
||||
|
||||
/**
|
||||
* The default image extension when none is set
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $default_image_ext = 'jpg';
|
||||
|
||||
/**
|
||||
* Functionality to run when various events occur
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
public static function boot() {
|
||||
parent::boot();
|
||||
|
||||
static::deleting(function($item) {
|
||||
// delete associated images and files if they exist
|
||||
foreach ($item::$dashboard_columns as $column) {
|
||||
if ($column['type'] == 'image') {
|
||||
$item->deleteImage($column['name'], false);
|
||||
} else if ($column['type'] == 'file') {
|
||||
$item->deleteFile($column['name'], false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the dashboard heading
|
||||
*
|
||||
|
@ -155,7 +182,6 @@ class DashboardModel extends Model
|
|||
|
||||
$max_width = 0;
|
||||
$max_height = 0;
|
||||
$main_ext = 'jpg';
|
||||
|
||||
// Retrieve the column
|
||||
$column = static::getColumn($name);
|
||||
|
@ -165,9 +191,11 @@ class DashboardModel extends Model
|
|||
return 'no-such-column-fail';
|
||||
}
|
||||
|
||||
// Update the extension if it's been configured
|
||||
// Use the configured image extension or fall back on the default if none is set
|
||||
if (array_key_exists('ext', $column)) {
|
||||
$main_ext = $column['ext'];
|
||||
} else {
|
||||
$main_ext = $this::$default_image_ext;
|
||||
}
|
||||
|
||||
// Create the directory if it doesn't exist
|
||||
|
@ -254,7 +282,6 @@ class DashboardModel extends Model
|
|||
}
|
||||
|
||||
// Set up our variables
|
||||
$main_ext = 'jpg';
|
||||
$extensions = [];
|
||||
|
||||
// Retrieve the column
|
||||
|
@ -265,9 +292,11 @@ class DashboardModel extends Model
|
|||
return 'no-such-column-fail';
|
||||
}
|
||||
|
||||
// Update the extension if it's been configured
|
||||
// Use the configured image extension or fall back on the default if none is set
|
||||
if (array_key_exists('ext', $column)) {
|
||||
$main_ext = $column['ext'];
|
||||
} else {
|
||||
$main_ext = $this::$default_image_ext;
|
||||
}
|
||||
|
||||
// Build the set of extensions to delete
|
||||
|
|
47
readme.md
47
readme.md
|
@ -14,12 +14,13 @@ A Hypothetical website template for bootstrapping new projects.
|
|||
## Major Components
|
||||
|
||||
* Bootstrap 5
|
||||
* Fontawesome 5
|
||||
* Gsap 3
|
||||
* Gulp 4
|
||||
* Jquery 3
|
||||
* Browsersync
|
||||
* Fontawesome
|
||||
* Gsap
|
||||
* Gulp
|
||||
* Jquery
|
||||
* Laravel 9
|
||||
* Sass 1.32
|
||||
* Sass
|
||||
* Vue 3 (Optional)
|
||||
|
||||
## Setup
|
||||
|
@ -71,13 +72,13 @@ Reading through its contents is encouraged for a complete understanding of what
|
|||
|
||||
* `gulp`: Update the compiled javascript and css in `public/js` and `public/css`, and copy fonts to `public/fonts`.
|
||||
* `gulp --production`: Does the same as `gulp` except the compiled javascript and css is minified, and console logging is removed from the javascript (good for production deployments).
|
||||
* `gulp default watch`: Does the same as `gulp` but continues running to watch for changes to files so it can recompile updated assets and reload them in the browser using BrowserSync (good for development environments).
|
||||
* `gulp default watch`: Does the same as `gulp` but continues running to watch for changes to files so it can recompile updated assets and reload them in the browser using Browsersync (good for development environments).
|
||||
|
||||
**NOTE**: If `gulp` isn't installed globally or its version is less than `4`, you should use the version included in `node_modules` by running `"$(npm bin)/gulp"` in place of the `gulp` command.
|
||||
|
||||
### BrowserSync
|
||||
### Browsersync
|
||||
|
||||
BrowserSync is used to keep the browser in sync with your code when running the `watch` task with gulp.
|
||||
Browsersync is used to keep the browser in sync with your code when running the `watch` task with gulp.
|
||||
|
||||
## Public
|
||||
|
||||
|
@ -148,6 +149,13 @@ In a Vue.js component:
|
|||
|
||||
## Dashboard
|
||||
|
||||
### Important Note
|
||||
|
||||
The naming convention of dashboard database tables and model classes should be the following:
|
||||
|
||||
* Database table names should be lower case with hyphen separators: `your_table_name`
|
||||
* Model classes should be the same name but in camel case with its first character capitalized: `YourTableName.php` and `class YourTableName extends DashboardModel`
|
||||
|
||||
### Registration
|
||||
|
||||
The `REGISTRATION` variable in the `.env` file controls whether a new dashboard user can be registered.
|
||||
|
@ -229,7 +237,7 @@ Models with their `$dashboard_type` set to `edit` also use:
|
|||
* `date-time`: 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
|
||||
* `list`: One or more `text` or `image` items saved to a connected table
|
||||
* `image`: Fields that contain image uploads
|
||||
* `file`: Fields that contains file uploads
|
||||
* `display`: Displayed information that can't be edited
|
||||
|
@ -240,9 +248,19 @@ Models with their `$dashboard_type` set to `edit` also use:
|
|||
* `name`: (required by `file` and `image`) Used along with the record id to determine the filename
|
||||
* `delete`: (optional for `file` and `image`) Enables a delete button for the upload when set to true
|
||||
* `ext`: (required by `file` and optional for `image`) Configures the file extension of the upload (`image` defaults to `jpg`)
|
||||
* `model`: (required by `list`) The class name of the model that the list will be generated from
|
||||
* `foreign` (required by `list`) The name of the list table's foreign id column that references the id on the current table
|
||||
* `sort` (required by `list`) The name of the list table's column that the order will be stored in
|
||||
* `max_width`: (optional for `image`) Configures the maximum width of an image upload (defaults to `0` which sets no maximum width)
|
||||
* `max_height`: (optional for `image`) Configures the maximum height of an image upload (defaults to `0` which sets no maximum height)
|
||||
|
||||
Models with their `$dashboard_type` set to `list` also use:
|
||||
|
||||
* `type`: The column type which can be any of the following:
|
||||
* `string`: Single-line text input field
|
||||
* `image`: Fields that contain image uploads
|
||||
* `ext`: (optional for `image`) Configures the file extension of the upload (`image` defaults to `jpg`)
|
||||
|
||||
An example of the `$dashboard_columns` array in a model with its `$dashboard_type` set to `view`:
|
||||
|
||||
```php
|
||||
|
@ -262,6 +280,15 @@ An example of the `$dashboard_columns` array in a model with its `$dashboard_typ
|
|||
[ 'name' => 'title', 'required' => true, 'unique' => true, 'type' => 'string' ],
|
||||
[ 'name' => 'body', 'required' => true, 'type' => 'mkd' ],
|
||||
[ 'name' => 'header-image', 'title' => 'Header Image', 'type' => 'image', 'delete' => true, 'ext' => 'jpg' ],
|
||||
[ 'name' => 'tags', 'type' => 'list', 'model' => 'BlogTags', 'columns' => [ 'name' ], 'foreign' => 'blog_id', 'sort' => 'order' ]
|
||||
[ 'name' => 'tags', 'type' => 'list', 'model' => 'BlogTags', 'foreign' => 'blog_id', 'sort' => 'order' ]
|
||||
];
|
||||
```
|
||||
|
||||
An example of the `$dashboard_columns` array in a model with its `$dashboard_type` set to `list`:
|
||||
|
||||
```php
|
||||
public static $dashboard_columns = [
|
||||
[ 'type' => 'string', 'name' => 'name' ],
|
||||
[ 'type' => 'image', 'name' => 'photo' ]
|
||||
];
|
||||
```
|
||||
|
|
|
@ -323,17 +323,17 @@ function editItemInit() {
|
|||
$currencyInputs = $(".currency-input"),
|
||||
$datePickers = $(".date-picker"),
|
||||
$mkdEditors = $(".mkd-editor"),
|
||||
$fileUploads = $(".file-upload"),
|
||||
$imgUploads = $(".image-upload"),
|
||||
$lists = $(".list"),
|
||||
$token = $("#token"),
|
||||
model = $editItem.data("model"),
|
||||
id = $editItem.data("id"),
|
||||
operation = id === "new" ? "create" : "update";
|
||||
operation = $editItem.data("id") === "new" ? "create" : "update";
|
||||
|
||||
let allowTimes = [],
|
||||
let $imgUploads = [],
|
||||
$fileUploads = [],
|
||||
allowTimes = [],
|
||||
easymde = [],
|
||||
formData = {},
|
||||
id = $editItem.data("id"),
|
||||
submitting = false,
|
||||
hours,
|
||||
minutes,
|
||||
|
@ -404,30 +404,33 @@ function editItemInit() {
|
|||
value = [];
|
||||
|
||||
$this.find(".list-items .list-items-row").each(function(index, row) {
|
||||
const rowData = {};
|
||||
const rowData = {},
|
||||
id = $(row).data("id");
|
||||
|
||||
$(row).find(".list-items-row-input-inner").each(function(index, input) {
|
||||
const $input = $(input),
|
||||
column = $input.data("column"),
|
||||
value = $input.val();
|
||||
$(row).find(".list-items-row-content-inner").each(function(index, inner) {
|
||||
const $inner = $(inner),
|
||||
$input = $inner.find(".list-input"),
|
||||
column = $inner.data("column");
|
||||
|
||||
rowData[column] = value;
|
||||
if ($inner.data("type") === "string") {
|
||||
rowData[column] = { type: "string", value: $input.val() };
|
||||
}
|
||||
});
|
||||
|
||||
value.push(rowData);
|
||||
value.push({ id: typeof id === "undefined" ? "new" : id, data: rowData });
|
||||
});
|
||||
|
||||
addFormData("list", column, value);
|
||||
});
|
||||
};
|
||||
|
||||
const uploadFile = function(row_id, currentFile) {
|
||||
const uploadFile = function(currentFile) {
|
||||
let file, fileUpload;
|
||||
|
||||
// functionality to run on success
|
||||
const returnSuccess = function() {
|
||||
loadingModal("hide");
|
||||
window.location.href = `/dashboard/edit/${model}/${row_id}`;
|
||||
window.location.href = `/dashboard/edit/${model}/${id}`;
|
||||
};
|
||||
|
||||
// add the file from the file upload box for file-upload class elements
|
||||
|
@ -439,9 +442,9 @@ function editItemInit() {
|
|||
|
||||
// add the file, id and model to the formData variable
|
||||
file.append("file", fileUpload.files[0]);
|
||||
file.append("name", $(fileUpload).attr("name"));
|
||||
file.append("id", row_id);
|
||||
file.append("model", model);
|
||||
file.append("name", $(fileUpload).data("column"));
|
||||
file.append("id", $(fileUpload).data("id"));
|
||||
file.append("model", $(fileUpload).data("model"));
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
|
@ -452,7 +455,7 @@ function editItemInit() {
|
|||
beforeSend: function(xhr) { xhr.setRequestHeader("X-CSRF-TOKEN", $token.val()); }
|
||||
}).always(function(response) {
|
||||
if (response === "success") {
|
||||
uploadFile(row_id, currentFile + 1);
|
||||
uploadFile(currentFile + 1);
|
||||
} else {
|
||||
loadingModal("hide");
|
||||
|
||||
|
@ -462,19 +465,19 @@ function editItemInit() {
|
|||
}
|
||||
});
|
||||
} else {
|
||||
uploadFile(row_id, currentFile + 1);
|
||||
uploadFile(currentFile + 1);
|
||||
}
|
||||
} else {
|
||||
returnSuccess();
|
||||
}
|
||||
};
|
||||
|
||||
const uploadImage = function(row_id, currentImage) {
|
||||
const uploadImage = function(currentImage) {
|
||||
let file, imgUpload;
|
||||
|
||||
// functionality to run on success
|
||||
const returnSuccess = function() {
|
||||
uploadFile(row_id, 0);
|
||||
uploadFile(0);
|
||||
};
|
||||
|
||||
// add the image from the image upload box for image-upload class elements
|
||||
|
@ -486,9 +489,9 @@ function editItemInit() {
|
|||
|
||||
// add the file, id and model to the formData variable
|
||||
file.append("file", imgUpload.files[0]);
|
||||
file.append("name", $(imgUpload).attr("name"));
|
||||
file.append("id", row_id);
|
||||
file.append("model", model);
|
||||
file.append("name", $(imgUpload).data("column"));
|
||||
file.append("id", $(imgUpload).data("id"));
|
||||
file.append("model", $(imgUpload).data("model"));
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
|
@ -499,7 +502,7 @@ function editItemInit() {
|
|||
beforeSend: function(xhr) { xhr.setRequestHeader("X-CSRF-TOKEN", $token.val()); }
|
||||
}).always(function(response) {
|
||||
if (response === "success") {
|
||||
uploadImage(row_id, currentImage + 1);
|
||||
uploadImage(currentImage + 1);
|
||||
} else {
|
||||
loadingModal("hide");
|
||||
|
||||
|
@ -509,7 +512,7 @@ function editItemInit() {
|
|||
}
|
||||
});
|
||||
} else {
|
||||
uploadImage(row_id, currentImage + 1);
|
||||
uploadImage(currentImage + 1);
|
||||
}
|
||||
} else {
|
||||
returnSuccess();
|
||||
|
@ -524,7 +527,7 @@ function editItemInit() {
|
|||
// initialize image deletion
|
||||
$(".edit-button.delete.image").on("click", function(e) {
|
||||
const $this = $(this),
|
||||
name = $this.data("name");
|
||||
name = $this.data("column");
|
||||
|
||||
if (!submitting) {
|
||||
submitting = true;
|
||||
|
@ -535,8 +538,8 @@ function editItemInit() {
|
|||
type: "DELETE",
|
||||
url: "/dashboard/image-delete",
|
||||
data: {
|
||||
id: id,
|
||||
model: model,
|
||||
id: $this.data("id"),
|
||||
model: $this.data("model"),
|
||||
name: name,
|
||||
_token: $token.val()
|
||||
}
|
||||
|
@ -559,7 +562,7 @@ function editItemInit() {
|
|||
// initialize file deletion
|
||||
$(".edit-button.delete.file").on("click", function(e) {
|
||||
const $this = $(this),
|
||||
name = $this.data("name"),
|
||||
name = $this.data("column"),
|
||||
ext = $this.data("ext");
|
||||
|
||||
if (!submitting) {
|
||||
|
@ -571,8 +574,8 @@ function editItemInit() {
|
|||
type: "DELETE",
|
||||
url: "/dashboard/file-delete",
|
||||
data: {
|
||||
id: id,
|
||||
model: model,
|
||||
id: $this.data("id"),
|
||||
model: $this.data("model"),
|
||||
name: name,
|
||||
ext: ext,
|
||||
_token: $token.val()
|
||||
|
@ -617,7 +620,7 @@ function editItemInit() {
|
|||
const $row = $(row);
|
||||
|
||||
// initialize delete button functionality
|
||||
$row.find(".list-items-row-button").off("click").on("click", function() {
|
||||
$row.find(".list-items-row-buttons-delete").off("click").on("click", function() {
|
||||
$row.remove();
|
||||
initSort();
|
||||
contentChanged();
|
||||
|
@ -627,24 +630,40 @@ function editItemInit() {
|
|||
|
||||
const initList = function() {
|
||||
$list.find(".list-data-row").each(function(rowIndex, row) {
|
||||
// Add the values from the current data row to the template
|
||||
const id = $(row).data("id");
|
||||
|
||||
// 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");
|
||||
value = $item.data("value"),
|
||||
type = $item.data("type");
|
||||
|
||||
$template.find(".list-items-row-input-inner").each(function(inputIndex, input) {
|
||||
const $input = $(input);
|
||||
$template.find(".list-items-row-content-inner").each(function(inputIndex, inner) {
|
||||
const $inner = $(inner);
|
||||
|
||||
if ($input.data("column") === column) {
|
||||
$input.val(value);
|
||||
if ($inner.data("column") === column) {
|
||||
if (type === "string") {
|
||||
$inner.find(".list-input").val(value);
|
||||
} else if (type === "image" && value !== "") {
|
||||
$inner.find(".image-link").attr("href", value).addClass("active");
|
||||
$inner.find(".image-preview").attr("src", value);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Add the populated template to the list of items then clear the template values
|
||||
// add the populated template to the list of items
|
||||
$template.find(".list-items-row").clone().appendTo($items);
|
||||
$template.find(".list-items-row-input-inner").val("");
|
||||
|
||||
// set the id for the list items row
|
||||
$items.find(".list-items-row").last().data("id", id);
|
||||
$items.find(".list-items-row").last().find(".list-input").data("id", id);
|
||||
|
||||
// clear the template values
|
||||
$template.find(".list-input").val("");
|
||||
$template.find(".image-link").attr("href", "").removeClass("active");
|
||||
$template.find(".image-preview").attr("src", "");
|
||||
});
|
||||
|
||||
initSort();
|
||||
|
@ -740,6 +759,10 @@ function editItemInit() {
|
|||
if (!submitting && changes) {
|
||||
submitting = true;
|
||||
|
||||
// find the image and file upload inputs
|
||||
$imgUploads = $(".image-upload");
|
||||
$fileUploads = $(".file-upload");
|
||||
|
||||
// show the loading modal
|
||||
loadingModal("show");
|
||||
|
||||
|
@ -755,8 +778,26 @@ function editItemInit() {
|
|||
}).always(function(response) {
|
||||
let message = "";
|
||||
|
||||
if ((/^id:[0-9][0-9]*$/).test(response)) {
|
||||
uploadImage(response.replace(/^id:/, ""), 0);
|
||||
if (typeof response.id !== "undefined") {
|
||||
id = response.id;
|
||||
|
||||
// Add the appropriate ids to each list item input
|
||||
if (Object.keys(response.lists).length) {
|
||||
Object.keys(response.lists).forEach(function(key) {
|
||||
$(`#${key}`).find(".list-items").first().find(".list-items-row").each(function(index) {
|
||||
const listItemId = response.lists[key][index];
|
||||
|
||||
$(this).data("id", listItemId);
|
||||
$(this).find(".list-input").data("id", listItemId);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Add the current row id to each image and file upload input
|
||||
$(".image-upload, .file-upload").not(".list-input").data("id", response.id);
|
||||
|
||||
// Start uploading images (and then files)
|
||||
uploadImage(0);
|
||||
} else {
|
||||
loadingModal("hide");
|
||||
|
||||
|
@ -764,6 +805,8 @@ function editItemInit() {
|
|||
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 if ((/^invalid-list-model:/).test(response)) {
|
||||
message = `<strong>${response.replace(/'/g, "").replace(/^[^:]*:/, "").replace(/,([^,]*)$/, "</strong> and <strong>$1").replace(/,/g, "</strong>, <strong>")}</strong> is not a valid list`;
|
||||
} else {
|
||||
message = `Failed to <strong>${operation}</strong> record`;
|
||||
}
|
||||
|
|
|
@ -917,6 +917,12 @@ form {
|
|||
}
|
||||
}
|
||||
|
||||
.container-fluid {
|
||||
@include media-breakpoint-down(sm) {
|
||||
padding: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.list {
|
||||
margin-bottom: pxrem(15);
|
||||
padding-bottom: pxrem(15);
|
||||
|
@ -927,17 +933,37 @@ form {
|
|||
}
|
||||
|
||||
&-items-row {
|
||||
position: relative;
|
||||
margin-bottom: pxrem(10);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: pxrem(8) pxrem(8) pxrem(8) pxrem(33);
|
||||
border-radius: pxrem(6);
|
||||
background-color: darken($c-input-bg, 2%);
|
||||
|
||||
@include media-breakpoint-up(lg) {
|
||||
display: flex;
|
||||
padding: pxrem(5) pxrem(5) pxrem(5) pxrem(30);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
@include media-breakpoint-down(lg) {
|
||||
margin-bottom: pxrem(15);
|
||||
}
|
||||
}
|
||||
|
||||
.sort-icon {
|
||||
margin-right: pxrem(10);
|
||||
position: absolute;
|
||||
top: pxrem(11);
|
||||
left: pxrem(10);
|
||||
display: inline-block;
|
||||
opacity: 1;
|
||||
transition: opacity 100ms;
|
||||
cursor: grab;
|
||||
|
||||
@include media-breakpoint-up(lg) {
|
||||
top: pxrem(9);
|
||||
}
|
||||
|
||||
&-inner {
|
||||
position: relative;
|
||||
top: pxrem(2);
|
||||
|
@ -968,17 +994,29 @@ form {
|
|||
}
|
||||
}
|
||||
|
||||
&-input, &-button {
|
||||
&:not(:first-child) {
|
||||
margin-left: pxrem(5);
|
||||
&-content, &-button {
|
||||
@include media-breakpoint-down(lg) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-right: pxrem(5);
|
||||
@include media-breakpoint-down(lg) {
|
||||
margin-bottom: pxrem(10);
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(lg) {
|
||||
margin-right: pxrem(5);
|
||||
}
|
||||
}
|
||||
|
||||
&:not(:first-child) {
|
||||
@include media-breakpoint-up(lg) {
|
||||
margin-left: pxrem(5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-input {
|
||||
&-content {
|
||||
position: relative;
|
||||
|
||||
&.wide {
|
||||
|
@ -986,29 +1024,52 @@ form {
|
|||
}
|
||||
|
||||
&-inner {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
$row-height: pxrem(36);
|
||||
display: flex;
|
||||
height: $row-height;
|
||||
align-items: center;
|
||||
|
||||
&-overlay {
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: pxrem(5) pxrem(8);
|
||||
background-color: $c-input-bg;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
pointer-events: none;
|
||||
.list-input {
|
||||
margin-bottom: 0px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.image-link {
|
||||
margin-right: pxrem(10);
|
||||
display: block;
|
||||
width: pxrem(50);
|
||||
height: $row-height;
|
||||
background-color: $c-input-bg;
|
||||
|
||||
&:not(.active) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.image-preview {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-button {
|
||||
min-width: pxrem(70);
|
||||
height: $label-height;
|
||||
border: 1px solid fade-out($c-text, 0.75);
|
||||
border-radius: pxrem(4);
|
||||
&-buttons {
|
||||
@include media-breakpoint-up(lg) {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
&-delete {
|
||||
min-width: pxrem(70);
|
||||
height: $label-height;
|
||||
border: 1px solid fade-out($c-text, 0.75);
|
||||
border-radius: pxrem(4);
|
||||
background-color: $c-dashboard-delete;
|
||||
color: $c-text-light;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1016,6 +1077,11 @@ form {
|
|||
height: $label-height;
|
||||
border: 1px solid fade-out($c-text, 0.75);
|
||||
border-radius: pxrem(4);
|
||||
|
||||
@include media-breakpoint-down(lg) {
|
||||
margin-top: pxrem(15);
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1101,6 +1167,7 @@ form {
|
|||
padding: pxrem(5) pxrem(10);
|
||||
border-radius: pxrem(5);
|
||||
text-transform: uppercase;
|
||||
text-decoration: none;
|
||||
transition: background-color 150ms;
|
||||
cursor: pointer;
|
||||
|
||||
|
@ -1153,6 +1220,10 @@ form {
|
|||
margin-top: pxrem(10);
|
||||
margin-bottom: pxrem(20);
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
@php
|
||||
$value = $item !== null ? $item[$column['name']] : '';
|
||||
$type = $id == 'new' && array_key_exists('type-new', $column) ? $column['type-new'] : $column['type'];
|
||||
$ext = array_key_exists('ext', $column) ? $column['ext'] : 'jpg';
|
||||
$ext = array_key_exists('ext', $column) ? $column['ext'] : $default_img_ext;
|
||||
@endphp
|
||||
|
||||
@if($type == 'hidden')
|
||||
|
@ -72,6 +72,13 @@
|
|||
@endphp
|
||||
@endif
|
||||
|
||||
@if(gettype($select_title))
|
||||
@php
|
||||
$select_value = $select_value ? 1 : 0;
|
||||
$select_title = $select_title ? 'true' : 'false';
|
||||
@endphp
|
||||
@endif
|
||||
|
||||
@if($select_value === $value)
|
||||
<option value="{{ $select_value }}" selected="selected">{{ $select_title }}</option>
|
||||
@else
|
||||
|
@ -80,9 +87,14 @@
|
|||
@endforeach
|
||||
</select>
|
||||
@elseif($type == 'list')
|
||||
@php
|
||||
$list_model = App\Dashboard::getModel($value['model']);
|
||||
$list_columns = $list_model::$dashboard_columns;
|
||||
@endphp
|
||||
|
||||
<div class="list" id="{{ $column['name'] }}">
|
||||
<div class="list-template">
|
||||
<div class="list-items-row">
|
||||
<div class="list-items-row" data-id="new">
|
||||
<div class="sort-icon" title="Click and drag to reorder">
|
||||
<div class="sort-icon-inner">
|
||||
<div class="sort-icon-inner-bar"></div>
|
||||
|
@ -91,22 +103,46 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
@foreach($column['columns'] as $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="{{ $list_column }}" />
|
||||
@foreach($list_columns as $list_column)
|
||||
<div class="list-items-row-content {{ count($list_columns) == 1 ? 'wide' : '' }}">
|
||||
<div class="list-items-row-content-inner" data-column="{{ $list_column['name'] }}" data-type="{{ $list_column['type'] }}">
|
||||
@if($list_column['type'] == 'string')
|
||||
<input class="list-input" placeholder="{{ $list_column['name'] }}" />
|
||||
@elseif($list_column['type'] == 'image')
|
||||
<a class="image-link" href="" target="_blank"><img class="image-preview" src="" /></a>
|
||||
<input class="list-input image-upload" type="file" data-column="{{ $list_column['name'] }}" data-model="{{ $value['model'] }}" />
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
|
||||
<button class="list-items-row-button" type="button">Delete</button>
|
||||
<div class="list-items-row-buttons">
|
||||
<button class="list-items-row-buttons-delete" type="button">Delete</button>
|
||||
</div>
|
||||
</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>
|
||||
@foreach($value['list'] as $row)
|
||||
<div class="list-data-row" data-id="{{ $row['id'] }}">
|
||||
@foreach($list_columns as $list_column)
|
||||
@if($list_column['type'] == 'string')
|
||||
@php
|
||||
$list_column_value = $row[$list_column['name']]
|
||||
@endphp
|
||||
@elseif($list_column['type'] == 'image')
|
||||
@php
|
||||
$list_column_item = $list_model::find($row['id']);
|
||||
$list_column_image_ext = array_key_exists('ext', $list_column) ? $list_column['ext'] : $default_img_ext;
|
||||
$list_column_image_path = $list_model->getUploadsPath('image') . $row['id'] . '-' . $list_column['name'] . '.' . $list_column_image_ext;
|
||||
$list_column_value = file_exists(public_path($list_column_image_path)) ? $list_column_image_path . '?version=' . $list_column_item->timestamp() : '';
|
||||
@endphp
|
||||
|
||||
{{ $list_column_image_path }}
|
||||
@endif
|
||||
|
||||
<div class="list-data-row-item" data-type="{{ $list_column['type'] }}" data-column="{{ $list_column['name'] }}" data-value="{{ $list_column_value }}"></div>
|
||||
@endforeach
|
||||
</div>
|
||||
@endforeach
|
||||
|
@ -121,14 +157,14 @@
|
|||
$current_image = "/uploads/$model/img/$id-" . $column['name'] . '.' . $ext;
|
||||
@endphp
|
||||
|
||||
<input class="image-upload" type="file" name="{{ $column['name'] }}" id="{{ $column['name'] }}" />
|
||||
<input class="image-upload" type="file" data-column="{{ $column['name'] }}" data-model="{{ $model }}" data-id="{{ $id }}" />
|
||||
|
||||
@if(file_exists(base_path() . '/public' . $current_image))
|
||||
<div id="current-image-{{ $column['name'] }}">
|
||||
<img class="current-image" src="{{ $current_image }}?version={{ $item->timestamp() }}" />
|
||||
|
||||
@if(array_key_exists('delete', $column) && $column['delete'])
|
||||
<span class="edit-button delete image" data-name="{{ $column['name'] }}">
|
||||
<span class="edit-button delete image" data-column="{{ $column['name'] }}" data-model="{{ $model }}" data-id="{{ $id }}">
|
||||
Delete Image
|
||||
</span>
|
||||
@endif
|
||||
|
@ -139,14 +175,14 @@
|
|||
$current_file = "/uploads/$model/files/$id-" . $column['name'] . '.' . $column['ext'];
|
||||
@endphp
|
||||
|
||||
<input class="file-upload" type="file" name="{{ $column['name'] }}" id="{{ $column['name'] }}" />
|
||||
<input class="file-upload" type="file" data-column="{{ $column['name'] }}" data-model="{{ $model }}" data-id="{{ $id }}" />
|
||||
|
||||
@if(file_exists(base_path() . '/public' . $current_file))
|
||||
<div id="current-file-{{ $column['name'] }}">
|
||||
<a class="edit-button view" href="{{ $current_file }}?version={{ $item->timestamp() }}" target="_blank">View {{ strtoupper($column['ext']) }}</a>
|
||||
|
||||
@if(array_key_exists('delete', $column) && $column['delete'])
|
||||
<span class="edit-button delete file" data-name="{{ $column['name'] }}">
|
||||
<span class="edit-button delete file" data-column="{{ $column['name'] }}" data-model="{{ $model }}" data-id="{{ $id }}">
|
||||
Delete {{ strtoupper($column['ext']) }}
|
||||
</span>
|
||||
@endif
|
||||
|
|
Loading…
Reference in a new issue