Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

reorder fields for an asset definition #16975

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions inc/define.php
Expand Up @@ -594,6 +594,7 @@
],
],
'config' => [
'assetdefinition' => ['sortable'],
'commondropdown' => [
'ITILFollowupTemplate' => ['tinymce'],
'ProjectTaskTemplate' => ['tinymce'],
Expand Down
3 changes: 2 additions & 1 deletion install/migrations/update_10.0.x_to_11.0.0/assets.php
Expand Up @@ -54,6 +54,7 @@
`capacities` JSON NOT NULL,
`profiles` JSON NOT NULL,
`translations` JSON NOT NULL,
`fields_display ` JSON NOT NULL,
`date_creation` timestamp NULL DEFAULT NULL,
`date_mod` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
Expand All @@ -65,7 +66,7 @@
SQL;
$DB->doQueryOrDie($query);
} else {
foreach (['profiles', 'translations'] as $field) {
foreach (['profiles', 'translations', 'fields_display'] as $field) {
$migration->addField('glpi_assets_assetdefinitions', $field, 'JSON NOT NULL', ['update' => "'[]'"]);
}
}
Expand Down
1 change: 1 addition & 0 deletions install/mysql/glpi-empty.sql
Expand Up @@ -9827,6 +9827,7 @@ CREATE TABLE `glpi_assets_assetdefinitions` (
`capacities` JSON NOT NULL,
`profiles` JSON NOT NULL,
`translations` JSON NOT NULL,
`fields_display` JSON NOT NULL,
`date_creation` timestamp NULL DEFAULT NULL,
`date_mod` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
Expand Down
106 changes: 105 additions & 1 deletion src/Asset/AssetDefinition.php
Expand Up @@ -46,9 +46,14 @@
use Glpi\DBAL\QueryExpression;
use Glpi\DBAL\QueryFunction;
use Glpi\Search\SearchOption;
use Group;
use Location;
use Manufacturer;
use Profile;
use ProfileRight;
use Session;
use State;
use User;

final class AssetDefinition extends CommonDBTM
{
Expand Down Expand Up @@ -110,6 +115,7 @@ public function getTabNameForItem(CommonGLPI $item, $withtemplate = 0)
$translations_count = 0;
if ($_SESSION['glpishow_count_on_tabs']) {
$capacities_count = count($item->getDecodedCapacitiesField());
$fields_count = count($item->getDecodedFieldsField());
$profiles_count = count(array_filter($item->getDecodedProfilesField()));
$translations_count = count($item->getDecodedTranslationsField());
}
Expand All @@ -120,6 +126,12 @@ public function getTabNameForItem(CommonGLPI $item, $withtemplate = 0)
self::class,
'ti ti-adjustments'
),
2 => self::createTabEntry(
__('Fields'),
$fields_count,
self::class,
'ti ti-forms'
),
// 2 is reserved for "Fields"
3 => self::createTabEntry(
_n('Profile', 'Profiles', Session::getPluralNumber()),
Expand Down Expand Up @@ -147,7 +159,7 @@ public static function displayTabContentForItem(CommonGLPI $item, $tabnum = 1, $
$item->showCapacitiesForm();
break;
case 2:
// 2 is reserved for "Fields" form
$item->showFieldsForm();
break;
case 3:
$item->showProfilesForm();
Expand Down Expand Up @@ -223,6 +235,30 @@ private function showCapacitiesForm(): void
);
}


/**
* Display capacities form.
*
* @return void
*/
private function showFieldsForm(): void
{
$fields_display = $this->getDecodedFieldsField();
$used = array_column($fields_display, 'key');
$used = array_combine($used, $used);

TemplateRenderer::getInstance()->display(
'pages/admin/assetdefinition/fields_display.html.twig',
[
'item' => $this,
'classname' => $this->getAssetClassName(),
'all_fields' => $this->getAllFields(),
'fields_display' => $fields_display,
'used' => $used,
]
);
}

/**
* Display profiles form.
*
Expand Down Expand Up @@ -473,6 +509,17 @@ private function prepareInput(array $input): array|bool
}
}

if (array_key_exists('fields_display', $input)) {
$formatted_fields_display = [];
foreach ($input['fields_display'] as $field_order => $field_key) {
$formatted_fields_display[] = [
'order' => $field_order,
'key' => $field_key,
];
}
$input['fields_display'] = json_encode($formatted_fields_display);
}

return $has_errors ? false : $input;
}

Expand Down Expand Up @@ -951,6 +998,63 @@ private function getDecodedCapacitiesField(): array
return $capacities;
}


private function getAllFields(): array
{
$fields = [
'name' => __('Name'),
'comment' => __('Comment'),
'serial' => __('Serial'),
'otherserial' => __('Inventory number'),
'contact' => __('Alternate username'),
'contact_num' => __('Alternate username number'),
'users_id' => User::getTypeName(),
'groups_id' => Group::getTypeName(),
'users_id_tech' => __('Technician in charge'),
'groups_id_tech' => __('Group in charge'),
'locations_id' => Location::getTypeName(),
'manufacturers_id' => Manufacturer::getTypeName(),
'states_id' => State::getTypeName(),
];

// TODO add assets_assetmodels_id and assets_assettypes_id
// TODO add custom fields

return $fields;
}

private function getDefaultFieldsDisplay(): array
{
$all_fields = $this->getAllFields();

$default = [];
$order = 0;
foreach ($all_fields as $key => $label) {
$default[] = [
'key' => $key,
'order' => $order,
];
$order++;
}

return $default;
}


/**
* Return the decoded value of the `capacities` field.
*
* @return array
*/
private function getDecodedFieldsField(): array
{
$fields_display = @json_decode($this->fields['fields_display'], associative: true);
if (!is_array($fields_display) || count($fields_display) === 0) {
return $this->getDefaultFieldsDisplay();
}
return $fields_display;
}

/**
* Validate that the given capacities array contains valid values.
*
Expand Down
138 changes: 138 additions & 0 deletions templates/pages/admin/assetdefinition/fields_display.html.twig
@@ -0,0 +1,138 @@
{#
# ---------------------------------------------------------------------
#
# GLPI - Gestionnaire Libre de Parc Informatique
#
# http://glpi-project.org
#
# @copyright 2015-2024 Teclib' and contributors.
# @licence https://www.gnu.org/licenses/gpl-3.0.html
#
# ---------------------------------------------------------------------
#
# LICENSE
#
# This file is part of GLPI.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
# ---------------------------------------------------------------------
#}

{% extends "generic_show_form.html.twig" %}
{% import 'components/form/fields_macros.html.twig' as fields %}

{% set all_fields = all_fields ?? {} %}

{% macro draggable_field() %}
<div class="g-col-6 sortable-field d-flex align-items-center p-1">
<input type="hidden" name="fields_display[]" value="%field_key%" />
<div class="form-field row flex-grow-1">
<label class="col-form-label cursor-grab col-xxl-5 text-xxl-end">
%field_label%
<i class="ti ti-grip-vertical"></i>
</label>
<div class="col-xxl-7 field-container">
<input type="text" disabled class="form-control" />
</div>
</div>
<button type="button" class="btn btn-ghost-secondary btn-sm ms-2 remove-field" title="{{ __("Remove") }}">
<i class="ti ti-x"></i>
</button>
</div>
{% endmacro %}
{% import _self as my_draggable_macros %}

{% set params = {} %}
{# do not display delete button #}
{% set params = params|merge({'candel': false}) %}
{# do not display footer with dates #}
{% set params = params|merge({'formfooter': false}) %}

{% block form_fields %}
<input type="hidden" name="_update_fields_display" value="1" />

<div class="grid gap-3" id="sortable-fields">
{% for field in fields_display %}
{{ my_draggable_macros.draggable_field()|replace({
'%field_key%': field['key'],
'%field_label%': all_fields[field['key']]
})|raw }}
{% endfor %}
</div>

<div class="row mt-3">
<div class="col-6">
{% do call('Dropdown::showFromArray', ['new_field', all_fields, {
'rand': rand,
'used': used,
}]) %}
<button type="button" class="btn btn-outline-secondary" id="add-field">
<i class="ti ti-plus"></i>
<span>{{ __("Add a field") }}</span>
</button>
</div>
</div>

<style>
.sortable-dragging {
display: none !important; /* d-flex has an important rule */
}
</style>

<script>
$(() => {
// init sortable lib on fields
sortable('#sortable-fields', {
items: '.sortable-field',
placeholderClass: 'sortable-placeholder g-col-6',
forcePlaceholderSize: true,
});

const all_fields = {{ all_fields|json_encode()|raw }};

// add field action
$('#add-field').on('click', function() {
//get select2 value
const field_key = $('#dropdown_new_field{{ rand }}').val();
if (field_key && field_key != 0 ) {
// re-use twig macro to generate the field
let field_html = `{{ my_draggable_macros.draggable_field() }}`;
field_html = field_html.replace('%field_key%', field_key);
field_html = field_html.replace('%field_label%', all_fields[field_key]);

//add field to the list
$('#sortable-fields').append(field_html);

// remove the selected option in select2
$('#dropdown_new_field{{ rand }} option[value=' + field_key + ']').remove()
}
});

// remove field action
$('#sortable-fields').on('click', '.remove-field', function() {
const field_key = $(this).closest('.sortable-field').find('input[type=hidden]').val();

// re-add the option in select2
$('#dropdown_new_field{{ rand }}').append($('<option>', {
value: field_key,
text: all_fields[field_key]
}));

// remove the field from sortable list
$(this).closest('.sortable-field').remove();
});
});
</script>
{% endblock %}