Skip to content

Commit

Permalink
Merge pull request #9 from michielkempen/master
Browse files Browse the repository at this point in the history
Solve issues discussed in #6
  • Loading branch information
Jeroen Nijhuis authored Sep 17, 2018
2 parents 6eafb13 + cd2298b commit 41e448c
Show file tree
Hide file tree
Showing 14 changed files with 261 additions and 204 deletions.
87 changes: 42 additions & 45 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,75 +5,72 @@
[![License](https://img.shields.io/packagist/l/epartment/nova-dependency-container.svg)](https://github.com/epartment/nova-dependency-container/blob/master/LICENSE.md)

### Description

A container for grouping fields that depend on other field values. Dependencies can be set on any field type or value.

### Demo

![Demo](https://raw.githubusercontent.com/epartment/nova-dependency-container/master/docs/demo.gif)

### Installation
Install through composer: `composer require epartment/nova-dependency-container`

### Usage

Add a new `NovaDependencyContainer` to your Nova Resource and extend your resource from `Eparment\NovaDependencyContainer\ResourceWithDependencies`
The package can be installed through Composer.

```php
use \Epartment\NovaDependencyContainer\NovaDependencyContainer;

Select::make('Name format', 'name_format')
->options([
0 => 'First Name',
1 => 'First Name / Last Name',
2 => 'Full Name'
])->displayUsingLabels(),

NovaDependencyContainer::make('Dependent settings', [
\Laravel\Nova\Fields\Text::make('First Name', 'first_name')
])->dependsOn('name_format', 0)->onlyOnForms(),
```bash
composer require epartment/nova-dependency-container
```

### Options
### Usage

It's possible to rely on just a field without requiring a specific value. This is especially for handling relationshop fields, like a `BelongsTo` field.
1. Add the `Eparment\NovaDependencyContainer\HasDependencies` trait to your Nova Resource.
2. Add the `Epartment\NovaDependencyContainer\NovaDependencyContainer` to your Nova Resource `fields` method.

```php
NovaDependencyContainer::make('Dependent settings', [
Text::make('First Name', 'first_name')
])->dependsOnNotEmpy('customer')->onlyOnForms(),
class Page extends Resource
{
use HasDependencies;

public function fields(Request $request)
{
return [

Select::make('Name format', 'name_format')->options([
0 => 'First Name',
1 => 'First Name / Last Name',
2 => 'Full Name'
])->displayUsingLabels(),

NovaDependencyContainer::make([
Text::make('First Name', 'first_name')
])->dependsOn('name_format', 0),

];
}
}
```

It is also possible to set up multiple dependencies for your container by calling `dependsOn` multiple times on the container.
### Dependencies

You can use any type of field type dependency, i.e. a checkbox:
The package supports two kinds of dependencies:

![Demo](https://raw.githubusercontent.com/epartment/nova-dependency-container/master/docs/demo-2.gif)
1. `->dependsOn('field', 'value')`
2. `->dependsOnNotEmpty('field')`

```php
Boolean::make('Active', 'active'),

NovaDependencyContainer::make('Dependent settings', [
Text::make('First Name', 'first_name')
])->dependsOn('active', true)->onlyOnForms(),
```

### 3rd Party support
Using the `dependsOnCustomComponent` method on the Dependency Container, you can target 3rd Party Nova Components. Specifying the exact component name , the Dependency Container will add value watchers to these components, allowing you to depend on their values. You can find the component names `field.js` file in the source of the 3rd party package. Usually they start with `form-{component-name}`.

For example using the https://github.com/davidpiesse/nova-toggle field, you can get the name of the component here: https://github.com/davidpiesse/nova-toggle/blob/master/resources/js/field.js#L7 and use it like below:
These dependencies can be combined by chaining the methods on the `NovaDependencyContainer`:

```php
use \Davidpiesse\NovaToggle\Toggle;
NovaDependencyContainer::make(...)
->dependsOn('field1', 'value1')
->dependsOnNotEmpty('field2')
->dependsOn('field3', 'value3')
```

Toggle::make('Our Option', 'selectable_option')->hideFromIndex(),
The fields used as dependencies can by of any of the default Laravel Nova field types.

NovaDependencyContainer::make('Dependent settings', [
Trix::make('Details', 'selectable_option_details'),
])->dependsOn('selectable_option', true)->dependsOnCustomComponent('form-nova-toggle'),
```
For example a checkbox:

### Data handling
The container it self won't contain any values and will simply pass through the values of the contained fields and their corresponding attributes.
![Demo](https://raw.githubusercontent.com/epartment/nova-dependency-container/master/docs/demo-2.gif)

### License

The MIT License (MIT). Please see [License File](https://github.com/epartment/nova-dependency-container/blob/master/LICENSE.md) for more information.
Empty file removed dist/css/field.css
Empty file.
2 changes: 1 addition & 1 deletion dist/js/field.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions dist/js/field.js.map

Large diffs are not rendered by default.

3 changes: 1 addition & 2 deletions mix-manifest.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
{
"/dist/js/field.js": "/dist/js/field.js",
"/dist/css/field.css": "/dist/css/field.css"
"/dist/js/field.js": "/dist/js/field.js"
}
44 changes: 44 additions & 0 deletions resources/js/components/DetailField.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<template>
<div v-if="dependenciesSatisfied">
<div v-for="childField in field.fields">
<component
:is="'detail-' + childField.component"
:resource-id="resourceId"
:resource-name="resourceName"
:field="childField"
:ref="'field-' + childField.attribute"
/>
</div>
</div>
</template>

<script>
export default {
props: ['resource', 'resourceName', 'resourceId', 'field'],
created() {
this.updateDependencyStatus()
},
data() {
return {
dependenciesSatisfied: false,
}
},
methods: {
updateDependencyStatus() {
for (let dependency of this.field.dependencies) {
if(! dependency.satisfied) {
this.dependenciesSatisfied = false;
return;
}
}
this.dependenciesSatisfied = true;
},
}
}
</script>
186 changes: 89 additions & 97 deletions resources/js/components/FormField.vue
Original file line number Diff line number Diff line change
@@ -1,102 +1,94 @@
<template>
<div v-show="show">
<div v-for="field in childFields">
<component
:is="'form-' + field.component"
:resource-id="resourceId"
:resource-name="resourceName"
:field="field"
:ref="'field-' + field.attribute"
/>
</div>
</div>
<div v-if="dependenciesSatisfied">
<div v-for="childField in field.fields">
<component
:is="'form-' + childField.component"
:resource-id="resourceId"
:resource-name="resourceName"
:field="childField"
:ref="'field-' + childField.attribute"
/>
</div>
</div>
</template>

<script>
import {FormField, HandlesValidationErrors} from 'laravel-nova'
export default {
mixins: [FormField, HandlesValidationErrors],
props: ['resourceName', 'resourceId', 'field'],
created() {
this.field.fields.forEach(f => {
f.value = this.field.value[f.attribute]
})
},
mounted() {
this.field.dependencies.forEach(dependency => {
Nova.$on(dependency.field + '-value-changed', (e) => {
this.compareValues[dependency.field] = e.value
var vm = this
vm.show = true
this.field.dependencies.forEach(dependency => {
if (dependency.notEmpty) {
if (this.compareValues[dependency.field] == '' || this.compareValues[dependency.field] == null) {
vm.show = false
}
} else {
if (dependency.value !== this.compareValues[dependency.field]) {
vm.show = false
}
}
})
})
})
this.registerValueWatchers(this.$root)
},
data() {
return {
compareValues: {},
show: false
}
},
computed: {
childFields() {
return this.field.fields
}
},
methods: {
registerValueWatchers (root) {
root.$children.forEach(component => {
if (component.constructor.options !== undefined && component.constructor.options.name !== undefined) {
if (component.constructor.options.name.endsWith('-field') || this.field.depends_custom.includes(component.constructor.options.name)) {
component.$watch('value', (value, oldValue) => {
Nova.$emit(component.field.attribute + '-value-changed', {
field: component.field,
value: component.value
})
}, {immediate: true})
component.$watch('selectedResource.value', (value, oldValue) => {
Nova.$emit(component.field.attribute + '-value-changed', {
field: component.field,
value: component.selectedResource != null ? component.selectedResource.value : component.value
})
}, {immediate: true})
}
}
this.registerValueWatchers(component)
})
},
/**
* Fill the given FormData object with the field's internal value.
*/
fill(formData) {
this.field.fields.forEach(f => {
formData.append(f.attribute, this.$refs['field-' + f.attribute][0].value)
})
}
}
}
import {FormField, HandlesValidationErrors} from 'laravel-nova'
export default {
mixins: [FormField, HandlesValidationErrors],
props: ['resourceName', 'resourceId', 'field'],
mounted() {
this.registerDependencyWatchers(this.$root)
this.updateDependencyStatus()
},
data() {
return {
dependencyValues: {},
dependenciesSatisfied: false,
}
},
methods: {
registerDependencyWatchers(root) {
root.$children.forEach(component => {
if (this.componentIsDependency(component)) {
component.$watch('value', (value) => {
this.dependencyValues[component.field.attribute] = value;
this.updateDependencyStatus()
}, {immediate: true})
this.dependencyValues[component.field.attribute] = component.field.value;
}
this.registerDependencyWatchers(component)
})
},
componentIsDependency(component) {
if(component.field === undefined) {
return false;
}
for (let dependency of this.field.dependencies) {
if(component.field.attribute === dependency.field) {
return true;
}
}
return false;
},
updateDependencyStatus() {
for (let dependency of this.field.dependencies) {
if(dependency.hasOwnProperty('notEmpty') && ! this.dependencyValues[dependency.field]) {
this.dependenciesSatisfied = false;
return;
}
if(dependency.hasOwnProperty('value') && this.dependencyValues[dependency.field] !== dependency.value) {
this.dependenciesSatisfied = false;
return;
}
}
this.dependenciesSatisfied = true;
},
fill(formData) {
if(this.dependenciesSatisfied) {
this.$children.forEach(f => {
f.fill(formData)
})
}
}
}
}
</script>
1 change: 1 addition & 0 deletions resources/js/field.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
Nova.booting((Vue, router) => {
Vue.component('detail-nova-dependency-container', require('./components/DetailField'));
Vue.component('form-nova-dependency-container', require('./components/FormField'));
})
1 change: 0 additions & 1 deletion resources/sass/field.scss

This file was deleted.

11 changes: 0 additions & 11 deletions src/FieldServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,6 @@ public function boot()
{
Nova::serving(function (ServingNova $event) {
Nova::script('nova-dependency-container', __DIR__.'/../dist/js/field.js');
Nova::style('nova-dependency-container', __DIR__.'/../dist/css/field.css');
});
}

/**
* Register any application services.
*
* @return void
*/
public function register()
{
//
}
}
Loading

0 comments on commit 41e448c

Please sign in to comment.