Skip to content

Commit

Permalink
Merge pull request #5 from optimistdigital/translatable-support
Browse files Browse the repository at this point in the history
Resolve row fields values. Add nova-translatable support
  • Loading branch information
Kaspar Rosin authored Jan 28, 2021
2 parents a0985b9 + b17510a commit 9882a76
Show file tree
Hide file tree
Showing 7 changed files with 256 additions and 52 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.2.0] - 2021-01-18

### Added

- Added `minRows` config option [Artexis10](https://github.com/Artexis10)
- Initializes the minimum amount of rows defined by user [Artexis10](https://github.com/Artexis10)
- Added [nova-translatable](https://github.com/optimistdigital/nova-translatable) support

### Changed

- Reworked `SimpleRepeatable` to resolve each row field's values.

## [1.1.2] - 2021-01-18

### Changed
Expand Down
2 changes: 1 addition & 1 deletion dist/js/field.js

Large diffs are not rendered by default.

8 changes: 3 additions & 5 deletions resources/js/components/DetailField.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ export default {
computed: {
values() {
const headers = this.headers;
let value = this.field.value;
if (!value) return void 0;
Expand All @@ -45,14 +43,14 @@ export default {
},
headers() {
const fields = this.field.repeatableFields;
return fields.map(field => ({ name: field.name, attribute: field.attribute }));
const rows = this.field.rows;
return rows[0].fields.map(field => ({ name: field.name, attribute: field.attribute }));
},
},
methods: {
handleValue(valuesArray) {
const fields = this.field.repeatableFields;
const fields = this.field.fields;
const fieldsWithOptions = fields.filter(field => Array.isArray(field.options) && !!field.options.length);
if (fieldsWithOptions.length > 0) {
Expand Down
93 changes: 54 additions & 39 deletions resources/js/components/FormField.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,26 @@
<div class="flex flex-col">
<!-- Title columns -->
<div class="simple-repeatable-header-row flex border-b border-40 py-2">
<div v-for="(repField, i) in field.repeatableFields" :key="i" class="font-bold text-90 text-md w-full mr-3">
{{ repField.name }}
<div v-for="(rowField, i) in rows[0]" :key="i" class="font-bold text-90 text-md w-full mr-3 flex">
{{ rowField.name }}

<!-- If field is nova-translatable, render seperate locale-tabs -->
<nova-translatable-locale-tabs
style="padding: 0"
class="ml-auto"
v-if="rowField.component === 'translatable-field'"
:locales="getFieldLocales(rowField.translatable.locales)"
:active-locale="activeLocale || getFieldLocales(rowField.translatable.locales)[0].key"
@tabClick="setAllLocales"
@dblclick="setAllLocales"
/>
</div>
</div>

<draggable v-model="fieldsWithValues" :options="{ handle: '.vue-draggable-handle' }">
<draggable v-model="rows" handle=".vue-draggable-handle">
<div
v-for="(fields, i) in fieldsWithValues"
:key="fields[0].attribute"
v-for="(row, i) in rows"
:key="row[0].attribute"
class="simple-repeatable-row flex py-3 pl-3 relative rounded-md"
>
<div class="vue-draggable-handle flex justify-center items-center cursor-pointer">
Expand All @@ -25,10 +36,10 @@

<div class="simple-repeatable-fields-wrapper w-full flex">
<component
v-for="(repField, j) in fields"
v-for="(rowField, j) in row"
:key="j"
:is="`form-${repField.component}`"
:field="repField"
:is="`form-${rowField.component}`"
:field="rowField"
:errors="repeatableErrors[i]"
class="mr-3"
/>
Expand All @@ -37,7 +48,7 @@
<div
class="delete-icon flex justify-center items-center cursor-pointer"
@click="deleteRow(i)"
v-if="field.canDeleteRows"
v-if="canDeleteRows"
>
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" class="fill-current">
<path
Expand All @@ -52,7 +63,7 @@
v-if="canAddRows"
@click="addRow"
class="add-button btn btn-default btn-primary mt-3"
:class="{ 'delete-width': field.canDeleteRows }"
:class="{ 'delete-width': canDeleteRows }"
type="button"
>
{{ __('simpleRepeatable.addRow') }}
Expand All @@ -66,60 +77,56 @@
import Draggable from 'vuedraggable';
import { Errors } from 'form-backend-validation';
import { FormField, HandlesValidationErrors } from 'laravel-nova';
import NovaTranslatableSupport from '../mixins/NovaTranslatableSupport';
let UNIQUE_ID_INDEX = 0;
export default {
mixins: [FormField, HandlesValidationErrors],
mixins: [FormField, HandlesValidationErrors, NovaTranslatableSupport],
components: { Draggable },
props: ['resourceName', 'resourceId', 'field'],
data() {
return {
fieldsWithValues: [],
rows: [],
};
},
methods: {
setInitialValue() {
let value = [];
try {
if (!this.field.value) {
value = [];
} else if (typeof this.field.value === 'string') {
value = JSON.parse(this.field.value) || [];
} else if (Array.isArray(this.field.value)) {
value = this.field.value;
}
} catch (e) {
value = [];
}
this.rows = this.field.rows.map(row => this.copyFields(row.fields));
this.fieldsWithValues = value.map(this.copyFields);
// Initialize minimum amount of rows
if (this.field.minRows && !isNaN(this.field.minRows)) {
while (this.rows.length < this.field.minRows) this.addRow();
}
},
copyFields(value) {
return this.field.repeatableFields.map(field => ({
copyFields(fields) {
// Return an array of fields with unique attribute
return fields.map(field => ({
...field,
attribute: `${field.attribute}---${UNIQUE_ID_INDEX++}`,
value: value && value[field.attribute],
value: field.value,
}));
},
fill(formData) {
const allValues = [];
for (const fields of this.fieldsWithValues) {
for (const row of this.rows) {
let formData = new FormData();
const rowValues = {};
for (const field of fields) {
const formData = new FormData();
field.fill(formData);
// Fill formData with field values
row.forEach(field => field.fill(formData));
const normalizedAttribute = field.attribute.replace(/---\d+/, '');
rowValues[normalizedAttribute] = formData.get(field.attribute);
// Save field values to rowValues
for (const item of formData) {
const normalizedAttribute = item[0].replace(/---\d+/, '');
rowValues[normalizedAttribute] = item[1];
}
allValues.push(rowValues);
Expand All @@ -129,11 +136,11 @@ export default {
},
addRow() {
this.fieldsWithValues.push(this.copyFields());
this.rows.push(this.copyFields(this.field.fields));
},
deleteRow(index) {
this.fieldsWithValues.splice(index, 1);
this.rows.splice(index, 1);
},
},
Expand Down Expand Up @@ -166,7 +173,13 @@ export default {
canAddRows() {
if (!this.field.canAddRows) return false;
if (!!this.field.maxRows) return this.fieldsWithValues.length < this.field.maxRows;
if (!!this.field.maxRows) return this.rows.length < this.field.maxRows;
return true;
},
canDeleteRows() {
if (!this.field.canDeleteRows) return false;
if (!!this.field.minRows) return this.rows.length > this.field.minRows;
return true;
},
},
Expand All @@ -183,10 +196,12 @@ export default {
width: calc(100% + 68px);
> .simple-repeatable-fields-wrapper {
> * {
> *,
// Improve compatibility with nova-translatable
.translatable-field:nth-child(2) div {
flex: 1;
flex-shrink: 0;
min-width: 0px;
min-width: 0;
border: none !important;
// Hide name
Expand Down
31 changes: 31 additions & 0 deletions resources/js/mixins/NovaTranslatableSupport.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
export default {
data() {
return {
activeLocale: null,
};
},

beforeMount() {
Nova.$on('nova-translatable@setAllLocale', this.setActiveLocale);
},

methods: {
getFieldLocales(locales) {
return Object.keys(locales)
.sort((a, b) => {
if (a === Nova.config.locale && b !== Nova.config.locale) return -1;
if (a !== Nova.config.locale && b === Nova.config.locale) return 1;
return 0;
})
.map(key => ({ key, name: locales[key] }));
},

setAllLocales(newLocale) {
Nova.$emit('nova-translatable@setAllLocale', newLocale);
},

setActiveLocale(newLocale) {
this.activeLocale = newLocale;
},
},
};
69 changes: 69 additions & 0 deletions src/Row.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

namespace OptimistDigital\NovaSimpleRepeatable;

use JsonSerializable;
use Laravel\Nova\Fields\Field;
use Laravel\Nova\Fields\FieldCollection;

class Row implements JsonSerializable
{
protected $fields;
protected $attributes;

public function __construct($fields = null, $attributes = [])
{
$this->fields = new FieldCollection($fields);
$this->attributes = $attributes;
}

/**
* Get a cloned instance with set values
*
* @param array $attributes
* @return Row
*/
public function duplicateAndHydrate(array $attributes = [])
{
return new static ($this->fields->map(function ($field) {
return $this->cloneField($field);
}), $attributes);
}

/**
* Resolve fields using given attributes.
*
* @return void
*/
public function resolve()
{
$this->fields->each->resolve($this->attributes);
}

/**
* Create a working field clone instance
*
* @param \Laravel\Nova\Fields\Field $original
* @return \Laravel\Nova\Fields\Field
*/
protected function cloneField(Field $original)
{
$field = clone $original;

$callables = ['displayCallback', 'resolveCallback', 'fillCallback', 'requiredCallback'];

foreach ($callables as $callable) {
if (!is_a($field->$callable ?? null, \Closure::class)) continue;
$field->$callable = $field->$callable->bindTo($field);
}

return $field;
}

public function jsonSerialize()
{
return [
'fields' => $this->fields->jsonSerialize(),
];
}
}
Loading

0 comments on commit 9882a76

Please sign in to comment.