Commit f87a4346 authored by Romain Courteaud's avatar Romain Courteaud

[erp5_web_renderjs_ui] Search editor: support form submit with enter

Reduce the number of event listeners.
parent 69024c79
...@@ -527,6 +527,7 @@ div[data-gadget-scope='editor_panel'] { ...@@ -527,6 +527,7 @@ div[data-gadget-scope='editor_panel'] {
div[data-gadget-scope='editor_panel'] div[data-role="header"] { div[data-gadget-scope='editor_panel'] div[data-role="header"] {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
flex-direction: row-reverse;
} }
div[data-gadget-scope='editor_panel'] div[data-role="header"] h1 { div[data-gadget-scope='editor_panel'] div[data-role="header"] h1 {
text-align: left; text-align: left;
......
...@@ -242,7 +242,7 @@ ...@@ -242,7 +242,7 @@
</item> </item>
<item> <item>
<key> <string>serial</string> </key> <key> <string>serial</string> </key>
<value> <string>955.29626.47611.24814</string> </value> <value> <string>955.33777.42224.15633</string> </value>
</item> </item>
<item> <item>
<key> <string>state</string> </key> <key> <string>state</string> </key>
...@@ -260,7 +260,7 @@ ...@@ -260,7 +260,7 @@
</tuple> </tuple>
<state> <state>
<tuple> <tuple>
<float>1480081058.6</float> <float>1480337940.73</float>
<string>UTC</string> <string>UTC</string>
</tuple> </tuple>
</state> </state>
......
...@@ -26,26 +26,23 @@ ...@@ -26,26 +26,23 @@
<!-- custom script --> <!-- custom script -->
<script src="gadget_erp5_search_editor.js"></script> <script src="gadget_erp5_search_editor.js"></script>
<script id="options-template" type="text/x-handlebars-template"> <script id="options-template" type="text/x-handlebars-template">
<select data-iconpos="left"> <select data-iconpos="left">
{{#each option}} {{#each option}}
<option value="{{value}}" data-i18n="{{text}}">{{text}}</option> <option value="{{value}}" data-i18n="{{text}}">{{text}}</option>
{{/each}} {{/each}}
</select> </select>
</script> </script>
<script id="filter-item-template" type="text/x-handlebars-template"> <script id="filter-item-template" type="text/x-handlebars-template">
<button type="submit" class="ui-icon ui-btn ui-btn-inline ui-icon-minus ui-icon-shadow"></button> <button class="ui-icon ui-btn ui-btn-inline ui-icon-minus ui-icon-shadow"></button>
<div class="filter_item {{class_value}}" > <div class="filter_item {{class_value}}" >
<select data-iconpos="left"> <select class="column" data-iconpos="left">
{{#each option}} {{#each option}}
{{#equal value selected_option}} {{#equal value selected_option}}
<option selected="selected" data-i18n="{{text}}" value="{{value}}">{{text}}</option> <option selected="selected" data-i18n="{{text}}" value="{{value}}">{{text}}</option>
{{else}} {{else}}
<option value="{{value}}" data-i18n="{{text}}">{{text}}</option> <option value="{{value}}" data-i18n="{{text}}">{{text}}</option>
{{/equal}} {{/equal}}
{{/each}} {{/each}}
</select> </select>
...@@ -54,7 +51,7 @@ ...@@ -54,7 +51,7 @@
{{#equal value selected_option}} {{#equal value selected_option}}
<option selected="selected" data-i18n="{{text}}" value="{{value}}">{{text}}</option> <option selected="selected" data-i18n="{{text}}" value="{{value}}">{{text}}</option>
{{else}} {{else}}
<option value="{{value}}" data-i18n="{{text}}">{{text}}</option> <option value="{{value}}" data-i18n="{{text}}">{{text}}</option>
{{/equal}} {{/equal}}
{{/each}} {{/each}}
</select> </select>
...@@ -62,54 +59,41 @@ ...@@ -62,54 +59,41 @@
<input type="{{input_type}}" value="{{input_value}}"></input> <input type="{{input_type}}" value="{{input_value}}"></input>
</div> </div>
</div> </div>
</script> </script>
<script id="filter-template" type="text/x-handlebars-template">
<div class="ui-panel-inner">
<div data-role="header" role="banner" class="ui-header ui-bar-inherit">
<div class="ui-controlgroup ui-controlgroup-horizontal ui-btn-right">
<div class="ui-controlgroup-controls">
<script id="filter-template" type="text/x-handlebars-template"> <button data-i18n="submit" type="submit" class="submit responsive ui-last-child ui-btn ui-btn-icon-left ui-icon-check">Submit</button>
<div class="ui-panel-inner">
<div data-role="header" role="banner" class="ui-header ui-bar-inherit">
<div class="ui-controlgroup ui-controlgroup-horizontal ui-btn-left">
<div class="ui-controlgroup-controls">
<form class="delete">
<button data-rel="close" type="submit" data-i18n="Close" class="close responsive ui-first-child ui-btn ui-btn-icon-left ui-icon-times">Close</button>
</form>
</div>
</div> </div>
</div>
<h1 class="ui-title" role="heading" data-i18n="Filter Editor" aria-level="1">Filter Editor</h1>
<div class="ui-controlgroup ui-controlgroup-horizontal ui-btn-right"> <h1 class="ui-title" role="heading" data-i18n="Filter Editor" aria-level="1">Filter Editor</h1>
<div class="ui-controlgroup-controls"> <div class="ui-controlgroup ui-controlgroup-horizontal ui-btn-left">
<form class="submit"> <div class="ui-controlgroup-controls">
<button data-rel="save" data-i18n="submit" type="submit" class="submit responsive ui-last-child ui-btn ui-btn-icon-left ui-icon-check">Submit</button> <button data-i18n="Close" class="close responsive ui-first-child ui-btn ui-btn-icon-left ui-icon-times">Close</button>
</form>
</div>
</div> </div>
</div> </div>
</div>
<section class="ui-body-c ui-content-section"> <section class="ui-body-c ui-content-section">
<fieldset class="ui-controlgroup ui-corner-all"> <fieldset class="ui-controlgroup ui-corner-all">
<select data-iconpos="left" name="heard_about"> <select data-iconpos="left" name="heard_about">
<option data-i18n="All criterions (AND)" value="AND">All criterions (AND)</option> <option data-i18n="All criterions (AND)" value="AND">All criterions (AND)</option>
<option data-i18n="At least one (OR)" value="OR">At least one (OR)</option> <option data-i18n="At least one (OR)" value="OR">At least one (OR)</option>
</select> </select>
</fieldset> </fieldset>
<div class="filter_item_container"> <div class="filter_item_container">
</div> </div>
<form class="plus"> <button class="plus ui-btn-c ui-override-theme ui-btn ui-icon-plus ui-btn-icon-left ui-corner-all">Add Criteria</button>
<button type="submit" class="plus ui-btn-c ui-override-theme ui-btn ui-icon-plus ui-btn-icon-left ui-corner-all">Add Criteria</button> </section>
</form> </div>
</section>
</div>
</script> </script>
</head> </head>
<body> <body>
<form class="filter_editor"> <form class="filter_editor">
......
...@@ -234,7 +234,7 @@ ...@@ -234,7 +234,7 @@
</item> </item>
<item> <item>
<key> <string>serial</string> </key> <key> <string>serial</string> </key>
<value> <string>954.7210.40753.48042</string> </value> <value> <string>955.38108.27853.11810</string> </value>
</item> </item>
<item> <item>
<key> <string>state</string> </key> <key> <string>state</string> </key>
...@@ -252,7 +252,7 @@ ...@@ -252,7 +252,7 @@
</tuple> </tuple>
<state> <state>
<tuple> <tuple>
<float>1474905528.91</float> <float>1480342657.32</float>
<string>UTC</string> <string>UTC</string>
</tuple> </tuple>
</state> </state>
......
/*jslint indent: 2, maxerr: 3, maxlen: 100, nomen: true */ /*jslint indent: 2, maxerr: 3, maxlen: 100, nomen: true */
/*global window, document, rJS, RSVP, Handlebars, loopEventListener, /*global window, document, rJS, RSVP, Handlebars,
QueryFactory, SimpleQuery, ComplexQuery, Query, console*/ QueryFactory, SimpleQuery, ComplexQuery, Query, console*/
(function (window, document, rJS, RSVP, Handlebars, loopEventListener, (function (window, document, rJS, RSVP, Handlebars,
QueryFactory, SimpleQuery, ComplexQuery, Query, console) { QueryFactory, SimpleQuery, ComplexQuery, Query, console) {
"use strict"; "use strict";
var gadget_klass = rJS(window), var gadget_klass = rJS(window),
filter_item_source = gadget_klass.__template_element template_element = gadget_klass.__template_element,
filter_item_template = Handlebars.compile(template_element
.getElementById("filter-item-template") .getElementById("filter-item-template")
.innerHTML, .innerHTML),
filter_item_template = Handlebars.compile(filter_item_source), filter_template = Handlebars.compile(template_element
filter_source = gadget_klass.__template_element
.getElementById("filter-template") .getElementById("filter-template")
.innerHTML, .innerHTML),
filter_template = Handlebars.compile(filter_source), options_template = Handlebars.compile(template_element
options_source = gadget_klass.__template_element
.getElementById("options-template") .getElementById("options-template")
.innerHTML, .innerHTML),
options_template = Handlebars.compile(options_source); NUMERIC = [
["Equals To", "="], ["Greater Than", ">"],
["Less Than", "<"], ["Not Greater Than", "<="],
["Not Less Than", ">="]
],
OTHER = [
["Exact Match", "exacte_match"],
["keyword", "keyword"]
],
DEFAULT = [["Contain", "Contain"]];
Handlebars.registerHelper('equal', function (left_value, Handlebars.registerHelper('equal', function (left_value, right_value, options) {
right_value, options) {
if (arguments.length < 3) { if (arguments.length < 3) {
throw new Error("Handlebars Helper equal needs 2 parameters"); throw new Error("Handlebars Helper equal needs 2 parameters");
} }
...@@ -30,9 +37,9 @@ ...@@ -30,9 +37,9 @@
return options.fn(this); return options.fn(this);
}); });
//XXXXX // XXX
//define input's type according to column's value // define input's type according to column's value
//the way to determiner is not generic // the way to determiner is not generic
function isNumericComparison(value) { function isNumericComparison(value) {
return value.indexOf('date') !== -1 || return value.indexOf('date') !== -1 ||
value.indexOf('quantity') !== -1 || value.indexOf('quantity') !== -1 ||
...@@ -45,27 +52,24 @@ ...@@ -45,27 +52,24 @@
i; i;
if (value !== "searchable_text") { if (value !== "searchable_text") {
if (isNumericComparison(value)) { if (isNumericComparison(value)) {
tmp = gadget.props.numeric; tmp = NUMERIC;
} else { } else {
tmp = gadget.props.other; tmp = OTHER;
} }
} else { } else {
tmp = gadget.props.default; tmp = DEFAULT;
} }
for (i = 0; i < tmp.length; i += 1) { for (i = 0; i < tmp.length; i += 1) {
option.push({ option.push({
"text": tmp[i][0], text: tmp[i][0],
"value": tmp[i][1] value: tmp[i][1]
}); });
} }
return gadget.translateHtml(options_template({option: option})); return gadget.translateHtml(options_template({option: option}));
} }
function createFilterItemTemplate(gadget, class_value, filter_item) { function createFilterItemTemplate(gadget, class_value, filter_item) {
var column_list = gadget.props.search_column_list, var column_list = gadget.state.search_column_list,
option = [], option = [],
tmp, tmp,
operator_option = [], operator_option = [],
...@@ -74,33 +78,33 @@ ...@@ -74,33 +78,33 @@
if (filter_item) { if (filter_item) {
if (isNumericComparison(filter_item.key)) { if (isNumericComparison(filter_item.key)) {
tmp = gadget.props.numeric; tmp = NUMERIC;
if (filter_item.key.indexOf("date") !== -1) { if (filter_item.key.indexOf("date") !== -1) {
input_type = "date"; input_type = "date";
} else { } else {
input_type = "number"; input_type = "number";
} }
} else { } else {
tmp = gadget.props.other; tmp = OTHER;
} }
} else { } else {
tmp = gadget.props.default; tmp = DEFAULT;
filter_item = {}; filter_item = {};
} }
for (i = 0; i < tmp.length; i += 1) { for (i = 0; i < tmp.length; i += 1) {
operator_option.push({ operator_option.push({
"text": tmp[i][0], text: tmp[i][0],
"value": tmp[i][1], value: tmp[i][1],
"selected_option": filter_item.operator selected_option: filter_item.operator
}); });
} }
for (i = 0; i < column_list.length; i += 1) { for (i = 0; i < column_list.length; i += 1) {
option.push({ option.push({
"text": column_list[i][1], text: column_list[i][1],
"value": column_list[i][0], value: column_list[i][0],
"selected_option": filter_item.key || "searchable_text" selected_option: filter_item.key || "searchable_text"
}); });
} }
return gadget.translateHtml(filter_item_template({ return gadget.translateHtml(filter_item_template({
...@@ -112,256 +116,185 @@ ...@@ -112,256 +116,185 @@
})); }));
} }
gadget_klass
function listenToSelect(gadget, class_value) {
var list = [],
i,
filter_item_list =
gadget.props.element.querySelectorAll("." + class_value);
function createFunction(i) {
var select_list = filter_item_list[i].querySelectorAll("select"),
input = filter_item_list[i].querySelector("input");
return loopEventListener(
select_list[0],
"change",
false,
function (event) {
return new RSVP.Queue()
.push(function () {
return createOptionsTemplate(gadget, event.target.value);
})
.push(function (innerHTML) {
select_list[1].innerHTML = innerHTML;
if (isNumericComparison(event.target.value)) {
if (event.target.value.indexOf("date") !== -1) {
input.setAttribute("type", "date");
} else {
input.setAttribute("type", "number");
}
} else {
input.setAttribute("type", "text");
}
});
}
);
}
for (i = 0; i < filter_item_list.length; i += 1) {
list.push(createFunction(i));
}
return RSVP.all(list);
}
rJS(window)
/////////////////////////////////////////////////////////////////
// ready
/////////////////////////////////////////////////////////////////
// Init local properties
.ready(function (g) {
g.props = {};
})
.ready(function (g) {
return g.getElement()
.push(function (element) {
g.props.element = element;
g.props.numeric = [["Equals To", "="], ["Greater Than", ">"],
["Less Than", "<"], ["Not Greater Than", "<="],
["Not Less Than", ">="]];
g.props.other = [["Exact Match", "exacte_match"],
["keyword", "keyword"]];
g.props.default = [["Contain", "Contain"]];
});
})
////////////////////////////////////////////// //////////////////////////////////////////////
// acquired method // acquired method
////////////////////////////////////////////// //////////////////////////////////////////////
.declareAcquiredMethod("translateHtml", "translateHtml") .declareAcquiredMethod("translateHtml", "translateHtml")
.declareAcquiredMethod("redirect", "redirect") .declareAcquiredMethod("redirect", "redirect")
.declareAcquiredMethod("trigger", "trigger") .declareAcquiredMethod("trigger", "trigger")
////////////////////////////////////////////// //////////////////////////////////////////////
// initialize the gadget content // initialize the gadget content
////////////////////////////////////////////// //////////////////////////////////////////////
.declareMethod('render', function (options) { .onStateChange(function () {
var gadget = this; var gadget = this,
gadget.props.search_column_list = options.search_column_list; container = gadget.element.querySelector(".container"),
gadget.props.begin_from = options.begin_from; div = document.createElement("div"),
operator_select,
filter_item_container,
query_list = [],
promise_list = [],
i;
return gadget.translateHtml(filter_template())
.push(function (translated_html) {
div.innerHTML = translated_html;
gadget.props.extended_search = options.extended_search; operator_select = div.querySelector("select");
filter_item_container = div.querySelector(".filter_item_container");
return new RSVP.Queue() if (gadget.state.extended_search) {
.push(function () { // string to query
var tmp = filter_template(); try {
return gadget.translateHtml(tmp); query_list = QueryFactory.create(gadget.state.extended_search);
} catch (error) {
// XXX hack to not crash interface
// it catch all error, not only search criteria invalid error
console.warn(error);
return [];
}
if (query_list.operator === "OR") {
operator_select.querySelectorAll("option")[1].selected = "selected";
}
query_list = query_list.query_list || [query_list];
for (i = 0; i < query_list.length; i += 1) {
promise_list.push(createFilterItemTemplate(gadget, "auto", query_list[i]));
}
}
return RSVP.all(promise_list);
}) })
.push(function (translated_html) { .push(function (result_list) {
var tmp = document.createElement("div"); var subdiv;
tmp.innerHTML = translated_html; for (i = 0; i < result_list.length; i += 1) {
gadget.props.element.querySelector(".container").appendChild(tmp); subdiv = document.createElement("div");
subdiv.innerHTML = result_list[i];
filter_item_container.appendChild(subdiv);
}
while (container.firstChild) {
container.removeChild(container.firstChild);
}
container.appendChild(div);
}); });
}) })
//////////////////////////////////////////////
.declareService(function () { .declareMethod('render', function (options) {
var gadget = this, return this.changeState({
i, search_column_list: options.search_column_list,
list = [], begin_from: options.begin_from,
operator_select = gadget.props.element.querySelector("select"), extended_search: options.extended_search
container = gadget.props.element.querySelector(".filter_item_container"), });
query_list; })
if (gadget.props.extended_search) {
//string to query .onEvent('submit', function () {
try { var i,
query_list = QueryFactory.create(gadget.props.extended_search); gadget = this,
} catch (error) { simple_operator,
//XXXX hack to not crash interface query,
//it catch all error, not only search criteria invalid error key,
console.warn(error); select_list,
return; simple_query_list = [],
} complex_query,
if (query_list.operator === "OR") { select,
operator_select.querySelectorAll("option")[1].selected = "selected"; value,
options = {},
filter_item_list = gadget.element.querySelectorAll(".filter_item"),
operator_select = gadget.element.querySelector("select"),
operator = operator_select[operator_select.selectedIndex].value;
for (i = 0; i < filter_item_list.length; i += 1) {
select_list = filter_item_list[i].querySelectorAll("select");
value = filter_item_list[i].querySelector("input").value;
simple_operator = "";
select = select_list[1][select_list[1].selectedIndex].value;
if (select === "keyword") {
value = "%" + value + "%";
} else if (["", ">", "<", "<=", ">="].indexOf(select) !== -1) {
simple_operator = select;
} }
query_list = query_list.query_list || [query_list]; if (select_list[0][select_list[0].selectedIndex].value === "searchable_text") {
for (i = 0; i < query_list.length; i += 1) { key = "";
list.push(createFilterItemTemplate(gadget, "auto", query_list[i])); } else {
key = select_list[0][select_list[0].selectedIndex].value;
} }
return RSVP.Queue()
.push(function () { simple_query_list.push(new SimpleQuery(
return RSVP.all(list); {
}) key: key,
.push(function (all_result) { operator: simple_operator,
var div; type: "simple",
for (i = 0; i < all_result.length; i += 1) { value: value
div = document.createElement("div");
div.innerHTML = all_result[i];
container.appendChild(div);
}
return listenToSelect(gadget, "auto");
});
}
})
.declareService(function () {
var gadget = this,
container = gadget.props.element.querySelector(".filter_item_container");
return loopEventListener(
gadget.props.element.querySelector(".filter_editor"),
"submit",
false,
function () {
var focused = document.activeElement;
if (focused.nodeName === "BUTTON") {
container.removeChild(focused.parentElement);
} }
} ));
); }
if (simple_query_list.length > 0) {
complex_query = new ComplexQuery({
operator: operator,
query_list: simple_query_list,
type: "complex"
});
//query to string
query = Query.objectToSearchText(complex_query);
} else {
query = "";
}
options.extended_search = query;
options[gadget.state.begin_from] = undefined;
return gadget.redirect({
command: 'store_and_change',
options : options
});
}) })
.declareService(function () {
.onEvent('click', function (evt) {
var gadget = this; var gadget = this;
return loopEventListener(
gadget.props.element.querySelector(".submit"),
"submit",
false,
function () {
var i,
simple_operator,
query,
key,
select_list,
simple_query_list = [],
complex_query,
select,
value,
options = {},
filter_item_list = gadget.props.element.querySelectorAll(".filter_item"),
operator_select = gadget.props.element.querySelector("select"),
operator = operator_select[operator_select.selectedIndex].value;
for (i = 0; i < filter_item_list.length; i += 1) {
select_list = filter_item_list[i].querySelectorAll("select");
value = filter_item_list[i].querySelector("input").value;
simple_operator = "";
select = select_list[1][select_list[1].selectedIndex].value;
if (select === "keyword") {
value = "%" + value + "%";
} else if (["", ">", "<", "<=", ">="].indexOf(select) !== -1) {
simple_operator = select;
}
if (select_list[0][select_list[0].selectedIndex].value === "searchable_text") { if (evt.target.classList.contains('close')) {
key = ""; evt.preventDefault();
} else { return this.trigger();
key = select_list[0][select_list[0].selectedIndex].value; }
}
simple_query_list.push(new SimpleQuery( if (evt.target.classList.contains('plus')) {
{ evt.preventDefault();
key: key, return createFilterItemTemplate(gadget, 'auto')
operator: simple_operator, .push(function (template) {
type: "simple", var tmp = document.createElement("div"),
value: value container = gadget.element.querySelector(".filter_item_container");
} tmp.innerHTML = template;
)); container.appendChild(tmp);
} });
}
if (simple_query_list.length > 0) { if (evt.target.classList.contains('ui-icon-minus')) {
complex_query = new ComplexQuery({ evt.preventDefault();
operator: operator, evt.target.parentElement.parentElement.removeChild(evt.target.parentElement);
query_list: simple_query_list, }
type: "complex" }, false, false)
});
//query to string .onEvent('change', function (evt) {
query = Query.objectToSearchText(complex_query);
} else {
query = "";
}
options.extended_search = query;
options[gadget.props.begin_from] = undefined;
return gadget.redirect(
{
command: 'store_and_change',
options : options
}
);
}
);
})
.declareService(function () {
var gadget = this,
class_value = "add_after";
return loopEventListener(
gadget.props.element.querySelector(".plus"),
"submit",
false,
function () {
return new RSVP.Queue()
.push(function () {
return createFilterItemTemplate(gadget, class_value);
})
.push(function (template) {
var tmp = document.createElement("div"),
container = gadget.props.element.querySelector(".filter_item_container");
tmp.innerHTML = template;
container.appendChild(tmp);
return listenToSelect(gadget, class_value);
});
}
);
})
.declareService(function () {
var gadget = this; var gadget = this;
return loopEventListener( if (evt.target.classList.contains('column')) {
gadget.props.element.querySelector(".delete"), evt.preventDefault();
"submit", return createOptionsTemplate(gadget, evt.target.value)
false, .push(function (innerHTML) {
function () { evt.target.parentElement.querySelectorAll('select')[1].innerHTML = innerHTML;
return gadget.trigger(); if (isNumericComparison(evt.target.value)) {
} if (evt.target.value.indexOf("date") !== -1) {
); evt.target.parentElement.querySelector('input').setAttribute("type", "date");
}); } else {
evt.target.parentElement.querySelector('input').setAttribute("type", "number");
}
} else {
evt.target.parentElement.querySelector('input').setAttribute("type", "text");
}
});
}
}, false, false);
}(window, document, rJS, RSVP, Handlebars, loopEventListener, }(window, document, rJS, RSVP, Handlebars,
QueryFactory, SimpleQuery, ComplexQuery, Query, console)); QueryFactory, SimpleQuery, ComplexQuery, Query, console));
\ No newline at end of file
...@@ -230,7 +230,7 @@ ...@@ -230,7 +230,7 @@
</item> </item>
<item> <item>
<key> <string>serial</string> </key> <key> <string>serial</string> </key>
<value> <string>952.34519.30853.16520</string> </value> <value> <string>955.38135.49149.41489</string> </value>
</item> </item>
<item> <item>
<key> <string>state</string> </key> <key> <string>state</string> </key>
...@@ -248,7 +248,7 @@ ...@@ -248,7 +248,7 @@
</tuple> </tuple>
<state> <state>
<tuple> <tuple>
<float>1468415510.46</float> <float>1480342558.79</float>
<string>UTC</string> <string>UTC</string>
</tuple> </tuple>
</state> </state>
......
...@@ -633,6 +633,7 @@ div[data-gadget-scope='editor_panel'] { ...@@ -633,6 +633,7 @@ div[data-gadget-scope='editor_panel'] {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
flex-direction: row-reverse;
h1 { h1 {
text-align: left; text-align: left;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment