├── dist
├── css
│ └── field.css
└── js
│ └── field.js
├── resources
├── sass
│ └── field.scss
└── js
│ ├── field.js
│ └── components
│ └── IndexField.vue
├── docs
└── screenshot.png
├── mix-manifest.json
├── src
├── Http
│ ├── Routes
│ │ └── api.php
│ ├── Requests
│ │ └── ReorderResourceRequest.php
│ └── Controllers
│ │ └── ResourceSortingController.php
├── Sortable.php
├── Concerns
│ └── SortsIndexEntries.php
└── FieldServiceProvider.php
├── .gitignore
├── webpack.mix.js
├── composer.json
├── package.json
├── LICENSE.md
└── README.md
/dist/css/field.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/resources/sass/field.scss:
--------------------------------------------------------------------------------
1 | // Nova Tool CSS
2 |
--------------------------------------------------------------------------------
/docs/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Naxon/nova-field-sortable/HEAD/docs/screenshot.png
--------------------------------------------------------------------------------
/mix-manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "/dist/js/field.js": "/dist/js/field.js",
3 | "/dist/css/field.css": "/dist/css/field.css"
4 | }
--------------------------------------------------------------------------------
/resources/js/field.js:
--------------------------------------------------------------------------------
1 | Nova.booting((Vue, router) => {
2 | Vue.component('index-nova-field-sortable', require('./components/IndexField'));
3 | })
4 |
--------------------------------------------------------------------------------
/src/Http/Routes/api.php:
--------------------------------------------------------------------------------
1 | =7.1.0",
11 | "laravel/nova": "*",
12 | "spatie/eloquent-sortable": "^3.4"
13 | },
14 | "autoload": {
15 | "psr-4": {
16 | "Naxon\\NovaFieldSortable\\": "src/"
17 | }
18 | },
19 | "extra": {
20 | "laravel": {
21 | "providers": [
22 | "Naxon\\NovaFieldSortable\\FieldServiceProvider"
23 | ]
24 | }
25 | },
26 | "config": {
27 | "sort-packages": true
28 | },
29 | "minimum-stability": "dev",
30 | "prefer-stable": true
31 | }
32 |
--------------------------------------------------------------------------------
/src/Concerns/SortsIndexEntries.php:
--------------------------------------------------------------------------------
1 | when(empty($request->get('orderBy')), function (Builder $q) {
20 | $q->getQuery()->orders = [];
21 |
22 | return $q->orderBy(static::$defaultSortField);
23 | });
24 |
25 | return $query;
26 | }
27 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "dev": "npm run development",
5 | "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
6 | "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",
7 | "watch-poll": "npm run watch -- --watch-poll",
8 | "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",
9 | "prod": "npm run production",
10 | "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
11 | },
12 | "devDependencies": {
13 | "cross-env": "^5.0.0",
14 | "laravel-mix": "^1.0",
15 | "laravel-nova": "^1.0"
16 | },
17 | "dependencies": {
18 | "vue": "^2.5.0"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Http/Controllers/ResourceSortingController.php:
--------------------------------------------------------------------------------
1 | findResourceOrFail()->authorizeToUpdate($request);
13 | $direction = $request->get('direction', null);
14 |
15 | if (!in_array($direction, ['up', 'down'])) {
16 | return response('', 500);
17 | }
18 |
19 | $model = $request->findModelQuery()->firstOrFail();
20 |
21 | if (!method_exists($model, 'moveOrderUp') || !method_exists($model, 'moveOrderDown')) {
22 | return response(__('Missing sorting methods on model :model', ['model' => get_class($model)]), 500);
23 | }
24 |
25 | if ($direction == 'up') {
26 | $model->moveOrderUp();
27 | } else {
28 | $model->moveOrderDown();
29 | }
30 |
31 | return response('', 200);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) Daniel Naxon
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/src/FieldServiceProvider.php:
--------------------------------------------------------------------------------
1 | registerRoutes();
35 | }
36 |
37 | /**
38 | * Registers field routes
39 | *
40 | * @return void
41 | */
42 | private function registerRoutes()
43 | {
44 | Route::domain(config('nova.domain', null))
45 | ->middleware(config('nova.middleware', []))
46 | ->prefix('/nova-vendor/naxon/nova-field-sortable')
47 | ->group(__DIR__ . '/Http/Routes/api.php');
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/resources/js/components/IndexField.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
54 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Laravel Nova Sortable Field
2 |
3 | [](https://packagist.org/packages/naxon/nova-field-sortable)
4 | [](https://packagist.org/packages/naxon/nova-field-sortable)
5 |
6 | ## Description
7 |
8 | This field adds reordering functionality to your resource's index using the awesome [eloquent-sortable](https://github.com/spatie/eloquent-sortable) package by the great people of [Spatie](https://spatie.be).
9 |
10 | 
11 |
12 | ## Requrements
13 |
14 | * Laravel 5.6+ with Nova.
15 | * [spatie/eloquent-sortable](https://github.com/spatie/eloquent-sortable) (If not already installed, this package will install if for you and all you have to do is follow the [installation](https://github.com/spatie/eloquent-sortable#installation) instructions).
16 |
17 | ## Installation
18 |
19 | This package can be installed through Composer.
20 | ```bash
21 | composer require naxon/nova-field-sortable
22 | ```
23 |
24 | ## Usage
25 |
26 | 1. Follow the [usage instructions](https://github.com/spatie/eloquent-sortable#usage) on the eloquent-sortable repository to make your model sortable.
27 | 2. Use the `Naxon\NovaFieldSortable\Concerns\SortsIndexEntries` trait in your Nova Resource.
28 | 3. Add a public static property called `$defaultSortField` to your resource, containing your sorting column (I recomment adding it in your main `app/Nova/Resource.php` file).
29 | 4. Add the `Naxon\NovaFieldSortable\Sortable` field to your Nova Resource `fields` method, using a label and your primary key column.
30 |
31 | ### Example
32 |
33 | ```php
34 | sortable(),
61 |
62 | Text::make('Title'),
63 |
64 | Sortable::make('Order', 'id')
65 | ->onlyOnIndex(),
66 | ];
67 | }
68 | }
69 |
70 | ```
71 |
72 | ## Security
73 |
74 | If you discover any security related issues, please email naxond@gmail.com instead of using the issue tracker.
75 |
76 | ## Credits
77 |
78 | * [Spatie](https://spatie.be)
79 |
80 | ## License
81 |
82 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information.
83 |
--------------------------------------------------------------------------------
/dist/js/field.js:
--------------------------------------------------------------------------------
1 | !function(t){var e={};function r(n){if(e[n])return e[n].exports;var o=e[n]={i:n,l:!1,exports:{}};return t[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}r.m=t,r.c=e,r.d=function(t,e,n){r.o(t,e)||Object.defineProperty(t,e,{configurable:!1,enumerable:!0,get:n})},r.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return r.d(e,"a",e),e},r.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},r.p="",r(r.s=0)}([function(t,e,r){r(1),t.exports=r(9)},function(t,e,r){Nova.booting(function(t,e){t.component("index-nova-field-sortable",r(2))})},function(t,e,r){var n=r(3)(r(4),r(8),!1,null,null,null);t.exports=n.exports},function(t,e){t.exports=function(t,e,r,n,o,i){var a,c=t=t||{},s=typeof t.default;"object"!==s&&"function"!==s||(a=t,c=t.default);var u,f="function"==typeof c?c.options:c;if(e&&(f.render=e.render,f.staticRenderFns=e.staticRenderFns,f._compiled=!0),r&&(f.functional=!0),o&&(f._scopeId=o),i?(u=function(t){(t=t||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||"undefined"==typeof __VUE_SSR_CONTEXT__||(t=__VUE_SSR_CONTEXT__),n&&n.call(this,t),t&&t._registeredComponents&&t._registeredComponents.add(i)},f._ssrRegister=u):n&&(u=n),u){var l=f.functional,h=l?f.render:f.beforeCreate;l?(f._injectStyles=u,f.render=function(t,e){return u.call(e),h(t,e)}):f.beforeCreate=h?[].concat(h,u):[u]}return{esModule:a,exports:c,options:f}}},function(t,e,r){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var n=r(5),o=r.n(n);e.default={props:["resourceName","field"],methods:{reorderResource:function(){var t,e=(t=o.a.mark(function t(e){return o.a.wrap(function(t){for(;;)switch(t.prev=t.next){case 0:return t.prev=0,t.next=3,this.reorderRequest(e);case 3:t.sent,this.$toasted.show(this.__("The new order has been set!"),{type:"success"}),this.$router.go(this.$router.currentRoute),t.next=11;break;case 8:t.prev=8,t.t0=t.catch(0),this.$toasted.show(this.__("An error occured while trying to reorder the resource."),{type:"error"});case 11:case"end":return t.stop()}},t,this,[[0,8]])}),function(){var e=t.apply(this,arguments);return new Promise(function(t,r){return function n(o,i){try{var a=e[o](i),c=a.value}catch(t){return void r(t)}if(!a.done)return Promise.resolve(c).then(function(t){n("next",t)},function(t){n("throw",t)});t(c)}("next")})});return function(t){return e.apply(this,arguments)}}(),reorderRequest:function(t){return Nova.request().patch("/nova-vendor/naxon/nova-field-sortable/"+this.resourceName+"/"+this.field.value+"/reorder",{direction:t})},createFormData:function(t){return _.tap(new FormData,function(e){e.append("direction",t)})}}}},function(t,e,r){t.exports=r(6)},function(t,e,r){var n=function(){return this}()||Function("return this")(),o=n.regeneratorRuntime&&Object.getOwnPropertyNames(n).indexOf("regeneratorRuntime")>=0,i=o&&n.regeneratorRuntime;if(n.regeneratorRuntime=void 0,t.exports=r(7),o)n.regeneratorRuntime=i;else try{delete n.regeneratorRuntime}catch(t){n.regeneratorRuntime=void 0}},function(t,e){!function(e){"use strict";var r,n=Object.prototype,o=n.hasOwnProperty,i="function"==typeof Symbol?Symbol:{},a=i.iterator||"@@iterator",c=i.asyncIterator||"@@asyncIterator",s=i.toStringTag||"@@toStringTag",u="object"==typeof t,f=e.regeneratorRuntime;if(f)u&&(t.exports=f);else{(f=e.regeneratorRuntime=u?t.exports:{}).wrap=x;var l="suspendedStart",h="suspendedYield",p="executing",d="completed",v={},y={};y[a]=function(){return this};var g=Object.getPrototypeOf,m=g&&g(g(k([])));m&&m!==n&&o.call(m,a)&&(y=m);var w=E.prototype=b.prototype=Object.create(y);L.prototype=w.constructor=E,E.constructor=L,E[s]=L.displayName="GeneratorFunction",f.isGeneratorFunction=function(t){var e="function"==typeof t&&t.constructor;return!!e&&(e===L||"GeneratorFunction"===(e.displayName||e.name))},f.mark=function(t){return Object.setPrototypeOf?Object.setPrototypeOf(t,E):(t.__proto__=E,s in t||(t[s]="GeneratorFunction")),t.prototype=Object.create(w),t},f.awrap=function(t){return{__await:t}},R(O.prototype),O.prototype[c]=function(){return this},f.AsyncIterator=O,f.async=function(t,e,r,n){var o=new O(x(t,e,r,n));return f.isGeneratorFunction(e)?o:o.next().then(function(t){return t.done?t.value:o.next()})},R(w),w[s]="Generator",w[a]=function(){return this},w.toString=function(){return"[object Generator]"},f.keys=function(t){var e=[];for(var r in t)e.push(r);return e.reverse(),function r(){for(;e.length;){var n=e.pop();if(n in t)return r.value=n,r.done=!1,r}return r.done=!0,r}},f.values=k,P.prototype={constructor:P,reset:function(t){if(this.prev=0,this.next=0,this.sent=this._sent=r,this.done=!1,this.delegate=null,this.method="next",this.arg=r,this.tryEntries.forEach(C),!t)for(var e in this)"t"===e.charAt(0)&&o.call(this,e)&&!isNaN(+e.slice(1))&&(this[e]=r)},stop:function(){this.done=!0;var t=this.tryEntries[0].completion;if("throw"===t.type)throw t.arg;return this.rval},dispatchException:function(t){if(this.done)throw t;var e=this;function n(n,o){return c.type="throw",c.arg=t,e.next=n,o&&(e.method="next",e.arg=r),!!o}for(var i=this.tryEntries.length-1;i>=0;--i){var a=this.tryEntries[i],c=a.completion;if("root"===a.tryLoc)return n("end");if(a.tryLoc<=this.prev){var s=o.call(a,"catchLoc"),u=o.call(a,"finallyLoc");if(s&&u){if(this.prev=0;--r){var n=this.tryEntries[r];if(n.tryLoc<=this.prev&&o.call(n,"finallyLoc")&&this.prev=0;--e){var r=this.tryEntries[e];if(r.finallyLoc===t)return this.complete(r.completion,r.afterLoc),C(r),v}},catch:function(t){for(var e=this.tryEntries.length-1;e>=0;--e){var r=this.tryEntries[e];if(r.tryLoc===t){var n=r.completion;if("throw"===n.type){var o=n.arg;C(r)}return o}}throw new Error("illegal catch attempt")},delegateYield:function(t,e,n){return this.delegate={iterator:k(t),resultName:e,nextLoc:n},"next"===this.method&&(this.arg=r),v}}}function x(t,e,r,n){var o=e&&e.prototype instanceof b?e:b,i=Object.create(o.prototype),a=new P(n||[]);return i._invoke=function(t,e,r){var n=l;return function(o,i){if(n===p)throw new Error("Generator is already running");if(n===d){if("throw"===o)throw i;return F()}for(r.method=o,r.arg=i;;){var a=r.delegate;if(a){var c=j(a,r);if(c){if(c===v)continue;return c}}if("next"===r.method)r.sent=r._sent=r.arg;else if("throw"===r.method){if(n===l)throw n=d,r.arg;r.dispatchException(r.arg)}else"return"===r.method&&r.abrupt("return",r.arg);n=p;var s=_(t,e,r);if("normal"===s.type){if(n=r.done?d:h,s.arg===v)continue;return{value:s.arg,done:r.done}}"throw"===s.type&&(n=d,r.method="throw",r.arg=s.arg)}}}(t,r,a),i}function _(t,e,r){try{return{type:"normal",arg:t.call(e,r)}}catch(t){return{type:"throw",arg:t}}}function b(){}function L(){}function E(){}function R(t){["next","throw","return"].forEach(function(e){t[e]=function(t){return this._invoke(e,t)}})}function O(t){var e;this._invoke=function(r,n){function i(){return new Promise(function(e,i){!function e(r,n,i,a){var c=_(t[r],t,n);if("throw"!==c.type){var s=c.arg,u=s.value;return u&&"object"==typeof u&&o.call(u,"__await")?Promise.resolve(u.__await).then(function(t){e("next",t,i,a)},function(t){e("throw",t,i,a)}):Promise.resolve(u).then(function(t){s.value=t,i(s)},a)}a(c.arg)}(r,n,e,i)})}return e=e?e.then(i,i):i()}}function j(t,e){var n=t.iterator[e.method];if(n===r){if(e.delegate=null,"throw"===e.method){if(t.iterator.return&&(e.method="return",e.arg=r,j(t,e),"throw"===e.method))return v;e.method="throw",e.arg=new TypeError("The iterator does not provide a 'throw' method")}return v}var o=_(n,t.iterator,e.arg);if("throw"===o.type)return e.method="throw",e.arg=o.arg,e.delegate=null,v;var i=o.arg;return i?i.done?(e[t.resultName]=i.value,e.next=t.nextLoc,"return"!==e.method&&(e.method="next",e.arg=r),e.delegate=null,v):i:(e.method="throw",e.arg=new TypeError("iterator result is not an object"),e.delegate=null,v)}function N(t){var e={tryLoc:t[0]};1 in t&&(e.catchLoc=t[1]),2 in t&&(e.finallyLoc=t[2],e.afterLoc=t[3]),this.tryEntries.push(e)}function C(t){var e=t.completion||{};e.type="normal",delete e.arg,t.completion=e}function P(t){this.tryEntries=[{tryLoc:"root"}],t.forEach(N,this),this.reset(!0)}function k(t){if(t){var e=t[a];if(e)return e.call(t);if("function"==typeof t.next)return t;if(!isNaN(t.length)){var n=-1,i=function e(){for(;++n