├── .eslintrc.js ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .php-cs-fixer.dist.php ├── changelog.md ├── composer.json ├── dist ├── css │ └── field.css ├── js │ ├── field.js │ └── field.js.LICENSE.txt └── mix-manifest.json ├── package.json ├── readme.md ├── resources ├── css │ └── field.css └── js │ ├── components │ ├── DetailField.vue │ ├── FormField.vue │ └── IndexField.vue │ └── field.js ├── src ├── AjaxField.php └── FieldServiceProvider.php ├── version.txt ├── webpack.mix.js └── yarn.lock /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: [ 4 | "eslint:recommended", 5 | "plugin:vue/essential" 6 | ], 7 | "globals": { 8 | "Nova": true, 9 | "store": true, 10 | }, 11 | rules: { 12 | // override/add rules settings here... 13 | "indent": ["error", "tab"] 14 | }, 15 | env: { 16 | node: true, 17 | }, 18 | } -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI Checks 2 | 3 | on: [push] 4 | 5 | jobs: 6 | php-ci: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v1 11 | - name: Install depenedencies 12 | uses: php-actions/composer@v1 13 | - name: Run linter 14 | run: composer run lint 15 | 16 | npm-ci: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v1 20 | - name: Use Node.js 21 | uses: actions/setup-node@v3 22 | - name: Install depenedencies 23 | run: npm install 24 | - name: Run linter 25 | run: npm run lint 26 | - name: Build assets 27 | run: npm run prod 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /vendor 3 | /node_modules 4 | package-lock.json 5 | composer.phar 6 | composer.lock 7 | phpunit.xml 8 | .phpunit.result.cache 9 | .DS_Store 10 | Thumbs.db 11 | -------------------------------------------------------------------------------- /.php-cs-fixer.dist.php: -------------------------------------------------------------------------------- 1 | setRules([ 6 | '@Symfony' => true, 7 | '@PHP71Migration' => true, 8 | 'array_syntax' => ['syntax' => 'short'], 9 | 'method_chaining_indentation' => true, 10 | 'no_useless_else' => true, 11 | 'no_useless_return' => true, 12 | 'ordered_class_elements' => true, 13 | 'ordered_imports' => true, 14 | 'phpdoc_order' => true, 15 | 'strict_comparison' => true, 16 | 'strict_param' => true, 17 | 'yoda_style' => false, 18 | 'full_opening_tag' => true, 19 | 'indentation_type' => true, 20 | ]) 21 | ->setIndent("\t") 22 | ->setLineEnding("\n") 23 | ->setRiskyAllowed(true) 24 | ->setFinder( 25 | PhpCsFixer\Finder::create() 26 | ->in([ 27 | 'src', 28 | ]) 29 | ); 30 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | 9 | ## 1.3.1 - 2024-11-14 10 | 11 | - Fixed the #0f0 green 12 | - Correctly fire the parent field observer 13 | 14 | ## 1.3.0 - 2024-11-14 15 | 16 | - Fixed selected parent option not being passed correctly to the ajax endpoint 17 | - Added Custom stylesheet to help dark mode conflicts 18 | 19 | ## 1.2.3 - 2024-11-06 20 | 21 | - Updated dist bundle 22 | 23 | ## 1.2.2 - 2024-11-06 24 | 25 | - Fix to vue-select 3.x returning an instance of the event on change, rather than the updated value 26 | - `root` declaration on eslint config to fix deprecation warnings 27 | 28 | ## 1.2.1 - 2024-11-05 29 | 30 | - Remove Nova secrets from being required in the CI runner action during Composer setup 31 | 32 | ## 1.2.0 - 2024-11-05 33 | 34 | - Fixed javascript error when inside a flexible componet 35 | - Any Ajax Request results are now stored in a cached variable so label isn't lost on search 36 | - Use Nova Placeholder before name on search input 37 | 38 | ## 1.0.1 - 2022-05-11 39 | 40 | - Added filterable option flag, contributed by https://github.com/nea 41 | 42 | ## 1.0.0 - 2022-04-07 43 | 44 | - Update to support Nova 4.0 45 | ## 0.3.5 - 2020-08-18 46 | 47 | - Fixed bug where if the inital value was an array the options array would not be populated correctly 48 | 49 | ## 0.3.4 - 2020-08-18 50 | 51 | - Rebuilt assets 52 | 53 | ## 0.3.3 - 2020-08-18 54 | 55 | - Removed some left over code 56 | 57 | ## 0.3.2 - 2020-07-09 58 | 59 | - Fixed an empty string value being selected with multi method 60 | 61 | ## 0.3.1 - 2020-07-09 62 | 63 | - Fixed responsive multiselect fields losing their values on search 64 | 65 | ## 0.3.0 - 2020-07-02 66 | 67 | - Add new responsive() method that will trigger ajax select on text change 68 | 69 | ## 0.2.3 - 2020-07-01 70 | 71 | - Dist files updated 72 | 73 | ## 0.2.2 - 2020-07-01 74 | 75 | - Fixed bug with passing params in get request 76 | 77 | ## 0.2.1 - 2020-07-01 78 | 79 | - Add ability to add a parent field reference to pass through filter to ajax endpoint 80 | 81 | ## 0.2.0 - 2020-06-30 82 | 83 | - Added the ability to enable multiselect. 84 | - Fixed `labelKey` missing in vue component. 85 | 86 | ## 0.1.0 - 2020-06-30 87 | 88 | - Initial release. 89 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "razorcreations/ajax-field", 3 | "description": "A searchable AJAX powered field for Laravel Nova 3 and 4.", 4 | "version": "1.3.2", 5 | "authors": [ 6 | { 7 | "name": "Ross Cooper", 8 | "email": "ross@razorcreations.com", 9 | "homepage": "http://www.razorcreations.com", 10 | "role": "Developer" 11 | }, 12 | { 13 | "name": "Tom Stowe", 14 | "email": "tom@razorcreations.com", 15 | "homepage": "http://www.razorcreations.com", 16 | "role": "Developer" 17 | }, 18 | { 19 | "name": "Gary Garside", 20 | "email": "gary@razorcreations.com", 21 | "homepage": "http://www.razorcreations.com", 22 | "role": "Developer" 23 | } 24 | ], 25 | "keywords": [ 26 | "laravel", 27 | "nova", 28 | "field", 29 | "ajax" 30 | ], 31 | "license": "MIT", 32 | "require": { 33 | "php": "^7.3|^8.0" 34 | }, 35 | "autoload": { 36 | "psr-4": { 37 | "Razorcreations\\AjaxField\\": "src/" 38 | } 39 | }, 40 | "extra": { 41 | "laravel": { 42 | "providers": [ 43 | "Razorcreations\\AjaxField\\FieldServiceProvider" 44 | ] 45 | } 46 | }, 47 | "config": { 48 | "sort-packages": true 49 | }, 50 | "minimum-stability": "dev", 51 | "prefer-stable": true, 52 | "require-dev": { 53 | "friendsofphp/php-cs-fixer": "^3.8" 54 | }, 55 | "scripts": { 56 | "fix": "./vendor/bin/php-cs-fixer fix", 57 | "lint": "./vendor/bin/php-cs-fixer fix --dry-run" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /dist/css/field.css: -------------------------------------------------------------------------------- 1 | .dark .vs--single .vs__selected{color:#0ca5e9!important} 2 | -------------------------------------------------------------------------------- /dist/js/field.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /*! 2 | * Determine if an object is a Buffer 3 | * 4 | * @author Feross Aboukhadijeh 5 | * @license MIT 6 | */ 7 | 8 | /** 9 | * @license 10 | * Lodash 11 | * Copyright JS Foundation and other contributors 12 | * Released under MIT license 13 | * Based on Underscore.js 1.8.3 14 | * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 15 | */ 16 | 17 | /** 18 | * @license 19 | * Lodash 20 | * Copyright OpenJS Foundation and other contributors 21 | * Released under MIT license 22 | * Based on Underscore.js 1.8.3 23 | * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 24 | */ 25 | -------------------------------------------------------------------------------- /dist/mix-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "/js/field.js": "/js/field.js", 3 | "/css/field.css": "/css/field.css" 4 | } 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "dev": "npm run development", 5 | "development": "mix", 6 | "watch": "mix watch", 7 | "watch-poll": "mix watch -- --watch-options-poll=1000", 8 | "hot": "mix watch --hot", 9 | "prod": "npm run production", 10 | "production": "mix --production", 11 | "fix": "eslint --fix --ext .js,.vue resources", 12 | "lint": "eslint --ext .js,.vue resources" 13 | }, 14 | "devDependencies": { 15 | "cross-env": "^5.0.0", 16 | "eslint": "^7.3.1", 17 | "eslint-plugin-vue": "^7.0.0-alpha.8", 18 | "laravel-mix": "^6.0", 19 | "laravel-nova": "^1.0", 20 | "vue-loader": "^16.8.3" 21 | }, 22 | "dependencies": { 23 | "lodash": "^4.17.21", 24 | "vue": "^3.0", 25 | "vue-select": "^4.0.0-beta.3" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # AJAX Field for Laravel Nova 2 | 3 | [![Latest Stable Version](https://poser.pugx.org/razorcreations/ajax-field/v)](//packagist.org/packages/razorcreations/ajax-field) [![Total Downloads](https://poser.pugx.org/razorcreations/ajax-field/downloads)](//packagist.org/packages/razorcreations/ajax-field) [![Latest Unstable Version](https://poser.pugx.org/razorcreations/ajax-field/v/unstable)](//packagist.org/packages/razorcreations/ajax-field) [![License](https://poser.pugx.org/razorcreations/ajax-field/license)](//packagist.org/packages/razorcreations/ajax-field) 4 | 5 | ![Preview](http://g.recordit.co/kQljVQNidD.gif) 6 | 7 | ## Installation 8 | 9 | `composer require razorcreations/ajax-field` 10 | 11 | For Nova 3 support use 12 | 13 | `composer require razorcreations/ajax-field@0.3.2` 14 | 15 | ## Usage 16 | 17 | ```php 18 | 19 | use Razorcreations\AjaxField\AjaxField; 20 | 21 | // Inside your resources fields definition 22 | AjaxField::make('Foo')->setUrl('/api/ajaxselect/foo'), 23 | 24 | // If you are using integers or floats for the values ensure to chain on the type methods... 25 | AjaxField::make('Foo')->setUrl('/api/ajaxselect/foo')->typeInt(), 26 | ``` 27 | 28 | The field expects the response from the AJAX call to respond with a JSON array of options in the following format: 29 | ```json 30 | [ 31 | { 32 | "label": "Bob", 33 | "value": 1, 34 | }, 35 | { 36 | "label": "Jenny", 37 | "value": 2, 38 | } 39 | ] 40 | ``` 41 | If you wish you can override the default keys of "value" and "label" using the following methods: 42 | ```php 43 | AjaxField::make('Foo')->setUrl('/api/ajaxselect/foo')->setValueKey('id')->setLabelKey('name'), 44 | ``` 45 | 46 | You can pass through another Nova fields value by adding the parent method with the key of the nova field 47 | ```php 48 | // Create a parent field 49 | Text::make('Foo', 'foo'); 50 | 51 | // Create ajax field, with parent method 52 | AjaxField::make('Bar')->setUrl('/api/ajaxselect/foo')->parent('foo'), 53 | ``` 54 | This will hit the AjaxUrl with the fields key value pair appended as a get param e.g. /api/ajaxselect/foo?foo=value 55 | 56 | Rather than having the options loaded in once on page load, you can use ->responsive() to pass through the field value when the input value changes 57 | ```php 58 | AjaxField::make('Foo')->setUrl('/api/ajaxselect/foo')->responsive(), 59 | ``` 60 | 61 | 62 | ## Contributing 63 | 64 | If you would like to contribute please fork the project and submit a PR. 65 | 66 | ### Coding Standards 67 | 68 | - `composer run fix` to fix any PHP linting issues automatically 69 | - `composer run lint` to show any PHP linting issues 70 | - `npm run fix` to fix any JS/Vue linting issue automatically 71 | - `npm run lint` to show any JS/Vue linting issues -------------------------------------------------------------------------------- /resources/css/field.css: -------------------------------------------------------------------------------- 1 | /* Nova Field CSS */ 2 | .dark .vs--single .vs__selected { 3 | color: #0ca5e9 !important;; 4 | } -------------------------------------------------------------------------------- /resources/js/components/DetailField.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 13 | -------------------------------------------------------------------------------- /resources/js/components/FormField.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 300 | 301 | -------------------------------------------------------------------------------- /resources/js/components/IndexField.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | -------------------------------------------------------------------------------- /resources/js/field.js: -------------------------------------------------------------------------------- 1 | import IndexField from './components/IndexField' 2 | import DetailField from './components/DetailField' 3 | import FormField from './components/FormField' 4 | 5 | Nova.booting((app) => { 6 | app.component('IndexAjaxField', IndexField) 7 | app.component('DetailAjaxField', DetailField) 8 | app.component('FormAjaxField', FormField) 9 | }) 10 | -------------------------------------------------------------------------------- /src/AjaxField.php: -------------------------------------------------------------------------------- 1 | withMeta([ 31 | 'url' => $url, 32 | ]); 33 | } 34 | 35 | /** 36 | * Sets the key of the selected option that should be saved as the value, default is "value". 37 | */ 38 | public function setValueKey(string $key): self 39 | { 40 | return $this->withMeta([ 41 | 'valueKey' => $key, 42 | ]); 43 | } 44 | 45 | /** 46 | * Sets the key of the selected option that should be displayed, default is "label". 47 | */ 48 | public function setLabelKey(string $key): self 49 | { 50 | return $this->withMeta([ 51 | 'labelKey' => $key, 52 | ]); 53 | } 54 | 55 | /** 56 | * Sets the type of the value being save to an integer. 57 | */ 58 | public function typeInt(): self 59 | { 60 | return $this->setType('int'); 61 | } 62 | 63 | /** 64 | * Sets the type of the value being save to a float. 65 | */ 66 | public function typeFloat(): self 67 | { 68 | return $this->setType('float'); 69 | } 70 | 71 | /** 72 | * Enable multi-select. 73 | * 74 | * Note: values are saved comma seperated 75 | */ 76 | public function multiple(): self 77 | { 78 | return $this->withMeta([ 79 | 'multiple' => true, 80 | ]); 81 | } 82 | 83 | /* 84 | * Adds a param to the ajax call so it can pass through a value from another field 85 | */ 86 | public function parent(string $attribute): self 87 | { 88 | return $this->withMeta([ 89 | 'parent_field' => $attribute, 90 | ]); 91 | } 92 | 93 | /* 94 | * Ajax request to be on text input as opposed to single intitial request 95 | */ 96 | public function responsive(): self 97 | { 98 | return $this->withMeta([ 99 | 'responsive' => true, 100 | 'filterable' => false, // @see https://vue-select.org/guide/ajax.html#disabling-filtering 101 | ]); 102 | } 103 | 104 | /** 105 | * When loading server side options, it can be useful to disable the client side filtering. Use the filterable prop to disable filtering. 106 | * 107 | * @see https://vue-select.org/guide/ajax.html#disabling-filtering 108 | */ 109 | public function filterable(bool $filterable = true): self 110 | { 111 | return $this->withMeta([ 112 | 'filterable' => $filterable, 113 | ]); 114 | } 115 | 116 | private function setType(string $type): self 117 | { 118 | return $this->withMeta([ 119 | 'type' => $type, 120 | ]); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/FieldServiceProvider.php: -------------------------------------------------------------------------------- 1 |