A ton of progress, search start
continuous-integration/drone/push Build is passing

This commit is contained in:
Tyler
2020-12-26 05:46:23 -05:00
parent bed34d5350
commit d086b29bcc
47 changed files with 87115 additions and 7581 deletions
+8 -16
View File
@@ -4,15 +4,13 @@ namespace App\Http\Controllers;
use Illuminate\Http\Request;
class BookController extends Controller
{
class BookController extends Controller {
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
public function index() {
//
}
@@ -21,8 +19,7 @@ class BookController extends Controller
*
* @return \Illuminate\Http\Response
*/
public function create()
{
public function create() {
//
}
@@ -32,8 +29,7 @@ class BookController extends Controller
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
public function store(Request $request) {
//
}
@@ -43,8 +39,7 @@ class BookController extends Controller
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id)
{
public function show($id) {
//
}
@@ -54,8 +49,7 @@ class BookController extends Controller
* @param int $id
* @return \Illuminate\Http\Response
*/
public function edit($id)
{
public function edit($id) {
//
}
@@ -66,8 +60,7 @@ class BookController extends Controller
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, $id)
{
public function update(Request $request, $id) {
//
}
@@ -77,8 +70,7 @@ class BookController extends Controller
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy($id)
{
public function destroy($id) {
//
}
}
+9 -2
View File
@@ -2,6 +2,7 @@
namespace App\Http\Controllers;
use App\Models\Location;
use Illuminate\Http\Request;
class LocationController extends Controller {
@@ -37,10 +38,16 @@ class LocationController extends Controller {
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
* @return mixed
*/
public function show($id) {
//
$location = Location::find($id);
if (!$location) {
abort(404);
}
return view('location', [ 'location' => $location ]);
}
/**
+64 -3
View File
@@ -2,12 +2,73 @@
namespace App\Http\Controllers;
use App\Models\Author;
use App\Models\Book;
use App\Models\Location;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
class MainController extends Controller {
public function index() {
return view('welcome');
$rows = Book::paginate(15);
return view('index', [ 'rows' => $rows ]);
}
public function add() {
return view('add');
public function search(Request $request) {
$this->validate($request, [
'query' => [ 'required' ]
]);
$rows = Book::where('name', 'LIKE', '%' . $request->get('query') . '%')->paginate(15);
return view('index', [ 'rows' => $rows ]);
}
public function add(Request $request) {
return view('add', [
'old' => array_filter($request->old('books', []), function($item) {
return !empty($item['barcode']);
})
]);
}
public function save(Request $request) {
$this->validate($request, [
'location' => [ 'required' ]
]);
$input = array_filter($request->get('books'), function($item) {
return !empty($item['barcode']) && !empty($item['name']);
});
$this->getValidationFactory()->make($input, [
'*.barcode' => [ 'required' ],
'*.name' => [ 'required' ]
]);
$location = Location::firstOrCreate([
'name' => trim($request->get('location'))
]);
foreach ($input as $item) {
$book = Book::create([
'location_id' => $location->id,
'barcode' => $item['barcode'],
'name' => $item['name']
]);
$authors = [];
foreach (Arr::get($item, 'authors') as $author) {
$authors[] = Author::firstOrCreate([
'name' => $author
]);
}
$authors = array_map(function($author) { return $author->id; }, $authors);
$book->authors()->attach($authors);
}
}
}
+2 -2
View File
@@ -2,9 +2,9 @@
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Database\Eloquent\Model;
class Author extends Authenticatable {
class Author extends Model {
/**
* The attributes that are mass assignable.
+25 -5
View File
@@ -2,9 +2,15 @@
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use App\Services\Search\BookConfigurator;
use Illuminate\Database\Eloquent\Model;
use ScoutElastic\Searchable;
class Book extends Authenticatable {
class Book extends Model {
use Searchable;
protected $indexConfigurator = BookConfigurator::class;
/**
* The attributes that are mass assignable.
@@ -12,14 +18,28 @@ class Book extends Authenticatable {
* @var array
*/
protected $fillable = [
'name',
'location_id', 'barcode', 'name',
];
public function author() {
return $this->hasMany(Author::class);
public function authors() {
return $this->belongsToMany(Author::class, 'book_authors');
}
public function location() {
return $this->belongsTo(Location::class);
}
protected $mapping = [
'properties' => [
'name' => [
'type' => 'text',
// Also you can configure multi-fields, more details you can find here https://www.elastic.co/guide/en/elasticsearch/reference/current/multi-fields.html
'fields' => [
'raw' => [
'type' => 'keyword',
]
]
],
]
];
}
+2 -2
View File
@@ -2,9 +2,9 @@
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Database\Eloquent\Model;
class Location extends Authenticatable {
class Location extends Model {
/**
* The attributes that are mass assignable.
+22
View File
@@ -0,0 +1,22 @@
<?php
namespace App\Services\Search;
use ScoutElastic\IndexConfigurator;
class BookConfigurator extends IndexConfigurator
{
// It's not obligatory to determine name. By default it'll be a snaked class name without `IndexConfigurator` part.
protected $name = 'books';
// You can specify any settings you want, for example, analyzers.
protected $settings = [
'analysis' => [
'analyzer' => [
'es_std' => [
'type' => 'standard',
'stopwords' => '_spanish_'
]
]
]
];
}
+3 -2
View File
@@ -6,15 +6,16 @@
"type": "project",
"require": {
"php": "^7.3|^8.0",
"babenkoivan/scout-elasticsearch-driver": "^4.2",
"fideloper/proxy": "^4.4",
"fruitcake/laravel-cors": "^2.0",
"google/apiclient": "^2.2",
"guzzlehttp/guzzle": "^7.0.1",
"laravel/framework": "^8.12",
"laravel/scout": "^8.5",
"laravel/tinker": "^2.5",
"laravel/ui": "^3.0",
"sunra/php-simple-html-dom-parser": "^1.5",
"yajra/laravel-datatables-oracle": "^9.0"
"sunra/php-simple-html-dom-parser": "^1.5"
},
"require-dev": {
"facade/ignition": "^2.5",
Generated
+1379 -82
View File
File diff suppressed because it is too large Load Diff
+22 -10
View File
@@ -2,20 +2,32 @@
"private": true,
"scripts": {
"dev": "npm run development",
"development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
"watch": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --watch --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
"development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --config=node_modules/laravel-mix/setup/webpack.config.js",
"watch": "npm run development -- --watch",
"watch-poll": "npm run watch -- --watch-poll",
"hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js",
"hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --disable-host-check --config=node_modules/laravel-mix/setup/webpack.config.js",
"prod": "npm run production",
"production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
"production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --config=node_modules/laravel-mix/setup/webpack.config.js"
},
"devDependencies": {
"axios": "^0.17",
"bootstrap-sass": "^3.3.7",
"cross-env": "^5.1",
"@ttskch/select2-bootstrap4-theme": "^1.3.4",
"axios": "^0.19",
"bootstrap": "^4.0.0",
"cross-env": "^7.0",
"datatables.net-bs4": "^1.10.23",
"datatables.net-buttons-bs4": "^1.6.5",
"jquery": "^3.2",
"laravel-mix": "^1.0",
"lodash": "^4.17.4",
"vue": "^2.5.7"
"laravel-mix": "^5.0.1",
"lodash": "^4.17.19",
"popper.js": "^1.12",
"resolve-url-loader": "^2.3.1",
"sass": "^1.20.1",
"sass-loader": "^8.0.0",
"select2": "^4.0.13",
"vue": "^2.5.17",
"vue-template-compiler": "^2.6.10"
},
"dependencies": {
"vue-router": "^3.4.9"
}
}
+11287
View File
File diff suppressed because it is too large Load Diff
+2332 -27
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+5 -5
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+36 -52
View File
@@ -1,8 +1,8 @@
/*!
* Bootstrap Reboot v4.0.0-beta.2 (https://getbootstrap.com)
* Copyright 2011-2017 The Bootstrap Authors
* Copyright 2011-2017 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* Bootstrap Reboot v4.5.3 (https://getbootstrap.com/)
* Copyright 2011-2020 The Bootstrap Authors
* Copyright 2011-2020 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
*/
*,
@@ -15,22 +15,16 @@ html {
font-family: sans-serif;
line-height: 1.15;
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
-ms-overflow-style: scrollbar;
-webkit-tap-highlight-color: transparent;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
@-ms-viewport {
width: device-width;
}
article, aside, dialog, figcaption, figure, footer, header, hgroup, main, nav, section {
article, aside, figcaption, figure, footer, header, hgroup, main, nav, section {
display: block;
}
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
@@ -39,8 +33,8 @@ body {
background-color: #fff;
}
[tabindex="-1"]:focus {
outline: none !important;
[tabindex="-1"]:focus:not(:focus-visible) {
outline: 0 !important;
}
hr {
@@ -66,6 +60,8 @@ abbr[data-original-title] {
text-decoration: underline dotted;
cursor: help;
border-bottom: 0;
-webkit-text-decoration-skip-ink: none;
text-decoration-skip-ink: none;
}
address {
@@ -101,10 +97,6 @@ blockquote {
margin: 0 0 1rem;
}
dfn {
font-style: italic;
}
b,
strong {
font-weight: bolder;
@@ -134,7 +126,6 @@ a {
color: #007bff;
text-decoration: none;
background-color: transparent;
-webkit-text-decoration-skip: objects;
}
a:hover {
@@ -142,25 +133,21 @@ a:hover {
text-decoration: underline;
}
a:not([href]):not([tabindex]) {
a:not([href]):not([class]) {
color: inherit;
text-decoration: none;
}
a:not([href]):not([tabindex]):focus, a:not([href]):not([tabindex]):hover {
a:not([href]):not([class]):hover {
color: inherit;
text-decoration: none;
}
a:not([href]):not([tabindex]):focus {
outline: 0;
}
pre,
code,
kbd,
samp {
font-family: monospace, monospace;
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
font-size: 1em;
}
@@ -180,21 +167,9 @@ img {
border-style: none;
}
svg:not(:root) {
svg {
overflow: hidden;
}
a,
area,
button,
[role="button"],
input:not([type="range"]),
label,
select,
summary,
textarea {
-ms-touch-action: manipulation;
touch-action: manipulation;
vertical-align: middle;
}
table {
@@ -204,18 +179,19 @@ table {
caption {
padding-top: 0.75rem;
padding-bottom: 0.75rem;
color: #868e96;
color: #6c757d;
text-align: left;
caption-side: bottom;
}
th {
text-align: inherit;
text-align: -webkit-match-parent;
}
label {
display: inline-block;
margin-bottom: .5rem;
margin-bottom: 0.5rem;
}
button {
@@ -248,13 +224,28 @@ select {
text-transform: none;
}
[role="button"] {
cursor: pointer;
}
select {
word-wrap: normal;
}
button,
html [type="button"],
[type="button"],
[type="reset"],
[type="submit"] {
-webkit-appearance: button;
}
button:not(:disabled),
[type="button"]:not(:disabled),
[type="reset"]:not(:disabled),
[type="submit"]:not(:disabled) {
cursor: pointer;
}
button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
@@ -269,13 +260,6 @@ input[type="checkbox"] {
padding: 0;
}
input[type="date"],
input[type="time"],
input[type="datetime-local"],
input[type="month"] {
-webkit-appearance: listbox;
}
textarea {
overflow: auto;
resize: vertical;
@@ -314,7 +298,6 @@ progress {
-webkit-appearance: none;
}
[type="search"]::-webkit-search-cancel-button,
[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
@@ -330,6 +313,7 @@ output {
summary {
display: list-item;
cursor: pointer;
}
template {
File diff suppressed because one or more lines are too long
+5 -5
View File
@@ -1,8 +1,8 @@
/*!
* Bootstrap Reboot v4.0.0-beta.2 (https://getbootstrap.com)
* Copyright 2011-2017 The Bootstrap Authors
* Copyright 2011-2017 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* Bootstrap Reboot v4.5.3 (https://getbootstrap.com/)
* Copyright 2011-2020 The Bootstrap Authors
* Copyright 2011-2020 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
*/*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;-ms-overflow-style:scrollbar;-webkit-tap-highlight-color:transparent}@-ms-viewport{width:device-width}article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}dfn{font-style:italic}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent;-webkit-text-decoration-skip:objects}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto;-ms-overflow-style:scrollbar}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg:not(:root){overflow:hidden}[role=button],a,area,button,input:not([type=range]),label,select,summary,textarea{-ms-touch-action:manipulation;touch-action:manipulation}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#868e96;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item}template{display:none}[hidden]{display:none!important}
*/*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus:not(:focus-visible){outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([class]){color:inherit;text-decoration:none}a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto;-ms-overflow-style:scrollbar}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit;text-align:-webkit-match-parent}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}
/*# sourceMappingURL=bootstrap-reboot.min.css.map */
File diff suppressed because one or more lines are too long
+3189 -1300
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+5 -5
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+61292 -21
View File
File diff suppressed because it is too large Load Diff
+2473 -1729
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+4 -4
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1908 -1340
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+4 -4
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-22
View File
@@ -1,22 +0,0 @@
/**
* First we will load all of this project's JavaScript dependencies which
* includes Vue and other libraries. It is a great starting point when
* building robust, powerful web applications using Vue and Laravel.
*/
require('./bootstrap');
window.Vue = require('vue');
/**
* Next, we will create a fresh Vue application instance and attach it to
* the page. Then, you may begin adding components to this application
* or customize the JavaScript scaffolding to fit your unique needs.
*/
Vue.component('example-component', require('./components/ExampleComponent.vue'));
const app = new Vue({
el: '#app'
});
@@ -1,23 +0,0 @@
<template>
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">Example Component</div>
<div class="panel-body">
I'm an example component!
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
mounted() {
console.log('Component mounted.')
}
}
</script>
-38
View File
@@ -1,38 +0,0 @@
// Body
$body-bg: #f5f8fa;
// Borders
$laravel-border-color: darken($body-bg, 10%);
$list-group-border: $laravel-border-color;
$navbar-default-border: $laravel-border-color;
$panel-default-border: $laravel-border-color;
$panel-inner-border: $laravel-border-color;
// Brands
$brand-primary: #3097D1;
$brand-info: #8eb4cb;
$brand-success: #2ab27b;
$brand-warning: #cbb956;
$brand-danger: #bf5329;
// Typography
$icon-font-path: "~bootstrap-sass/assets/fonts/bootstrap/";
$font-family-sans-serif: "Raleway", sans-serif;
$font-size-base: 14px;
$line-height-base: 1.6;
$text-color: #636b6f;
// Navbar
$navbar-default-bg: #fff;
// Buttons
$btn-default-color: $text-color;
// Inputs
$input-border: lighten($text-color, 40%);
$input-border-focus: lighten($brand-primary, 25%);
$input-color-placeholder: lighten($text-color, 30%);
// Panels
$panel-default-heading-bg: #fff;
-9
View File
@@ -1,9 +0,0 @@
// Fonts
@import url("https://fonts.googleapis.com/css?family=Raleway:300,400,600");
// Variables
@import "variables";
// Bootstrap
@import "~bootstrap-sass/assets/stylesheets/bootstrap";
+133
View File
@@ -0,0 +1,133 @@
/**
* First we will load all of this project's JavaScript dependencies which
* includes Vue and other libraries. It is a great starting point when
* building robust, powerful web applications using Vue and Laravel.
*/
require('./bootstrap');
$(document).ready(function(e) {
$.fn.dataTable.render.authorValue = function(_, context, book) {
let authors = [];
console.log(book);
for (let author of book.authors) {
authors.push(author.name);
}
return authors.join(', ');
};
var authorOptions = {
placeholder: 'Authors',
tags: true,
ajax: {
url: '/authors/search',
dataType: 'json'
}
};
var locationOptions = {
placeholder: 'Location',
tags: true
};
$('#add-form .select2-author').select2(authorOptions);
$('#add-form .select2-location').select2(locationOptions);
$('#add-form').on('click', '.remove-row', function(e) {
e.preventDefault();
$(this).closest('.form-row').remove();
});
$('#add-form').on('keydown', '.barcode_input', function(e) {
if (e.keyCode === 13) {
e.preventDefault();
}
});
$('#add-form').on('keyup', '.barcode_input', function (e) {
if (e.keyCode === 13) {
// Do something
e.preventDefault();
var $this = $(this),
$row = $this.closest('.form-row'),
barcodeValue = $this.val();
if (barcodeValue === '') {
return;
}
$.get('/lookup/' + barcodeValue, function(res) {
$row.find('input[name*=name]').val(res.title);
var $authors = $row.find('.select2-author');
$authors.children('option').remove();
for (var i = 0; i < res.authors.length; i++) {
$authors.append($('<option>', {value: res.authors[i], text: res.authors[i], selected: 'selected'}));
}
$authors.trigger('change');
}, 'json');
var count = emptyRowCount();
console.log('Empty rows:', count);
if (count < 1) {
var firstIndex = 0,
$container = $row.closest('.row-container');
for (var i = 0; i < 200; i++) {
if ($container.find('.form-row[data-index=' + i + ']').length < 1) {
firstIndex = i;
break;
}
}
var $template = $('#add-template'),
$clone = $template.clone();
$clone.attr('data-index', firstIndex);
$clone.find('input, select').each(function() {
$(this).attr('name', $(this).attr('name').replace('__INDEX__', firstIndex));
});
$clone.removeAttr('id');
$clone.find('input[type=text]').val('');
$clone.removeClass('invisible').addClass('form-row');
$container.append($clone);
setTimeout(function() {
$clone.find('.barcode_input').focus();
$clone.find('.select2-author').select2(authorOptions);
}, 150);
}
}
});
});
function emptyRowCount() {
var $barcodes = $('#add-form .barcode_input');
var count = 0;
$barcodes.each(function() {
if ($(this).val() == '') {
count++;
}
});
return count;
}
@@ -1,4 +1,3 @@
window._ = require('lodash');
/**
@@ -8,9 +7,13 @@ window._ = require('lodash');
*/
try {
window.Popper = require('popper.js').default;
window.$ = window.jQuery = require('jquery');
require('bootstrap-sass');
require('bootstrap');
require('select2');
require('datatables.net-bs4');
require('datatables.net-buttons-bs4');
} catch (e) {}
/**
@@ -23,31 +26,19 @@ window.axios = require('axios');
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
/**
* Next we will register the CSRF Token as a common header with Axios so that
* all outgoing HTTP requests automatically have it attached. This is just
* a simple convenience so we don't have to attach every token manually.
*/
let token = document.head.querySelector('meta[name="csrf-token"]');
if (token) {
window.axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content;
} else {
console.error('CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token');
}
/**
* Echo exposes an expressive API for subscribing to channels and listening
* for events that are broadcast by Laravel. Echo and event broadcasting
* allows your team to easily build robust real-time web applications.
*/
// import Echo from 'laravel-echo'
// import Echo from 'laravel-echo';
// window.Pusher = require('pusher-js');
// window.Echo = new Echo({
// broadcaster: 'pusher',
// key: 'your-pusher-key'
// key: process.env.MIX_PUSHER_APP_KEY,
// cluster: process.env.MIX_PUSHER_APP_CLUSTER,
// forceTLS: true
// });
+14
View File
@@ -0,0 +1,14 @@
// Bootstrap
@import "../../node_modules/bootstrap/scss/bootstrap";
// DataTables
@import "../../node_modules/datatables.net-bs4/css/dataTables.bootstrap4.css";
@import "../../node_modules/datatables.net-buttons-bs4/css/buttons.bootstrap4.css";
body {
padding-top: 5rem;
}
.form-row {
padding-bottom: 6px;
}
+16 -23
View File
@@ -1,7 +1,8 @@
@extends('layouts.main')
@section('content')
<form id="add-form">
<form id="add-form" method="POST" action="{{ route('save') }}">
{{ csrf_field() }}
<div class="form-row justify-content-md-center">
<div class="col-4">
<select name="location" class="form-control select2-location">
@@ -9,29 +10,21 @@
</div>
</div>
<br />
<div class="form-row">
<div class="col">
<input name="books[][barcode]" type="text" class="form-control barcode_input" placeholder="Barcode">
</div>
<div class="col-4">
<input name="books[][name]" type="text" class="form-control" placeholder="Name">
</div>
<div class="col">
<select name="books[][authors]" class="form-control select2-author" multiple="multiple">
</select>
<div class="row-container">
@if (!empty($old))
@foreach ($old as $index => $item)
@include('partials/row', [ 'index' => $index, 'item' => $item ])
@endforeach
@include('partials/row', [ 'index' => count($old), 'item' => [] ])
@else
@include('partials/row', [ 'index' => 0])
@endif
</div>
<div class="row">
<button class="btn btn-primary" name="save" type="submit">
Save
</button>
</div>
</form>
<div id="add-template" class="invisible">
<div class="col">
<input name="books[][barcode]" type="text" class="form-control barcode_input" placeholder="Barcode">
</div>
<div class="col-4">
<input name="books[][name]" type="text" class="form-control" placeholder="Name">
</div>
<div class="col">
<select name="books[][authors]" class="form-control select2-author" multiple="multiple">
</select>
</div>
</div>
@include('partials/row', [ 'id' => 'add-template', 'htmlClass' => 'invisible' ])
@endsection
+21
View File
@@ -0,0 +1,21 @@
@extends('layouts.main')
@section('content')
<table class="table table-compact">
<thead>
<th>Name</th>
<th>Authors</th>
<th>Location</th>
</thead>
<tbody>
@foreach ($rows as $row)
<tr>
<td>{{ $row->name }}</td>
<td>{{ $row->authors->pluck('name')->join(', ') }}</td>
<td>{{ $row->location->name }}</td>
</tr>
@endforeach
</tbody>
</table>
{{ $rows->links() }}
@endsection
+5 -34
View File
@@ -8,49 +8,20 @@
<title>{{ config('app.name') }}</title>
<!-- Bootstrap core CSS -->
<link href="{{ url('css/bootstrap.min.css') }}" rel="stylesheet">
<link href="{{ mix('css/app.css') }}" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.6-rc.0/css/select2.min.css" rel="stylesheet" />
<link href="{{ url('css/select2-bs4.css') }}" rel="stylesheet">
<link href="{{ url('css/app.css') }}" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top">
<a class="navbar-brand" href="#">{{ config('app.name') }}</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarsExampleDefault"
aria-controls="navbarsExampleDefault" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarsExampleDefault">
<ul class="navbar-nav mr-auto">
<li class="nav-item active">
<a class="nav-link" href="/">Home <span class="sr-only">(current)</span></a>
</li>
<li class="nav-item">
<a class="nav-link" href="/add">Add</a>
</li>
</ul>
<form class="form-inline my-2 my-lg-0">
<input class="form-control mr-sm-2" type="text" placeholder="Search" aria-label="Search">
<button class="btn btn-outline-success my-2 my-sm-0" type="submit">Search</button>
</form>
</div>
</nav>
@include('partials/navbar')
<main role="main" class="container">
@yield('content')
</main><!-- /.container -->
<script
src="https://code.jquery.com/jquery-3.2.1.min.js"
integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4="
crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.3/umd/popper.min.js" integrity="sha384-vFJXuSJphROIrBnz7yo7oB41mKfc8JzQZiCq4NCceLEaO4IHwicKwpJf9c9IpFgh" crossorigin="anonymous"></script>
<script src="{{ url('js/bootstrap.min.js') }}"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.6-rc.0/js/select2.min.js"></script>
<script src="{{ url('js/app.js') }}"></script>
<script src="{{ mix('js/app.js') }}"></script>
<script src="{{ asset('vendor/datatables/buttons.server-side.js') }}"></script>
@stack('scripts')
</body>
</html>
+22
View File
@@ -0,0 +1,22 @@
<nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top">
<a class="navbar-brand" href="#">{{ config('app.name') }}</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarsExampleDefault"
aria-controls="navbarsExampleDefault" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarsExampleDefault">
<ul class="navbar-nav mr-auto">
<li class="nav-item active">
<a class="nav-link" href="/">Home <span class="sr-only">(current)</span></a>
</li>
<li class="nav-item">
<a class="nav-link" href="/add">Add</a>
</li>
</ul>
<form class="form-inline my-2 my-lg-0" action="{{ route('search') }}">
<input name="query" class="form-control mr-sm-2" type="text" placeholder="Search" aria-label="Search" />
<button class="btn btn-outline-success my-2 my-sm-0" type="submit">Search</button>
</form>
</div>
</nav>
+20
View File
@@ -0,0 +1,20 @@
<div {!! !empty($id) ? 'id="' . $id . '" ' : '' !!}class="form-row {{ $htmlClass ?? '' }}" data-index="{{ $index ?? '__INDEX__' }}">
<div class="col">
<input name="books[{{ $index ?? '__INDEX__' }}][barcode]" type="text" class="form-control barcode_input" placeholder="Barcode" value="{{ $item['barcode'] ?? '' }}">
</div>
<div class="col-4">
<input name="books[{{ $index ?? '__INDEX__' }}][name]" type="text" class="form-control" placeholder="Name" value="{{ $item['name'] ?? '' }}">
</div>
<div class="col">
<select name="books[{{ $index ?? '__INDEX__' }}][authors][]" class="form-control select2-author" multiple="multiple">
@if(!empty($item['authors']))
@foreach($item['authors'] as $author)
<option value="{{ $author }}" selected="selected">{{ $author }}</option>
@endforeach
@endif
</select>
</div>
<div class="col-1">
<button class="remove-row btn btn-danger">Remove</button>
</div>
</div>
-1
View File
@@ -1 +0,0 @@
@extends('layouts.main')
+2
View File
@@ -13,6 +13,8 @@
Route::get('/', 'MainController@index');
Route::get('/add', 'MainController@add');
Route::post('/save', 'MainController@save')->name('save');
Route::get('/search', 'MainController@search')->name('search');
Route::get('/lookup/{isbn}', 'LookupController@lookup');
+3 -3
View File
@@ -1,4 +1,4 @@
let mix = require('laravel-mix');
const mix = require('laravel-mix');
/*
|--------------------------------------------------------------------------
@@ -11,5 +11,5 @@ let mix = require('laravel-mix');
|
*/
mix.js('resources/assets/js/app.js', 'public/js')
.sass('resources/assets/sass/app.scss', 'public/css');
mix.js('resources/js/app.js', 'public/js')
.sass('resources/sass/app.scss', 'public/css');