N°931 TagSet widget and its POC are now more generic (to be used in all AttributeSet hierarchy)

This commit is contained in:
Pierre Goiffon
2018-09-26 10:39:23 +02:00
parent e1f96974bb
commit 1530bb89fe
7 changed files with 146 additions and 166 deletions

View File

@@ -2041,15 +2041,15 @@ EOF
case 'TagSet':
$oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'/js/selectize.min.js');
$oPage->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/selectize.default.css');
$oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'/js/jquery.itop-tagset-widget.js');
$oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'/js/jquery.itop-set-widget.js');
$oPage->add_dict_entry('Core:AttributeTagSet:placeholder');
$oPage->add_dict_entry('Core:AttributeSet:placeholder');
/** @var \ormTagSet $value */
$sJson = $oAttDef->GetJsonForWidget($value);
$sInputId = "attr_{$sFormPrefix}{$sAttCode}";
$sHTMLValue = "<div class=\"field_input_zone field_input_tagset\"><input id='$sInputId' name='$sInputId' type='hidden' value='$sJson'></div>{$sValidationSpan}{$sReloadSpan}";
$sScript = "$('#$sInputId').tagset_widget();";
$sScript = "$('#$sInputId').set_widget();";
$oPage->add_ready_script($sScript);
break;

View File

@@ -6717,7 +6717,7 @@ class AttributeTagSet extends AttributeDBFieldVoid
/**
* @param \ormTagSet $oValue
*
* @return string JSON to be used in the itop.tagset_widget JQuery widget
* @return string JSON to be used in the itop.set_widget JQuery widget
* @throws \CoreException
*/
public function GetJsonForWidget($oValue)
@@ -6750,7 +6750,7 @@ class AttributeTagSet extends AttributeDBFieldVoid
$aJson['removed'] = array();
$iMaxTags = $this->GetTagMaxNb();
$aJson['max_tags_allowed'] = $iMaxTags;
$aJson['max_items_allowed'] = $iMaxTags;
return json_encode($aJson);
}

View File

@@ -36,7 +36,7 @@ Dict::Add('EN US', 'English', 'English', array(
'Core:AttributeTagSet' => 'List of tags',
'Core:AttributeTagSet+' => '',
'Core:AttributeTagSet:placeholder' => 'click to add',
'Core:AttributeSet:placeholder' => 'click to add',
'Core:AttributeCaseLog' => 'Log',
'Core:AttributeCaseLog+' => '',

View File

@@ -432,7 +432,7 @@ Dict::Add('FR FR', 'French', 'Français', array(
'Core:AttributeLinkedSet+' => 'Liste d\'objets d\'une classe donnée et pointant sur l\'objet courant',
'Core:AttributeTagSet' => 'Liste d\'étiquettes',
'Core:AttributeTagSet+' => '',
'Core:AttributeTagSet:placeholder' => 'cliquer pour ajouter',
'Core:AttributeSet:placeholder' => 'cliquer pour ajouter',
'Core:AttributeLinkedSetIndirect' => 'Objets liés (1-n)',
'Core:AttributeLinkedSetIndirect+' => 'Liste d\'objets d\'une classe donnée et liés à l\'objet courant via une classe intermédiaire',
'Core:AttributeInteger' => 'Nombre entier',

View File

@@ -41,6 +41,7 @@
* "label": "don't worry ;)"
* }
* ],
* "max_items_allowed": 20,
* "partial_values": [],
* "orig_value": [
* "critical"
@@ -55,10 +56,10 @@
* </code>
*
* <p>Needs js/selectize.js already loaded !! (https://github.com/selectize/selectize.js)<br>
* In the future we could use a solution like this :
* In the future we could use WebPack... Or a solution like this :
* https://www.safaribooksonline.com/library/view/learning-javascript-design/9781449334840/ch13s09.html
*/
$.widget('itop.tagset_widget',
$.widget('itop.set_widget',
{
// default options
options: {isDebug: false},
@@ -71,23 +72,23 @@ $.widget('itop.tagset_widget',
STATUS_ADDED: "added",
STATUS_REMOVED: "removed",
STATUS_NEUTRAL: "unchanged",
MAX_TAGS_ALLOWED: "max_tags_allowed",
MAX_ITEMS_ALLOWED_KEY: "max_items_allowed",
possibleValues: null,
partialValues: null,
originalValue: null,
/** will hold all interactions done : code as key and one of STATUS_* constant as value */
tagSetCodesStatus: null,
setItemsCodesStatus: null,
selectizeWidget: null,
maxTagsAllowed: null,
maxItemsAllowed: null,
// the constructor
_create: function () {
var $this = this.element;
this._initWidgetData($this.val());
this._generateTagSetField($this);
this._generateSelectionWidget($this);
},
// events bound via _bind are removed automatically
@@ -102,44 +103,44 @@ $.widget('itop.tagset_widget',
this.possibleValues = dataArray[this.POSSIBLE_VAL_KEY];
this.partialValues = ($.isArray(dataArray[this.PARTIAL_VAL_KEY])) ? dataArray[this.PARTIAL_VAL_KEY] : [];
this.originalValue = dataArray[this.ORIG_VAL_KEY];
this.maxTagsAllowed = dataArray[this.MAX_TAGS_ALLOWED];
this.tagSetCodesStatus = {};
this.maxItemsAllowed = dataArray[this.MAX_ITEMS_ALLOWED_KEY];
this.setItemsCodesStatus = {};
},
_generateTagSetField: function ($widgetElement) {
_generateSelectionWidget: function ($widgetElement) {
var $parentElement = $widgetElement.parent(),
inputId = $widgetElement.attr("id") + "-tagset-values";
inputId = $widgetElement.attr("id") + "-setwidget-values";
$parentElement.append("<input id='" + inputId + "' value='" + this.originalValue.join(" ") + "'>");
var $inputWidget = $("#" + inputId);
// create closure to have both tagset widget and Selectize instances available in callbacks
// create closure to have both set widget and Selectize instances available in callbacks
// selectize instance could also be retrieve on the source input DOM node (selectize property)
// I think this is much clearer this way !
var tagSetWidget = this;
var setWidget = this;
$inputWidget.selectize({
plugins: ['remove_button'],
delimiter: ' ',
maxItems: this.maxTagsAllowed,
maxItems: this.maxItemsAllowed,
hideSelected: true,
valueField: 'code',
labelField: 'label',
searchField: 'label',
options: this.possibleValues,
create: false,
placeholder: Dict.S("Core:AttributeTagSet:placeholder"),
placeholder: Dict.S("Core:AttributeSet:placeholder"),
onInitialize: function () {
var selectizeWidget = this;
tagSetWidget._onInitialize(selectizeWidget);
setWidget._onInitialize(selectizeWidget);
},
onItemAdd: function (value, $item) {
var selectizeWidget = this;
tagSetWidget._onTagAdd(value, $item, selectizeWidget);
setWidget._onTagAdd(value, $item, selectizeWidget);
},
onItemRemove: function (value) {
var selectizeWidget = this;
tagSetWidget._onTagRemove(value, selectizeWidget);
setWidget._onTagRemove(value, selectizeWidget);
}
});
@@ -156,14 +157,14 @@ $.widget('itop.tagset_widget',
widgetPublicData[this.PARTIAL_VAL_KEY] = this.partialValues;
widgetPublicData[this.ORIG_VAL_KEY] = this.originalValue;
for (var tagSetCode in this.tagSetCodesStatus) {
var tagSetCodeStatus = this.tagSetCodesStatus[tagSetCode];
switch (tagSetCodeStatus) {
for (var setItemCode in this.setItemsCodesStatus) {
var setItemCodeStatus = this.setItemsCodesStatus[setItemCode];
switch (setItemCodeStatus) {
case this.STATUS_ADDED:
addedValues.push(tagSetCode);
addedValues.push(setItemCode);
break;
case this.STATUS_REMOVED:
removedValues.push(tagSetCode);
removedValues.push(setItemCode);
break;
}
}
@@ -183,59 +184,59 @@ $.widget('itop.tagset_widget',
/**
* Updating items to have a specific rendering for partial codes.<br>
* At first I was thinking about using the Selectize render callback, but it is called before onItemAdd/onItemRemove :(<br>
* At first I was thinking about using the Selectize <code>render</code> callback, but it is called before <code>onItemAdd</code>/<code>onItemRemove</code> :(<br>
* Indeed as we only need to have partial items on first display, this callback is the right place O:)
* @param inputWidget Selectize object
* @param selectionWidget Selectize object
* @private
*/
_onInitialize: function (inputWidget) {
_onInitialize: function (selectionWidget) {
if (this.options.isDebug) {
console.debug("onInit", this);
}
var tagSetWidget = this;
inputWidget.items.forEach(function (tagSetCode) {
if (tagSetWidget._isCodeInPartialValues(tagSetCode)) {
inputWidget.getItem(tagSetCode).addClass("partial-code");
var setWidget = this;
selectionWidget.items.forEach(function (setItemCode) {
if (setWidget._isCodeInPartialValues(setItemCode)) {
selectionWidget.getItem(setItemCode).addClass("partial-code");
}
});
},
_onTagAdd: function (tagSetCode, $item, inputWidget) {
_onTagAdd: function (setItemCode, $item, inputWidget) {
if (this.options.isDebug) {
console.debug("tagAdd");
}
this.tagSetCodesStatus[tagSetCode] = this.STATUS_ADDED;
this.setItemsCodesStatus[setItemCode] = this.STATUS_ADDED;
if (this._isCodeInPartialValues(tagSetCode)) {
this.partialValues = this.partialValues.filter(item => (item !== tagSetCode));
if (this._isCodeInPartialValues(setItemCode)) {
this.partialValues = this.partialValues.filter(item => (item !== setItemCode));
} else {
if (this.originalValue.indexOf(tagSetCode) !== -1) {
if (this.originalValue.indexOf(setItemCode) !== -1) {
// do not add if was present initially and removed
this.tagSetCodesStatus[tagSetCode] = this.STATUS_NEUTRAL;
this.setItemsCodesStatus[setItemCode] = this.STATUS_NEUTRAL;
}
}
this.refresh();
},
_onTagRemove: function (tagSetCode, inputWidget) {
this.tagSetCodesStatus[tagSetCode] = this.STATUS_REMOVED;
_onTagRemove: function (setItemCode, inputWidget) {
this.setItemsCodesStatus[setItemCode] = this.STATUS_REMOVED;
if (this._isCodeInPartialValues(tagSetCode)) {
if (this._isCodeInPartialValues(setItemCode)) {
// force rendering items again, otherwise partial class will be kept
// can'be in the onItemAdd callback as it is called after the render callback...
inputWidget.clearCache("item");
}
if (this.originalValue.indexOf(tagSetCode) === -1) {
if (this.originalValue.indexOf(setItemCode) === -1) {
// do not remove if wasn't present initially
this.tagSetCodesStatus[tagSetCode] = this.STATUS_NEUTRAL;
this.setItemsCodesStatus[setItemCode] = this.STATUS_NEUTRAL;
}
this.refresh();
},
_isCodeInPartialValues: function (tagSetCode) {
return (this.partialValues.indexOf(tagSetCode) >= 0);
_isCodeInPartialValues: function (setItemCode) {
return (this.partialValues.indexOf(setItemCode) >= 0);
}
});

View File

@@ -0,0 +1,96 @@
<!--
~ Copyright (c) 2010-2018 Combodo SARL
~
~ This file is part of iTop.
~
~ iTop is free software; you can redistribute it and/or modify
~ it under the terms of the GNU Affero General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ iTop 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 Affero General Public License for more details.
~
~ You should have received a copy of the GNU Affero General Public License
~ along with iTop. If not, see <http://www.gnu.org/licenses/>
~
-->
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>AttributeSet fields widget test</title>
<script>
var aDictEntries = {"Core:AttributeSet:placeholder": "click to add"};
</script>
<script src="../js/utils.js"></script>
<script src="../js/jquery-3.3.1.min.js"></script>
<script src="../js/jquery-ui-1.11.4.custom.min.js"></script>
<script src="../js/selectize.min.js"></script>
<script src="../js/jquery.itop-set-widget.js"></script>
<link rel="stylesheet" type="text/css" href="../css/light-grey.css">
<link rel="stylesheet" type="text/css" href="../css/selectize.default.css">
</head>
<body>
<form>
<div class="field_container">
<div>
<div class="field_value">
<div class="attribute-edit">
<div class="field_input_zone field_input_tagset">
<label>Exemple un champ : </label>
<textarea id="tagset-field" rows="30" cols="60">
{
"possible_values": [
{
"code": "critical",
"label": "Critical ticket"
},
{
"code": "high",
"label": "don't forget it !"
},
{
"code": "normal",
"label": "when time available"
},
{
"code": "low",
"label": "don't worry ;)"
}
],
"max_items_allowed": 3,
"partial_values": [
"low"
],
"orig_value": [
"critical",
"low"
],
"added": "",
"removed": ""
}
</textarea>
</div>
</div>
</div>
</div>
</div>
</form>
<script>
$("#tagset-field").set_widget({isDebug: true});
</script>
</body>
</html>

View File

@@ -1,117 +0,0 @@
<!--
~ Copyright (c) 2010-2018 Combodo SARL
~
~ This file is part of iTop.
~
~ iTop is free software; you can redistribute it and/or modify
~ it under the terms of the GNU Affero General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ iTop 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 Affero General Public License for more details.
~
~ You should have received a copy of the GNU Affero General Public License
~ along with iTop. If not, see <http://www.gnu.org/licenses/>
~
-->
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>TagSet fields widget test</title>
<script>
var aDictEntries = {"Core:AttributeTagSet:placeholder": "click to add"};
</script>
<script src="../js/utils.js"></script>
<script src="../js/jquery-3.3.1.min.js"></script>
<script src="../js/jquery-ui-1.11.4.custom.min.js"></script>
<script src="../js/selectize.min.js"></script>
<script src="../js/jquery.itop-tagset-widget.js"></script>
<link rel="stylesheet" type="text/css" href="../css/selectize.default.css">
<style>
div.selectize-control {
position: static;
display: inline-block;
vertical-align: middle;
}
div.selectize-input {
width: auto;
min-width: 8em;
}
div.selectize-input.has-items:after {
content: "";
display: inline-block;
}
div.selectize-control > div.selectize-input > div.item.partial-code {
color: floralwhite;
background-color: grey;
background-image: linear-gradient(to bottom, white, grey);
border-color: darkgray;
font-style: italic;
}
div.selectize-control > div.selectize-input > div.item.partial-code.active {
background-image: linear-gradient(to bottom, grey, darkgrey);
}
div.selectize-control > div.selectize-input > div.item.partial-code > a.remove {
border-color: grey;
}
</style>
</head>
<body>
<form>
<label>Exemple un champ : </label>
<textarea id="tagset-field" rows="30" cols="60">
{
"possible_values": [
{
"code": "critical",
"label": "Critical ticket"
},
{
"code": "high",
"label": "don't forget it !"
},
{
"code": "normal",
"label": "when time available"
},
{
"code": "low",
"label": "don't worry ;)"
}
],
"partial_values": [
"low"
],
"orig_value": [
"critical",
"low"
],
"added": "",
"removed": ""
}
</textarea>
</form>
<script>
$("#tagset-field").tagset_widget({isDebug: true});
</script>
</body>
</html>